diff --git a/src/xbus/libxplanemp/.gitignore b/src/xbus/libxplanemp/.gitignore new file mode 100644 index 000000000..bfdbb50e1 --- /dev/null +++ b/src/xbus/libxplanemp/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.localized + +# Build directory +build/ + +# XCode user specific files +xcuserdata/ +*.xcworkspace/ +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.pbxuser diff --git a/src/xbus/libxplanemp/README.MULTIPLAYER b/src/xbus/libxplanemp/README.MULTIPLAYER new file mode 100644 index 000000000..e75d46ff7 --- /dev/null +++ b/src/xbus/libxplanemp/README.MULTIPLAYER @@ -0,0 +1,68 @@ +Copyright (c) 2006, Ben Supnik and Chris Serio. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS 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. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +****************************************************************************** + XSQUAWKBOX MULTIPLAYER ENGINE - README +****************************************************************************** + +Revision History + 3/14/05 - Initial draft + +INTRO + +This snippet of code is the XSB multiplayer engine, that is, the code +responsible for visualizing planes. It is provided totally as-is with +no guarantees of working or being rmotely useful. + +Two files, XPLMMultiplayer.cpp and XPLMPlaneRenderer.cpp implement the +lib. You must include XPLMMultiplayer.h to use it, and provide a text file +with your plugin to config multiplayer. + +HISTORY + +PATH NAMES AND CROSS-PLATFORM CODE + +Paths for XPMP are always "native". This means: + +- Backslash separators on Windows. +- Forward slash separators on Macintosh and Linux. +- For Macintosh (which has both "HFS" and "Posix" style paths) posix paths + are used. + +Clients initializing the library are responsible for passing paths in these +formats. (Macintosh clients can convert from HFS to Posix using the routine +HFS2PosixPath, which internally uses CFURL for the conversion.) + +The X-Plane SDK uses native paths, but on Macintosh uses HFS paths. XPMP +on Mac converts paths from HFS to Posix at all points of interaction with +the SDK. + +The paths in some X-Plane formats like OBJ can often have any directory +separator. These paths are always partial paths; XPMP converts them to +native separators when they are read in. + +Therefore the following rules should be followed when using/modifying XPMP: + +- Always pass native paths to the lib and expect native paths out. +- When getting file paths from external sources (like OBJ files) make them + native immediately. +- Mac only: In the lib wrap any SDK call with HFS->Posix conversion if + needed. \ No newline at end of file diff --git a/src/xbus/libxplanemp/README.markdown b/src/xbus/libxplanemp/README.markdown new file mode 100644 index 000000000..5b6b6d195 --- /dev/null +++ b/src/xbus/libxplanemp/README.markdown @@ -0,0 +1,67 @@ +#libxplanemp +###Multiplayer library for X-Plane + +This is libxplanemp, the multiplayer code for X-Plane. This code is +used in both the XSquawkBox and X-IvAp projects. + +See README.MULTIPLAYER for more information. + +To clone this repository to your local drive, use: +git clone https://github.com/wadesworld2112/libxplanemp + +###Compilation Note + +The files CSLLoaderThread.h and CSLLoaderThread.cpp are incomplete and will generate +errors. They are not currently used. The library will compile if these +files are not included. + +Fixes to these files are welcome. + +## Note to X-Plane and X-IvAp Developers + +This is now the authoratative repository for the mulitplayer code. If you +have previously been using Martin Domig's X-IvAp subversion repository on SourceForge, +the multiplayer code contained within that repository is now deprecated and will +be removed. + +Similarly, if you have been using the xivap repository provided by VATSIM, that +repository is now deprecated and will be deleted. + +All future updates will be made to this repository. + +##Migration Note + +The two separate versions of the multiplayer code have been combined into this +repository. Tags have been added for ease of access, should you +desire to obtain the code in the state it existed at the time of the +repository merge. + +To do so, after cloning the repository, run either of the following commands: + +Multiplayer as it existed on X-IvAp SourceForge: +git checkout -b <new branch name> last_xivap_subversion + +Multiplayer as it existed on XSB/VATSIM git repository: +git checkout -b < + +class XPCAircraft { +public: + + XPCAircraft( + const char * inICAOCode, + const char * inAirline, + const char * inLivery); + virtual ~XPCAircraft(); + + virtual XPMPPlaneCallbackResult GetPlanePosition( + XPMPPlanePosition_t * outPosition)=0; + + virtual XPMPPlaneCallbackResult GetPlaneSurfaces( + XPMPPlaneSurfaces_t * outSurfaces)=0; + + virtual XPMPPlaneCallbackResult GetPlaneRadar( + XPMPPlaneRadar_t * outRadar)=0; +protected: + + XPMPPlaneID mPlane; + + static XPMPPlaneCallbackResult AircraftCB( + XPMPPlaneID inPlane, + XPMPPlaneDataType inDataType, + void * ioData, + void * inRefcon); + +}; + +#endif diff --git a/src/xbus/libxplanemp/include/XPMPMultiplayer.h b/src/xbus/libxplanemp/include/XPMPMultiplayer.h new file mode 100644 index 000000000..6916f8636 --- /dev/null +++ b/src/xbus/libxplanemp/include/XPMPMultiplayer.h @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2004, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _XPLMMultiplayer_h_ +#define _XPLMMultiplayer_h_ + +#include "XPLMDefs.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************************************************************ + * X-PLANE MULTIPLAYER + ************************************************************************************/ + +/* + Multiplayer - THEORY OF OPERATION + + The multiplayer API allows plug-ins to control aircraft visible to other plug-ins and + the user via x-plane. It effectively provides glue between a series of observers + that wish to render or in other ways act upon those planes. + + A plug-in can control zero or more planes, and zero or more plug-ins can control planes. + However, each plane is controlled by exactly one plug-in. A plug-in thus dynamically + allocates planes to control. A plug-in registers a callback which is used to pull + information. The plug-in may decide to not return information or state that that + information is unchanged. + + A plug-in can also read the current aircrafts or any of their data. Aircraft data is + cached to guarantee minimum computing of data. + + Each 'kind' of data has an enumeration and corresponding structure.*/ + + +/************************************************************************************ + * PLANE DATA TYPES + ************************************************************************************/ + +/* + * XPMPPosition_t + * + * This data structure contains the basic position info for an aircraft. + * Lat and lon are the position of the aircraft in the world. They are double-precision to + * provide reasonably precise positioning anywhere. + * Elevation is in feet above mean sea level. + * + * Pitch, roll, and heading define the aircraft's orientation. Heading is in degrees, positive + * is clockwise from north. Pitch is the number of degrees, positive is nose up, and roll + * is positive equals roll right. + * + * Note that there is no notion of aircraft velocity or acceleration; you will be queried for + * your position every rendering frame. Higher level APIs can use velocity and acceleration. + * + */ +typedef struct { + long size; + double lat; + double lon; + double elevation; + float pitch; + float roll; + float heading; + char label[32]; +} XPMPPlanePosition_t; + + +/* + * XPMPLightStatus + * + * This enum defines the settings for the lights bitfield in XPMPPlaneSurfaces_t + * + * The upper 16 bit of the light code (timeOffset) should be initialized only once + * with a random number by the application. This number will be used to have strobes + * flashing at different times. + */ +union xpmp_LightStatus { + unsigned int lightFlags; + struct { + unsigned int timeOffset : 16; + + unsigned int landLights : 1; + unsigned int bcnLights : 1; + unsigned int strbLights : 1; + unsigned int navLights : 1; + + unsigned int flashPattern : 4; + }; +}; + +/* + * Light flash patterns + */ +enum { + xpmp_Lights_Pattern_Default = 0, // Jets: one strobe flash, short beacon (-*---*---*---) + xpmp_Lights_Pattern_EADS = 1, // Airbus+EADS: strobe flashes twice (-*-*-----*-*--), short beacon + xpmp_Lights_Pattern_GA = 2 // GA: one strobe flash, long beacon (-*--------*---) +}; + + +/* + * XPMPPlaneSurfaces_t + * + * This data structure will contain information about the external physical configuration of the plane, + * things you would notice if you are seeing it from outside. This includes flap position, gear position, + * etc. + * + * Lights is a 32 bit field with flags as defined in XPMPLightStatus + * + */ +typedef struct { + long size; + float gearPosition; + float flapRatio; + float spoilerRatio; + float speedBrakeRatio; + float slatRatio; + float wingSweep; + float thrust; + float yokePitch; + float yokeHeading; + float yokeRoll; + xpmp_LightStatus lights; +} XPMPPlaneSurfaces_t; + + +/* + * XPMPTransponderMode + * + * These enumerations define the way the transponder of a given plane is operating. + * + */ +enum { + xpmpTransponderMode_Standby, + xpmpTransponderMode_Mode3A, + xpmpTransponderMode_ModeC, + xpmpTransponderMode_ModeC_Low, + xpmpTransponderMode_ModeC_Ident +}; +typedef int XPMPTransponderMode; + +/* + * XPMPPlaneRadar_t + * + * This structure defines information about an aircraft visible to radar. Eventually it can include + * information about radar profiles, stealth technology, radar jamming, etc. + * + */ +typedef struct { + long size; + long code; + XPMPTransponderMode mode; +} XPMPPlaneRadar_t; + +/* + * XPMPPlaneData + * + * This enum defines the different categories of aircraft information we can query about. + * + */ +enum { + xpmpDataType_Position = 1L << 1, + xpmpDataType_Surfaces = 1L << 2, + xpmpDataType_Radar = 1L << 3 +}; +typedef int XPMPPlaneDataType; + +/* + * XPMPPlaneCallbackResult + * + * This definfes the different responses to asking for information. + * + */ +enum { + xpmpData_Unavailable = 0, /* The information has never been specified. */ + xpmpData_Unchanged = 1, /* The information from the last time the plug-in was asked. */ + xpmpData_NewData = 2 /* The information has changed this sim cycle. */ +}; +typedef int XPMPPlaneCallbackResult; + +/* + * XPMPPlaneID + * + * This is a unique ID for an aircraft created by a plug-in. + * + */ +typedef void * XPMPPlaneID; + +/************************************************************************************ + * PLANE CREATION API + ************************************************************************************/ + +/* + * XPMPPlaneData_f + * + * This is the aircraft data providing function. It is called no more than once per sim + * cycle per data type by the plug-in manager to get data about your plane. The data passed + * in is a pointer to one of the above structures. The function specifies the datatype, and the + * last data you provided is passed in. + * + */ +typedef XPMPPlaneCallbackResult (* XPMPPlaneData_f)( + XPMPPlaneID inPlane, + XPMPPlaneDataType inDataType, + void * ioData, + void * inRefcon); + +/* + * XPMPMultiplayerInitLegacyData + * + * This routine initializes legacy portions of the multiplayer library. + * + * inPlaneList is a ptr to a fully qualified file name that is a text file of ICAO -> model + * mappings. The xsb_aircraft.txt file can be used as a template. (Please note: other + * XSB files are NOT necessary and are not necessarily available under open source license.) + * + * inDoc8643 is the path to the ICAO document 8643, available at + * http://www.icao.int/anb/ais/TxtFiles/Doc8643.txt. This file lists all aircraft types + * with their manufacturer, ICAO equipment code and aircraft category. + * + * The two prefs funcs each take an ini section and key and return a value and also take + * a default. The renderer uses them for configuration. Currently the following keys are + * needed: + * + * section key type default description + * planes full_distance float 3.0 + * planes max_full_count int 50 + * + * The return value is a string indicating any problem that may have gone wrong in a human-readable + * form, or an empty string if initalizatoin was okay. + * + * Call this once from your XPluginStart routine. + * + * Depending on which plane packages are installed this can take between 30 seconds and 15 minutes. + * + * After transitioning to exclusively OBJ8-based packages, this function should no longer be required. + * Instead, make separate calls to XPMPLoadCSLPackages and XPMPSetDefaultPlaneICAO. + * + */ +const char * XPMPMultiplayerInitLegacyData( + const char * inCSLFolder, + const char * inRelatedPath, + const char * inTexturePath, + const char * inDoc8643, + const char * inDefaltICAO, + int (* inIntPrefsFunc)(const char *, const char *, int), + float (* inFloatPrefsFunc)(const char *, const char *, float)); + +/* + * XPMPMultiplayerInit + * + * The two prefs funcs each take an ini section and key and return a value and also take + * a default. The renderer uses them for configuration. Currently the following keys are + * needed: + * + * section key type default description + * planes full_distance float 3.0 + * planes max_full_count int 50 + * + * The return value is a string indicating any problem that may have gone wrong in a human-readable + * form, or an empty string if initalizatoin was okay. + * + * Call this once, typically from your XPluginStart routine. + * + */ +const char * XPMPMultiplayerInit( + int (* inIntPrefsFunc)(const char *, const char *, int), + float (* inFloatPrefsFunc)(const char *, const char *, float)); + +/* + * XPMPMultiplayerEnable + * + * Enable drawing of multiplayer planes. Call this once from your XPluginEnable routine to + * grab multiplayer; an empty string is returned on success, or a human-readable error message + * otherwise. + * + */ +const char * XPMPMultiplayerEnable(void); + +/* + * XPMPMultiplayerDisable + * + * Disable drawing of multiplayer planes. Call this from XPluginDisable to release multiplayer. + * Reverses the actions on XPMPMultiplayerEnable. + */ +void XPMPMultiplayerDisable(void); + +/* + * XPMPMultiplayerCleanup + * + * Clean up the multiplayer library. Call this from XPluginStop to reverse the actions of + * XPMPMultiplayerInit as much as possible. + */ +void XPMPMultiplayerCleanup(void); + +/* + * XPMPLoadCSLPackage + * + * Loads a collection of planes + * + * inPlaneList is a ptr to a fully qualified file name that is a text file of ICAO -> model + * mappings. The xsb_aircraft.txt file can be used as a template. (Please note: other + * XSB files are NOT necessary and are not necessarily available under open source license.) + * + * inDoc8643 is the path to the ICAO document 8643, available at + * http://www.icao.int/anb/ais/TxtFiles/Doc8643.txt. This file lists all aircraft types + * with their manufacturer, ICAO equipment code and aircraft category. + * + * This is fast if the planes are all OBJ8 (because the objects are loaded asynchronously, + * on demand), otherwise it could take several seconds or minutes depending on the quantity + * and fidelity of the planes. + * + */ +const char * XPMPLoadCSLPackage( + const char * inCSLFolder, + const char * inRelatedPath, + const char * inDoc8643); + +/* + * XPMPLoadPlanesIfNecessary + * + * This routine checks what planes are loaded and loads any that we didn't get. + * Call it after you oare enabled if it isn't the first time to set up models. + * + */ +void XPMPLoadPlanesIfNecessary(void); + +/* + * XPMPCreatePlane + * + * This function creates a new plane for a plug-in and returns it. Pass in an ICAO aircraft ID code, + * a livery string and a data function for fetching dynamic information. + * + */ + XPMPPlaneID XPMPCreatePlane( + const char * inICAOCode, + const char * inAirline, + const char * inLivery, + XPMPPlaneData_f inDataFunc, + void * inRefcon); + +/* + * XPMPDestroyPlane + * + * This function deallocates a created aircraft. + * + */ + void XPMPDestroyPlane(XPMPPlaneID); + +/* + * XPMPChangePlaneModel + * + * This routine lets you change an aircraft's model. This can be useful if a remote + * player changes planes or new information comes over the network asynchronously. + * + */ + void XPMPChangePlaneModel( + XPMPPlaneID inPlaneID, + const char * inICAOCode, + const char * inAirline, + const char * inLivery); + +/* + * XPMPSetDefaultPlaneICAO + * + * This routine controls what ICAO is used as a backup search criteria for a not-found plane. + * + */ +void XPMPSetDefaultPlaneICAO( + const char * inICAO); + +/************************************************************************************ + * PLANE OBSERVATION API + ************************************************************************************/ + +/* + * XPMPPlaneNotification + * + * These are the various notifications you receive when you register a notifier + * function. + * + */ +enum { + xpmp_PlaneNotification_Created = 1, + xpmp_PlaneNotification_ModelChanged = 2, + xpmp_PlaneNotification_Destroyed = 3 +}; +typedef int XPMPPlaneNotification; + +/* + * XPMPPlaneNotifier_f + * + * You can pass a notifier to find out when a plane is created or destroyed or other + * data changes. + * + */ +typedef void (* XPMPPlaneNotifier_f)( + XPMPPlaneID inPlaneID, + XPMPPlaneNotification inNotification, + void * inRefcon); + +/* + * XPMPCountPlanes + * + * This function returns the number of planes in existence. + * + */ + long XPMPCountPlanes(void); + +/* + * XPMPGetNthPlane + * + * This function returns the plane ID of the Nth plane. + * + */ + XPMPPlaneID XPMPGetNthPlane( + long index); + + +/* + * XPMPGetPlaneICAOAndLivery + * + * Given a plane, this function returns optionally its ICAO code or livery. Pass string buffers + * or NULL if you do not want the information. + * + */ + void XPMPGetPlaneICAOAndLivery( + XPMPPlaneID inPlane, + char * outICAOCode, // Can be NULL + char * outLivery); // Can be NULL + +/* + * XPMPRegisterPlaneCreateDestroyFunc + * + * This function registers a notifier functionfor obeserving planes being created and destroyed. + * + */ + void XPMPRegisterPlaneNotifierFunc( + XPMPPlaneNotifier_f inFunc, + void * inRefcon); + +/* + * XPMPUnregisterPlaneCreateDestroyFunc + * + * This function canceles a registration for a notifier functionfor obeserving + * planes being created and destroyed. + */ + void XPMPUnregisterPlaneNotifierFunc( + XPMPPlaneNotifier_f inFunc, + void * inRefcon); + +/* + * XPMPGetPlaneData + * + * This function fetches specific data about a plane in the sim. Pass in a plane ID, a data type + * and a pointer to a struct for the data. The struct's size field must be filled in! The data + * will be returned if possible, as well as the sim cycle the data is from, or 0 if the data could not + * be fetched. + * + */ + int XPMPGetPlaneData( + XPMPPlaneID inPlane, + XPMPPlaneDataType inDataType, + void * outData); + +/* + * XPMPIsICAOValid + * + * This functions searches through our global vector of valid ICAO codes and returns true if there + * was a match and false if there wasn't. + * + */ + bool XPMPIsICAOValid( + const char * inICAO); + +/************************************************************************************ + * PLANE RENDERING API + ************************************************************************************/ + +/* + * XPMPRenderPlanes_f + * + * You can register a callback to draw planes yourself. If you do this, the XPMP will not + * draw multiplayer planes out the cockpit window; do it yourself! Use the data access API + * to get plane info and then draw them. You are responsible for all planes. + * + */ +typedef void (* XPMPRenderPlanes_f)( + int inIsBlend, + void * inRef); + +/* + * XPMPSetPlaneRenderer + * + * This function setse the plane renderer. You can pass NULL for the function to restore + * the default renderer. + * + */ + void XPMPSetPlaneRenderer( + XPMPRenderPlanes_f inRenderer, + void * inRef); + +/* + * XPMPDumpOneCycle + * + * This causes the plane renderer implementation to dump debug info to the error.out for one + * cycle after it is called - useful for figuring out why your models don't look right. + * + */ +void XPMPDumpOneCycle(void); + +/* + * XPMPEnableAircraftLabels + * XPMPDisableAircraftLabels + * + * These functions enable and disable the drawing of aircraft labels above the aircraft + * + */ +void XPMPEnableAircraftLabels(void); + +void XPMPDisableAircraftLabels(void); + +bool XPMPDrawingAircraftLabels(void); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/xbus/libxplanemp/include/XPMPPlaneRenderer.h b/src/xbus/libxplanemp/include/XPMPPlaneRenderer.h new file mode 100644 index 000000000..e4dcfc401 --- /dev/null +++ b/src/xbus/libxplanemp/include/XPMPPlaneRenderer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef XPLMPLANERENDERER_H +#define XPLMPLANERENDERER_H + +// Theoretically you can plug in your own plane-rendering +// function (although in practice this isn't real useful. +// These functions do "the drawing" once per frame. + +void XPMPInitDefaultPlaneRenderer(void); +void XPMPDefaultPlaneRenderer(int is_blend); + +#endif \ No newline at end of file diff --git a/src/xbus/libxplanemp/src/BitmapUtils.cpp b/src/xbus/libxplanemp/src/BitmapUtils.cpp new file mode 100644 index 000000000..cf3ddcce7 --- /dev/null +++ b/src/xbus/libxplanemp/src/BitmapUtils.cpp @@ -0,0 +1,983 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "BitmapUtils.h" +#include +#include +#include +#include +#include +#include "Interpolation.h" + +#if APL + #include + #if defined(__POWERPC__) +inline void BMP_EndianFlipInt(int * x) { int t = Endian32_Swap(*x); *x = t; } +inline void BMP_EndianFlipShort(short * x) { short t = Endian16_Swap(*x); *x = t; } +#else +#define BMP_EndianFlipInt(x) (x) +#define BMP_EndianFlipShort(x) (x) +#endif +#else + #define BMP_EndianFlipInt(x) (x) + #define BMP_EndianFlipShort(x) (x) +#endif + +#if BITMAP_USE_JPEG + +#if IBM +#define XMD_H +#define HAVE_BOOLEAN +#endif + +#include "jpeglib.h" +#include "jerror.h" + +#endif /* BITMAP_USE_JPEG */ + +/* BEN SAYS 10/11/06: this is a bit of a mess. The basic problem is that pnglib should really be distributed as a library. But + * currently we have pnglib 1.2.8 in the src dir (as source) and pnglib 1.2.5 as headers in libxplanemp and presumably as binaries + * in the "libs" dir. What we really need is a unified approach. + * + * In the long term, the real solution will be for the SDK to guarantee pnglib 1.2.12 as part of the SDK environment; this will save + * all plugin developers a lot of grief. Under this configuration, bitmaputils will simply do a #include and the makefile + * will list a copy of png.g that would be archived in the XP-SDK distro headers. + * + * Until then the best way to fix this would be to simply get fresh png 1.2.12 source and either make building it part of the + * make-files or build it and check in the binaries to some kind of dump. + * + * For now, explicitly find the older png headers. I am intentionally leaving them in the top of libxplanemp so we don't forget + * that they do not belong here! + * + */ +//#include "../png.h" +/* + * MAT SAYS 2014-06-23: on Windows, swift's fork of libxplanemp uses the libpng contained in swift's external dependencies package. + * On other operating systems, it uses the libpng which the developer has installed on their system. There are no libpng or zlib + * headers, source code, or binaries, anywhere in swift's git repository (which includes our fork of libxplanemp). + */ +#include + +int CreateBitmapFromFile(const char * inFilePath, struct ImageInfo * outImageInfo) +{ + struct BMPHeader header; + struct BMPImageDesc imageDesc; + int pad; + int err = 0; + FILE * fi = NULL; + + outImageInfo->data = NULL; + + fi = fopen(inFilePath, "rb"); + if (fi == NULL) + goto bail; + + /* First we read in the headers, endian flip them, sanity check them, and decide how big our + image is. */ + + if (fread(&header, sizeof(header), 1, fi) != 1) + goto bail; + if (fread(&imageDesc, sizeof(imageDesc), 1, fi) != 1) + goto bail; + + BMP_EndianFlipInt(&header.fileSize); + BMP_EndianFlipInt(&header.dataOffset); + + BMP_EndianFlipInt(&imageDesc.imageWidth); + BMP_EndianFlipInt(&imageDesc.imageHeight); + BMP_EndianFlipShort(&imageDesc.bitCount); + + if ((header.signature1 != 'B') || + (header.signature2 != 'M') || + (imageDesc.bitCount != 24) || + (imageDesc.imageWidth <= 0) || + (imageDesc.imageHeight <= 0)) + goto bail; + + if ((header.fileSize - header.dataOffset) < (imageDesc.imageWidth * imageDesc.imageHeight * 3)) + goto bail; + + pad = (imageDesc.imageWidth * 3 + 3) & ~3; + pad -= imageDesc.imageWidth * 3; + + outImageInfo->width = imageDesc.imageWidth; + outImageInfo->height = imageDesc.imageHeight; + outImageInfo->pad = pad; + + /* Now we can allocate an image buffer. */ + + outImageInfo->channels = 3; + outImageInfo->data = (unsigned char *) malloc(imageDesc.imageWidth * imageDesc.imageHeight * outImageInfo->channels + imageDesc.imageHeight * pad); + if (outImageInfo->data == NULL) + goto bail; + + /* We can pretty much just read the bytes in; we know that we're 24 bit so there is no + color table, and 24 bit BMP files cannot be compressed. */ + + if (fread(outImageInfo->data, imageDesc.imageWidth * imageDesc.imageHeight * outImageInfo->channels + imageDesc.imageHeight * pad, 1, fi) != 1) + goto bail; + + fclose(fi); + return 0; + +bail: + err = errno; + if (fi != NULL) + fclose(fi); + if (outImageInfo->data != NULL) + free(outImageInfo->data); + if (err == 0) + err = -1; + return err; +} + +int WriteBitmapToFile(const struct ImageInfo * inImage, const char * inFilePath) +{ + FILE * fi = NULL; + struct BMPHeader header; + struct BMPImageDesc imageDesc; + int err = 0; + + /* First set up the appropriate header structures to match our bitmap. */ + + header.signature1 = 'B'; + header.signature2 = 'M'; + header.fileSize = sizeof(struct BMPHeader) + sizeof(struct BMPImageDesc) + ((inImage->width * 3 + inImage->pad) * inImage->height); + header.reserved = 0; + header.dataOffset = sizeof(struct BMPHeader) + sizeof(struct BMPImageDesc); + BMP_EndianFlipInt(&header.fileSize); + BMP_EndianFlipInt(&header.reserved); + BMP_EndianFlipInt(&header.dataOffset); + + imageDesc.structSize = sizeof(imageDesc); + imageDesc.imageWidth = inImage->width; + imageDesc.imageHeight = inImage->height; + imageDesc.planes = 1; + imageDesc.bitCount = 24; + imageDesc.compressionType = 0; + imageDesc.imageSize = (inImage->width * inImage->height * 3); + imageDesc.xPixelsPerM = 0; + imageDesc.yPixelsPerM = 0; + imageDesc.colorsUsed = 0; + imageDesc.colorsImportant = 0; + + BMP_EndianFlipInt(&imageDesc.structSize); + BMP_EndianFlipInt(&imageDesc.imageWidth); + BMP_EndianFlipInt(&imageDesc.imageHeight); + BMP_EndianFlipShort(&imageDesc.planes); + BMP_EndianFlipShort(&imageDesc.bitCount); + BMP_EndianFlipInt(&imageDesc.compressionType); + BMP_EndianFlipInt(&imageDesc.imageSize); + BMP_EndianFlipInt(&imageDesc.xPixelsPerM); + BMP_EndianFlipInt(&imageDesc.yPixelsPerM); + BMP_EndianFlipInt(&imageDesc.colorsUsed); + BMP_EndianFlipInt(&imageDesc.colorsImportant); + + fi = fopen(inFilePath, "wb"); + if (fi == NULL) + goto bail; + + /* We can just write out two headers and the data and be done. */ + + if (fwrite(&header, sizeof(header), 1, fi) != 1) + goto bail; + if (fwrite(&imageDesc, sizeof(imageDesc), 1, fi) != 1) + goto bail; + if (fwrite(inImage->data, (inImage->width * 3 + inImage->pad) * inImage->height, 1, fi) != 1) + goto bail; + + fclose(fi); + return 0; + +bail: + err = errno; + if (fi != NULL) + fclose(fi); + if (err == 0) + err = -1; + return err; +} + +int CreateNewBitmap(int inWidth, int inHeight, int inChannels, struct ImageInfo * outImageInfo) +{ + outImageInfo->width = inWidth; + outImageInfo->height = inHeight; + /* This nasty voodoo calculates the padding necessary to make each scanline a multiple of four bytes. */ + outImageInfo->pad = ((inWidth * inChannels + 3) & ~3) - (inWidth * inChannels); + outImageInfo->channels = inChannels; + outImageInfo->data = (unsigned char *) malloc(inHeight * ((inWidth * inChannels) + outImageInfo->pad)); + if (outImageInfo->data == NULL) + return ENOMEM; + return 0; +} + +void FillBitmap(const struct ImageInfo * inImageInfo, char c) +{ + memset(inImageInfo->data, c, inImageInfo->width * inImageInfo->height * inImageInfo->channels); +} + +void DestroyBitmap(const struct ImageInfo * inImageInfo) +{ + free(inImageInfo->data); +} + + +void CopyBitmapSection( + const struct ImageInfo * inSrc, + const struct ImageInfo * inDst, + int inSrcLeft, + int inSrcTop, + int inSrcRight, + int inSrcBottom, + int inDstLeft, + int inDstTop, + int inDstRight, + int inDstBottom) +{ + /* This routine copies a subsection of one bitmap onto a subsection of another, using bicubic interpolation + for scaling. */ + + double srcLeft = inSrcLeft, srcRight = inSrcRight, srcTop = inSrcTop, srcBottom = inSrcBottom; + double dstLeft = inDstLeft, dstRight = inDstRight, dstTop = inDstTop, dstBottom = inDstBottom; + + /* Here's why we subtract one from all of these... + (Ignore bicubic interpolation for a moment please...) + Every destination pixel is comprised of two pixels horizontally and two vertically. We use these widths and heights to do the rescaling + of the image. The goal is to have the rightmost pixel in the destination actually correspond to the rightmost pixel of the source. In + otherwords, we want to get (inDstRight - 1) from (inSrcRight - 1). Now since we use [inDstLeft - inDstRight) as our set of pixels, this + is the last pixel we ask for. But we have to subtract one from the width to get the rescaling right, otherwise we map inDstRight to + inSrcRight, which for very large upscales make inDstRight - 1 derive from inSrcRight - (something less than one) which is a pixel partially + off the right side of the bitmap, which is bad. + Bicubic interpolation is not used when we're this close to the edge of the border, so it is not a factor. + */ + + double dstWidth = dstRight - dstLeft - 1.0; + double dstHeight = dstBottom - dstTop - 1.0; + double srcWidth = srcRight - srcLeft - 1.0; + double srcHeight = srcBottom - srcTop - 1.0; + + double dx, dy; + + int srcRowBytes = inSrc->width * inSrc->channels + inSrc->pad; + int srcRowBytes2 = srcRowBytes * 2; + int dstRowBytes = inDst->width * inSrc->channels + inDst->pad; + unsigned char * srcBaseAddr = inSrc->data; + unsigned char * dstBaseAddr = inDst->data; + + int channels; + + for (dy = dstTop; dy < dstBottom; dy += 1.0) + { + for (dx = dstLeft; dx < dstRight; dx += 1.0) + { + /* For each pixel in the destination, find a pixel in the source. Note that it may have a fractional part + if we are scaling. */ + + double sx = ((dx - dstLeft) / dstWidth * srcWidth) + srcLeft; + double sy = ((dy - dstTop) / dstHeight * srcHeight) + srcTop; + + unsigned char * dstPixel = dstBaseAddr + ((int) dx * inDst->channels) + ((int) dy * dstRowBytes); + unsigned char * srcPixel = srcBaseAddr + ((int) sx * inSrc->channels) + ((int) sy * srcRowBytes); + + /* If we would need pixels from off the edge of the image for bicubic interpolation, + just use bilinear. */ + + if ((sx < 1) || + (sy < 1) || + (sx >= (inSrc->width - 2)) || + (sy >= (inSrc->height - 2))) + { + channels = inSrc->channels; + while (channels--) + { + // While this interpolation works right, when it reads the last row, its mix ratio is 100% the top/left + // BUT it reads the bot/right anyway. This causes access violations on Windows. + // (Apparently it's a "real" operating system...) + + // so we specifically check this case. + + double mixH = sx - floor(sx); + double mixV = sy - floor(sy); + + unsigned char tl = *srcPixel; + unsigned char tr = (mixH> 0.0) ? *(srcPixel+inSrc->channels) : 0; + unsigned char bl = (mixV> 0.0) ? *(srcPixel+srcRowBytes) : 0; + unsigned char br = ((mixH> 0.0) && (mixV > 0.0)) ? + *(srcPixel+srcRowBytes + inSrc->channels) : 0; + + /* Take the pixel (rounded down to integer coords), the one to the right, below, and below to the right. + The fractional part of the pixel is our weighting for interpolation. */ + unsigned char pixel = (unsigned char) BilinearInterpolate2d( + tl, tr, bl, br, mixH, mixV); + *dstPixel = pixel; + ++srcPixel; + ++dstPixel; + } + + } else { + channels = inSrc->channels; + while (channels--) + { + /* Same as above, except we now take 16 pixels surrounding the location we want. */ + *dstPixel = (unsigned char) BicubicInterpolate2d( + *(srcPixel-inSrc->channels-srcRowBytes),*(srcPixel-srcRowBytes),*(srcPixel+inSrc->channels-srcRowBytes),*(srcPixel+inSrc->channels*2-srcRowBytes), + *(srcPixel-inSrc->channels),*srcPixel,*(srcPixel+inSrc->channels),*(srcPixel+inSrc->channels*2), + *(srcPixel-inSrc->channels+srcRowBytes),*(srcPixel+srcRowBytes),*(srcPixel+inSrc->channels+srcRowBytes),*(srcPixel+inSrc->channels*2+srcRowBytes), + *(srcPixel-inSrc->channels+srcRowBytes2),*(srcPixel+srcRowBytes2),*(srcPixel+inSrc->channels+srcRowBytes2),*(srcPixel+inSrc->channels*2+srcRowBytes2), + sx - floor(sx), sy - floor(sy)); + ++srcPixel; + ++dstPixel; + } + } + + } + } +} + +inline double Interp2(double frac, double sml, double big) +{ + return sml + frac * (big - sml); +} + +void CopyBitmapSectionWarped( + const struct ImageInfo * inSrc, + const struct ImageInfo * inDst, + int inTopLeftX, + int inTopLeftY, + int inTopRightX, + int inTopRightY, + int inBotRightX, + int inBotRightY, + int inBotLeftX, + int inBotLeftY, + int inDstLeft, + int inDstTop, + int inDstRight, + int inDstBottom) +{ + /* This routine copies a subsection of one bitmap onto a subsection of another, using bicubic interpolation + for scaling. */ + + double dstLeft = inDstLeft, dstRight = inDstRight, dstTop = inDstTop, dstBottom = inDstBottom; + double topLeftX = inTopLeftX, topLeftY = inTopLeftY, topRightX = inTopRightX, topRightY = inTopRightY; + double botLeftX = inBotLeftX, botLeftY = inBotLeftY, botRightX = inBotRightX, botRightY = inBotRightY; + + /* Here's why we subtract one from all of these... + (Ignore bicubic interpolation for a moment please...) + Every destination pixel is comprised of two pixels horizontally and two vertically. We use these widths and heights to do the rescaling + of the image. The goal is to have the rightmost pixel in the destination actually correspond to the rightmost pixel of the source. In + otherwords, we want to get (inDstRight - 1) from (inSrcRight - 1). Now since we use [inDstLeft - inDstRight) as our set of pixels, this + is the last pixel we ask for. But we have to subtract one from the width to get the rescaling right, otherwise we map inDstRight to + inSrcRight, which for very large upscales make inDstRight - 1 derive from inSrcRight - (something less than one) which is a pixel partially + off the right side of the bitmap, which is bad. + Bicubic interpolation is not used when we're this close to the edge of the border, so it is not a factor. + */ + + double dstWidth = dstRight - dstLeft - 1.0; + double dstHeight = dstBottom - dstTop - 1.0; + + double dx, dy; + + int srcRowBytes = inSrc->width * inSrc->channels + inSrc->pad; + int srcRowBytes2 = srcRowBytes * 2; + int dstRowBytes = inDst->width * inSrc->channels + inDst->pad; + unsigned char * srcBaseAddr = inSrc->data; + unsigned char * dstBaseAddr = inDst->data; + + int channels; + + for (dy = dstTop; dy < dstBottom; dy += 1.0) + { + for (dx = dstLeft; dx < dstRight; dx += 1.0) + { + /* For each pixel in the destination, find a pixel in the source. Note that it may have a fractional part + if we are scaling. */ + + double frac_x = ((dx - dstLeft) / dstWidth); + double frac_y = ((dy - dstTop) / dstHeight); + + double sx = Interp2(frac_y, Interp2(frac_x, topLeftX, topRightX), Interp2(frac_x, botLeftX, botRightX)); + double sy = Interp2(frac_x, Interp2(frac_y, topLeftY, botLeftY), Interp2(frac_y, topRightY, botRightY)); + + unsigned char * dstPixel = dstBaseAddr + ((int) dx * inDst->channels) + ((int) dy * dstRowBytes); + unsigned char * srcPixel = srcBaseAddr + ((int) sx * inSrc->channels) + ((int) sy * srcRowBytes); + + /* If we would need pixels from off the edge of the image for bicubic interpolation, + just use bilinear. */ + + if ((sx < 1) || + (sy < 1) || + (sx >= (inSrc->width - 2)) || + (sy >= (inSrc->height - 2))) + { + channels = inSrc->channels; + while (channels--) + { + // While this interpolation works right, when it reads the last row, its mix ratio is 100% the top/left + // BUT it reads the bot/right anyway. This causes access violations on Windows. + // (Apparently it's a "real" operating system...) + + // so we specifically check this case. + + double mixH = sx - floor(sx); + double mixV = sy - floor(sy); + + unsigned char tl = *srcPixel; + unsigned char tr = (mixH> 0.0) ? *(srcPixel+inSrc->channels) : 0; + unsigned char bl = (mixV> 0.0) ? *(srcPixel+srcRowBytes) : 0; + unsigned char br = ((mixH> 0.0) && (mixV > 0.0)) ? + *(srcPixel+srcRowBytes + inSrc->channels) : 0; + + /* Take the pixel (rounded down to integer coords), the one to the right, below, and below to the right. + The fractional part of the pixel is our weighting for interpolation. */ + unsigned char pixel = (unsigned char) BilinearInterpolate2d( + tl, tr, bl, br, mixH, mixV); + *dstPixel = pixel; + ++srcPixel; + ++dstPixel; + } + + } else { + channels = inSrc->channels; + while (channels--) + { + /* Same as above, except we now take 16 pixels surrounding the location we want. */ + *dstPixel = (unsigned char) BicubicInterpolate2d( + *(srcPixel-inSrc->channels-srcRowBytes),*(srcPixel-srcRowBytes),*(srcPixel+inSrc->channels-srcRowBytes),*(srcPixel+inSrc->channels*2-srcRowBytes), + *(srcPixel-inSrc->channels),*srcPixel,*(srcPixel+inSrc->channels),*(srcPixel+inSrc->channels*2), + *(srcPixel-inSrc->channels+srcRowBytes),*(srcPixel+srcRowBytes),*(srcPixel+inSrc->channels+srcRowBytes),*(srcPixel+inSrc->channels*2+srcRowBytes), + *(srcPixel-inSrc->channels+srcRowBytes2),*(srcPixel+srcRowBytes2),*(srcPixel+inSrc->channels+srcRowBytes2),*(srcPixel+inSrc->channels*2+srcRowBytes2), + sx - floor(sx), sy - floor(sy)); + ++srcPixel; + ++dstPixel; + } + } + + } + } +} + +void RotateBitmapCCW( + struct ImageInfo * ioBitmap) +{ + /* We have to allocate a new bitmap to transfer our old data to. The new bitmap might not have the same + * storage size as the old bitmap because of padding! */ + + int newWidth = ioBitmap->height; + int newHeight = ioBitmap->width; + int newPad = ((newWidth * ioBitmap->channels + 3) & ~3) - (newWidth * ioBitmap->channels); + unsigned char * newData = (unsigned char *) malloc(((newWidth * ioBitmap->channels) + newPad) * newHeight); + if (newData == NULL) + return; + + for (int y = 0; y < ioBitmap->height; ++y) + for (int x = 0; x < ioBitmap->width; ++x) + { + int nx = ioBitmap->height - y - 1; + int ny = x; + + unsigned char * srcP = ioBitmap->data + (x * ioBitmap->channels) + (y * (ioBitmap->channels * ioBitmap->width + ioBitmap->pad)); + unsigned char * dstP = newData + (nx * ioBitmap->channels) + (ny * (ioBitmap->channels * newWidth + newPad)); + int chCount = ioBitmap->channels; + while (chCount--) + { + *dstP++ = *srcP++; + } + } + + free(ioBitmap->data); + ioBitmap->data = newData; + ioBitmap->width = newWidth; + ioBitmap->height = newHeight; + ioBitmap->pad = newPad; + +} + +int ConvertBitmapToAlpha( + struct ImageInfo * ioImage) +{ + unsigned char * oldData, * newData, * srcPixel, * dstPixel; + //int count; + int x,y; + + if (ioImage->channels == 4) + return 0; + + /* We have to allocate a new bitmap that is larger than the old to store the alpha channel. */ + + newData = (unsigned char *) malloc(ioImage->width * ioImage->height * 4); + if (newData == NULL) + return ENOMEM; + oldData = ioImage->data; + + srcPixel = oldData; + dstPixel = newData; + //count = ioImage->width * ioImage->height; + for (y = 0; y < ioImage->height; ++y) + for (x = 0; x < ioImage->width; ++x) + { + /* For each pixel, if it is pure magenta, it becomes pure black transparent. Otherwise it is + * opaque and retains its color. NOTE: one of the problems with the magenta=alpha strategy is + * that we don't know what color was 'under' the transparency, so if we stretch or skew this bitmap + * we can't really do a good job of interpolating. */ + if ((srcPixel[0] == 0xFF) && + (srcPixel[1] == 0x00) && + (srcPixel[2] == 0xFF)) + { + dstPixel[0] = 0; + dstPixel[1] = 0; + dstPixel[2] = 0; + dstPixel[3] = 0; + } else { + dstPixel[0] = srcPixel[0]; + dstPixel[1] = srcPixel[1]; + dstPixel[2] = srcPixel[2]; + dstPixel[3] = 0xFF; + } + + srcPixel += 3; + dstPixel += 4; + + if (x == (ioImage->width - 1)) + srcPixel += ioImage->pad; + } + + ioImage->data = newData; + ioImage->pad = 0; + free(oldData); + ioImage->channels = 4; + return 0; +} + + +int ConvertAlphaToBitmap( + struct ImageInfo * ioImage) +{ + unsigned char * oldData, * newData, * srcPixel, * dstPixel; + //int count; + int x,y; + + if (ioImage->channels == 3) + return 0; + + /* Allocate a new bitmap at 3 channels. + * WARNING WARNING WARNING WARNING WARNING: + * THIS CODE IS BUGGY!!!!!!!!!!!!!!!!!!!!! + * + * There is no padding being allocated to our new buffer, but we do calculate + * a pad below. If we are working with a non-multiple-of-four width image, + * we will scribble over memory and cause chaos. + */ + newData = (unsigned char *) malloc(ioImage->width * ioImage->height * 3); + if (newData == NULL) + return ENOMEM; + oldData = ioImage->data; + + ioImage->pad = ((ioImage->width * 3 + 3) & ~3) - (ioImage->width * 3); + + srcPixel = oldData; + dstPixel = newData; + //count = ioImage->width * ioImage->height; + + for (y = 0; y < ioImage->height; ++y) + for (x = 0; x < ioImage->width; ++x) + { + /* For each pixel, only full opaque is taken. Here's why: if the pixel is part alpha, then it is a blend of an + * alpha pixel and a non-alpha pixel. But...we don't have good color data for the alpha pixel; from the above + * routine we set the color to black. So the color data for this pixel is mixed with black. When viewed the + * edges of a stretched bitmap will appear to turn dark before they fade out. + * + * But this point is moot anyway; we really only have one bit of alpha, on or off. So we could pick any cutoff + * value and we'll still get a really sharp jagged line at the edge of the transparency. + * + * You might ask yourself, why does X-Plane do it this way? The answer is that as of this writing, most graphics + * cards do not have the alpha-blending fill rate to blend the entire aircraft panel; this would be a huge hit on + * frame rate. So Austin is using the alpha test mechanism for transparency, which is much faster but only one + * bit deep. + * + */ + if (srcPixel[3] != 0xFF) + { + dstPixel[0] = 0xFF; + dstPixel[1] = 0x00; + dstPixel[2] = 0xFF; + } else { + dstPixel[0] = srcPixel[0]; + dstPixel[1] = srcPixel[1]; + dstPixel[2] = srcPixel[2]; + } + + srcPixel += 4; + dstPixel += 3; + + if (x == (ioImage->width - 1)) + dstPixel += ioImage->pad; + } + + ioImage->data = newData; + free(oldData); + ioImage->channels = 3; + return 0; +} + +//#pragma mark - + +#if BITMAP_USE_JPEG + +/* + * JPEG in-memory source manager + * + * Given a JFIF file that is entirley in memory, this object uses that buffer to provide data + * to the JPEG lib. + * + */ + +typedef struct { + struct jpeg_source_mgr pub; // Fields inherited from all jpeg source mgs. + + JOCTET * buffer; // Buffer start and size + int len; // +} mem_source_mgr; +typedef struct mem_source_mgr * mem_src_ptr; + +METHODDEF(void) mem_init_source (j_decompress_ptr cinfo) +{ +} + +METHODDEF(boolean) mem_fill_input_buffer (j_decompress_ptr cinfo) +{ + // If we are asked to fill the buffer, we can't; we give JPEG + // all of the memory up front. So throw a fatal err. + ERREXIT(cinfo, JERR_INPUT_EMPTY); + return TRUE; +} + +METHODDEF(void) mem_skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + mem_src_ptr src = (mem_src_ptr) cinfo->src; + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; +} + +METHODDEF(void) mem_term_source (j_decompress_ptr cinfo) +{ +} + +/* + * JPEG exception-based error handler. This throws an exception + * rather than calling exit() on the process. + * + */ +METHODDEF(void) throw_error_exit (j_common_ptr cinfo) +{ + // On a fatal error, we deallocate the struct first, + // then throw. This is a good idea because the cinfo + // struct may go out of scope during the throw; this + // relieves client code from having to worry about + // order of destruction. + jpeg_destroy(cinfo); + throw EXIT_FAILURE; +} + +METHODDEF(void) +eat_output_message (j_common_ptr cinfo) +{ + // If the user needed to see something, this is where + // we'd find out. We currently don't have a good way + // of showing the users messages. + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); +} + +GLOBAL(struct jpeg_error_mgr *) +jpeg_throw_error (struct jpeg_error_mgr * err) +{ + // This routine sets up our various error handlers. + // We use their decision making logic, and change + // two of our own handlers. + jpeg_std_error(err); + err->error_exit = throw_error_exit; + err->output_message = eat_output_message; + + return err; +} + + + + + + +int CreateBitmapFromJPEG(const char * inFilePath, struct ImageInfo * outImageInfo) +{ + // We bail immediately if the file is no good. This prevents us from + // having to keep track of file openings; if we have a problem, but the file must be + // closed. + outImageInfo->data = NULL; + FILE * fi = fopen(inFilePath, "rb"); + if (!fi) return errno; + + try { + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + cinfo.err = jpeg_throw_error(&jerr); + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo, fi); + + jpeg_read_header(&cinfo, TRUE); + + jpeg_start_decompress(&cinfo); + + outImageInfo->width = cinfo.output_width; + outImageInfo->height = cinfo.output_height; + outImageInfo->pad = 0; + outImageInfo->channels = 3; + outImageInfo->data = (unsigned char *) malloc(outImageInfo->width * outImageInfo->height * outImageInfo->channels); + + int linesize = outImageInfo->width * outImageInfo->channels; + int linecount = outImageInfo->height; + unsigned char * p = outImageInfo->data + (linecount - 1) * linesize; + while (linecount--) + { + if (jpeg_read_scanlines (&cinfo, &p, 1) == 0) + break; + + if (cinfo.output_components == 1) + for (int n = cinfo.output_width - 1; n >= 0; --n) + { + p[n*3+2] = p[n*3+1] = p[n*3] = p[n]; + } + p -= linesize; + } + + jpeg_finish_decompress(&cinfo); + + jpeg_destroy_decompress(&cinfo); + fclose(fi); + return 0; + } catch (...) { + // If we ever get an exception, it's because we got a fatal JPEG error. Our + // error handler deallocates the jpeg struct, so all we have to do is close the + // file and bail. + fclose(fi); + return 1; + } +} + + + +int CreateBitmapFromJPEGData(void * inBytes, int inLength, struct ImageInfo * outImageInfo) +{ + try { + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + cinfo.err = jpeg_throw_error(&jerr); + jpeg_create_decompress(&cinfo); + + mem_source_mgr src; + + cinfo.src = (struct jpeg_source_mgr *) &src; + + src.pub.init_source = mem_init_source; + src.pub.fill_input_buffer = mem_fill_input_buffer; + src.pub.skip_input_data = mem_skip_input_data; + src.pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src.pub.term_source = mem_term_source; + src.buffer = (JOCTET *) inBytes; + src.len = inLength; + src.pub.bytes_in_buffer = inLength; /* forces fill_input_buffer on first read */ + src.pub.next_input_byte = (JOCTET *) inBytes; /* until buffer loaded */ + + jpeg_read_header(&cinfo, TRUE); + + jpeg_start_decompress(&cinfo); + + outImageInfo->width = cinfo.output_width; + outImageInfo->height = cinfo.output_height; + outImageInfo->pad = 0; + outImageInfo->channels = 3; + outImageInfo->data = (unsigned char *) malloc(outImageInfo->width * outImageInfo->height * outImageInfo->channels); + + int linesize = outImageInfo->width * outImageInfo->channels; + int linecount = outImageInfo->height; + unsigned char * p = outImageInfo->data + (linecount - 1) * linesize; + while (linecount--) + { + if (jpeg_read_scanlines (&cinfo, &p, 1) == 0) + break; + + if (cinfo.output_components == 1) + for (int n = cinfo.output_width - 1; n >= 0; --n) + { + p[n*3+2] = p[n*3+1] = p[n*3] = p[n]; + } + p -= linesize; + } + + jpeg_finish_decompress(&cinfo); + + jpeg_destroy_decompress(&cinfo); + return 0; + } catch (...) { + // If we get an exceptoin, cinfo is already cleaned up; just bail. + return 1; + } +} + +#endif /* BITMAP_USE_JPEG */ + +void my_error (png_structp,png_const_charp /*err*/){} +void my_warning(png_structp,png_const_charp /*err*/){} + +unsigned char * png_start_pos = NULL; +unsigned char * png_end_pos = NULL; +unsigned char * png_current_pos = NULL; + +void png_buffered_read_func(png_structp png_ptr, png_bytep data, png_size_t length) +{ + if((png_current_pos+length)>png_end_pos) + png_error(png_ptr,"PNG Read Error, overran end of buffer!"); + memcpy(data,png_current_pos,length); + png_current_pos+=length; +} + + +int CreateBitmapFromPNG(const char * inFilePath, struct ImageInfo * outImageInfo) +{ + png_uint_32 width, height; + int bit_depth,color_type,interlace_type,compression_type,P_filter_type; + + png_structp pngPtr = NULL; + png_infop infoPtr = NULL; + unsigned char * buffer = NULL; + FILE * file = NULL; + size_t fileLength = 0; + outImageInfo->data = NULL; + char** rows = NULL; + double lcl_gamma; // This will be the gamma of the file if it has one. +#if APL // Macs and PCs have different gamma responses. + double screen_gamma=1.8; // Darks look darker and brights brighter on the PC. +#endif // Macs are more even. +#if IBM||LIN + double screen_gamma=2.2; +#endif + + pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING,(png_voidp)NULL,my_error,my_warning); + if(!pngPtr) goto bail; + + infoPtr=png_create_info_struct(pngPtr); + if(!infoPtr) goto bail; + + file = fopen(inFilePath, "rb"); + if (!file) goto bail; + fseek(file, 0, SEEK_END); + fileLength = ftell(file); + fseek(file, 0, SEEK_SET); + buffer = new unsigned char[fileLength]; + if (!buffer) goto bail; + if (fread(buffer, 1, fileLength, file) != fileLength) goto bail; + fclose(file); + file = NULL; + + png_start_pos = buffer; + png_current_pos = buffer; + png_end_pos = buffer + fileLength; + + if (png_sig_cmp(png_current_pos,0,8)) goto bail; + + png_set_interlace_handling(pngPtr); + if(setjmp(png_jmpbuf(pngPtr))) + { + goto bail; + } + + png_init_io (pngPtr,NULL ); + png_set_read_fn (pngPtr,NULL,png_buffered_read_func); + png_set_sig_bytes(pngPtr,8 ); png_current_pos+=8; + png_read_info (pngPtr,infoPtr ); + + png_get_IHDR(pngPtr,infoPtr,&width,&height, + &bit_depth,&color_type,&interlace_type, + &compression_type,&P_filter_type); + + outImageInfo->width = width; + outImageInfo->height = height; + + if( png_get_gAMA (pngPtr,infoPtr ,&lcl_gamma)) // Perhaps the file has its gamma recorded, for example by photoshop. Just tell png to callibrate for our hw platform. + png_set_gamma(pngPtr,screen_gamma, lcl_gamma); + else png_set_gamma(pngPtr,screen_gamma, 1.0/1.8 ); // If the file doesn't have gamma, assume it was drawn on a Mac. + + if(color_type==PNG_COLOR_TYPE_PALETTE && bit_depth<= 8)png_set_expand (pngPtr); + if(color_type==PNG_COLOR_TYPE_GRAY && bit_depth< 8)png_set_expand (pngPtr); + if(png_get_valid(pngPtr,infoPtr,PNG_INFO_tRNS) )png_set_expand (pngPtr); + if( bit_depth==16)png_set_strip_16 (pngPtr); + if( bit_depth< 8)png_set_packing (pngPtr); + if( color_type==PNG_COLOR_TYPE_GRAY )png_set_gray_to_rgb (pngPtr); + if( color_type==PNG_COLOR_TYPE_GRAY_ALPHA )png_set_gray_to_rgb (pngPtr); + switch(color_type) { + case PNG_COLOR_TYPE_GRAY: outImageInfo->channels = 3; break; + case PNG_COLOR_TYPE_GRAY_ALPHA: outImageInfo->channels = 4; break; + case PNG_COLOR_TYPE_RGB: outImageInfo->channels = 3; break; + case PNG_COLOR_TYPE_RGBA: outImageInfo->channels = 4; break; + default: goto bail; + } + png_set_bgr(pngPtr); + png_read_update_info(pngPtr,infoPtr); + + outImageInfo->pad = 0; + outImageInfo->data = (unsigned char *) malloc(outImageInfo->width * outImageInfo->height * outImageInfo->channels); + if (!outImageInfo->data) goto bail; + + rows=(char**)malloc(height*sizeof(char*)); + if (!rows) goto bail; + + for(png_uint_32 i=0;idata +((outImageInfo->height-1-i)*(outImageInfo->width)*(outImageInfo->channels)); + } + + png_read_image(pngPtr,(png_byte**)rows); // Now we just tell pnglib to read in the data. When done our row ptrs will be filled in. + free(rows); + rows = NULL; + + delete [] buffer; + buffer = NULL; + + png_destroy_read_struct(&pngPtr,(png_infopp)&infoPtr,(png_infopp)NULL); + + return 0; +bail: + + if (pngPtr && infoPtr) png_destroy_read_struct(&pngPtr,(png_infopp)&infoPtr,(png_infopp)NULL); + else if (pngPtr) png_destroy_read_struct(&pngPtr,(png_infopp)NULL,(png_infopp)NULL); + if (buffer) delete [] buffer; + if (file) fclose(file); + if (outImageInfo->data) free(outImageInfo->data); + if (rows) free(rows); + + return -1; + +} + diff --git a/src/xbus/libxplanemp/src/BitmapUtils.h b/src/xbus/libxplanemp/src/BitmapUtils.h new file mode 100644 index 000000000..81ddd7c30 --- /dev/null +++ b/src/xbus/libxplanemp/src/BitmapUtils.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef _BitmapUtils_h_ +#define _BitmapUtils_h_ + +/* + WARNING: Struct alignment must be "68K" (e.g. 2-byte alignment) for these + structures to be happy!!! +*/ + +/* + These headers match the Microsoft bitmap file and image headers more or less + and are read right out of the file. +*/ + +#if APL +#pragma pack(push, 2) +#endif +#if IBM +#pragma pack(push, 2) +#endif + +struct BMPHeader { + char signature1; + char signature2; + int fileSize; + int reserved; + int dataOffset; +}; + +struct BMPImageDesc { + int structSize; + int imageWidth; + int imageHeight; + short planes; + short bitCount; + int compressionType; + int imageSize; + int xPixelsPerM; //130B0000? B013 = 45075? + int yPixelsPerM; + int colorsUsed; + int colorsImportant; +}; + +#if APL +#pragma pack(pop) +#endif +#if IBM +#pragma pack(pop) +#endif + + +/* + This is our in memory way of storing an image. Data is a pointer + to an array of bytes large enough to hold the image. We always + use 24-bit RGB or 32-bit ARGB. The lower left corner of the BMP + file is in the first byte of data. + + Pad is how many bytes we skip at the end of each scanline. Each + scanline must start on a 4-byte boundary! +*/ + +struct ImageInfo { + unsigned char * data; + int width; + int height; + int pad; + int channels; +}; + +/* Given a file path and an uninitialized imageInfo structure, this routine fills + * in the imageInfo structure by loading the bitmap. */ +int CreateBitmapFromFile(const char * inFilePath, struct ImageInfo * outImageInfo); + +#if BITMAP_USE_JPEG +/* Same as above, but uses IJG JPEG code. */ +int CreateBitmapFromJPEG(const char * inFilePath, struct ImageInfo * outImageInfo); + +/* Same as above, but create the image from an in-memory image of a JFIF file, + * allows you to read the image yourself, or mem map it. */ +int CreateBitmapFromJPEGData(void * inBytes, int inLength, struct ImageInfo * outImageInfo); +#endif + +/* Yada yada yada, libPng. */ +int CreateBitmapFromPNG(const char * inFilePath, struct ImageInfo * outImageInfo); + +/* Given an imageInfo structure, this routine writes it to disk as a .bmp file. + * Note that only 3-channel bitmaps may be written as .bmp files!! */ +int WriteBitmapToFile(const struct ImageInfo * inImage, const char * inFilePath); + +/* This routine creates a new bitmap and fills in an uninitialized imageInfo structure. + * The contents of the bitmap are undetermined and must be 'cleared' by you. */ +int CreateNewBitmap(int inWidth, int inHeight, int inChannels, struct ImageInfo * outImageInfo); + +/* Given a bitmap, this routine fills the whole bitmap in with a gray level of c, where + * c = 0 means black and c = 255 means white. */ +void FillBitmap(const struct ImageInfo * inImageInfo, char c); + +/* This routine deallocates a bitmap that was created with CreateBitmapFromFile or + * CreateNewBitmap. */ +void DestroyBitmap(const struct ImageInfo * inImageInfo); + +/* Given two bitmaps, this routine copies a section from one bitmap to another. + * This routine will use bicubic and bilinear interpolation to copy the bitmap + * as cleanly as possible. However, if the bitmap contains alpha, the copy routine + * will create a jagged edge to keep from smearing the alpha channel. */ +void CopyBitmapSection( + const struct ImageInfo * inSrc, + const struct ImageInfo * inDst, + int inSrcLeft, + int inSrcTop, + int inSrcRight, + int inSrcBottom, + int inDstLeft, + int inDstTop, + int inDstRight, + int inDstBottom); + +void CopyBitmapSectionWarped( + const struct ImageInfo * inSrc, + const struct ImageInfo * inDst, + int inTopLeftX, + int inTopLeftY, + int inTopRightX, + int inTopRightY, + int inBotRightX, + int inBotRightY, + int inBotLeftX, + int inBotLeftY, + int inDstLeft, + int inDstTop, + int inDstRight, + int inDstBottom); + +/* This routine rotates a bitmap counterclockwise 90 degrees, exchanging its width + * and height. */ +void RotateBitmapCCW( + struct ImageInfo * ioBitmap); + +/* This routine converts a 3-channel bitmap to a 4-channel bitmap by converting + * magenta pixels to alpha. */ +int ConvertBitmapToAlpha( + struct ImageInfo * ioImage); + +/* This routine converts a 4-channel bitmap to a 3-channel bitmap by converting + * alpha back to magenta. */ +int ConvertAlphaToBitmap( + struct ImageInfo * ioImage); +#endif diff --git a/src/xbus/libxplanemp/src/CSLLoaderThread.cpp b/src/xbus/libxplanemp/src/CSLLoaderThread.cpp new file mode 100644 index 000000000..9f9cb54c1 --- /dev/null +++ b/src/xbus/libxplanemp/src/CSLLoaderThread.cpp @@ -0,0 +1,101 @@ +#include "CSLLoaderThread.h" +#include "ptypes.h" +#include "pasync.h" + +#include "XPMPMultiplayerCSL.h" +#include "XPMPMultiplayerObj.h" + +USING_PTYPES + +const int maxthreads = 1; + + + +class loadjob: public message +{ +public: + CSLPlane_t* toload; + loadjob(const int msg, CSLPlane_t* itoload) + : message(msg) { toload = itoload; } + ~loadjob() { delete toload; } +}; + + +void loadjobthread::execute() +{ + bool quit = false; + while (!quit) + { + // get the next message from the queue + message* msg = jq->getmessage(); + + try + { + switch (msg->id) + { + case MSG_LOADJOB_OBJ: + { + CSLPlane_t* toload = ((loadjob*)msg)->toload; + toload->obj_idx = OBJ_LoadModel(toload->file_path.c_str()); + } + break; + + case MSG_LOADJOB_TEX: + { + CSLPlane_t* toload = ((loadjob*)msg)->toload; + toload->texID = OBJ_LoadTexture(toload->tex_path.c_str(), false); + } + break; + + case MSG_LOADJOB_TEX_LIT: + { + CSLPlane_t* toload = ((loadjob*)msg)->toload; + toload->texLitID = OBJ_LoadTexture(toload->texLit_path.c_str(), false); + } + break; + + case MSG_QUIT: + // MSG_QUIT is not used in our example + quit = true; + break; + } + } + catch(...) + { + // the message object must be freed! + delete msg; + throw; + } + delete msg; + } +} + + + +void CSLLoaderType::startthreads() +{ + // create the thread pool + static tobjlist threads(true); + int i; + for(i = 0; i < maxthreads; i++) + { + static loadjobthread* j = new loadjobthread(i + 1, jq); + j->start(); + threads.add(j); + } +} + +void CSLLoaderType::load(CSLPlane_t* toload) +{ + jq->post(new loadjob(MSG_LOADJOB_OBJ, toload)); +} + +void CSLLoaderType::loadTex(CSLPlane_t* toload) +{ + jq->post(new loadjob(MSG_LOADJOB_TEX, toload)); +} + +void CSLLoaderType::loadTexLIT(CSLPlane_t* toload) +{ + jq->post(new loadjob(MSG_LOADJOB_TEX_LIT, toload)); +} diff --git a/src/xbus/libxplanemp/src/CSLLoaderThread.h b/src/xbus/libxplanemp/src/CSLLoaderThread.h new file mode 100644 index 000000000..5b52d1b27 --- /dev/null +++ b/src/xbus/libxplanemp/src/CSLLoaderThread.h @@ -0,0 +1,41 @@ +#include "ptypes.h" +#include "pasync.h" + +#include "XPMPMultiplayerCSL.h" + +const int MSG_LOADJOB_OBJ = 1; +const int MSG_LOADJOB_TEX = 2; +const int MSG_LOADJOB_TEX_LIT = 3; + +const int NOT_LOADED_TEX_LIT=-6; +const int NOT_LOADED_TEX=-5; +const int LOADING_TEX=-4; +const int NOT_LOADED_OBJ=-3; +const int LOADING_OBJ=-2; + +class loadjobthread: public pt::thread +{ +protected: + int id; + pt::jobqueue* jq; + virtual void execute(); +public: + loadjobthread(int iid, pt::jobqueue* ijq) + : thread(true), id(iid), jq(ijq) {} + ~loadjobthread() { } +}; + + + +class CSLLoaderType +{ +protected: + pt::jobqueue *jq; +public: + CSLLoaderType() { jq=new pt::jobqueue(); } + ~CSLLoaderType() {} + void startthreads(); + void load(CSLPlane_t* toload); + void loadTex(CSLPlane_t* toload); + void loadTexLIT(CSLPlane_t* toload); +}; diff --git a/src/xbus/libxplanemp/src/Interpolation.h b/src/xbus/libxplanemp/src/Interpolation.h new file mode 100644 index 000000000..d6e286ad8 --- /dev/null +++ b/src/xbus/libxplanemp/src/Interpolation.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef _Interpolation_h_ +#define _Interpolation_h_ + +/* Interpolation Utilities. These are defined inline in the hope of speed. */ + +double clamp(double v, double min, double max); + +double BilinearInterpolate1d( + double v0, + double v1, + double position); + +double BilinearInterpolate2d( + double v0, double v1, + double v2, double v3, + double hPosition, + double vPosition); + +double BicubicInterpolate1d( + double v0, + double v1, + double v2, + double v3, + double position); + +double BicubicInterpolate2d( + double v0, double v1, double v2, double v3, + double v4, double v5, double v6, double v7, + double v8, double v9, double v10, double v11, + double v12, double v13, double v14, double v15, + double hPosition, + double vPosition); + +#include "Interpolation.i" + +#endif diff --git a/src/xbus/libxplanemp/src/Interpolation.i b/src/xbus/libxplanemp/src/Interpolation.i new file mode 100644 index 000000000..12c8e6577 --- /dev/null +++ b/src/xbus/libxplanemp/src/Interpolation.i @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + + +/* Interpolation inlines... */ + +double clamp(double v, double min, double max) +{ + return (v < min) ? min : ((v > max) ? max : v); +} + +/* Bilinear interpolation is just a weighted average of the two sources. + v0 is the value at point 0, v1 is the value at point 1, and position is a + fractional position between the two. */ + +double BilinearInterpolate1d( + double v0, + double v1, + double position) +{ + return v0 * (1.0 - position) + v1 * position; +} + +/* 2-d interpolation is just a recursion on 1-d interpolation. Our points + are organized as + 0 1 + 2 3 + in two dimensions with hPosition and vPosition being fractions specifying + where in the square we want to interpolate. */ + +double BilinearInterpolate2d( + double v0, double v1, + double v2, double v3, + double hPosition, + double vPosition) +{ + return BilinearInterpolate1d( + BilinearInterpolate1d(v0, v1, hPosition), + BilinearInterpolate1d(v2, v3, hPosition), + vPosition); +} + +/* Bicubic Interpolation. We take four values at -1, 0, 1, and 2. + We use all of them to interpolate a fractional value between 0 and 1. + But we take advantage of the extra data to provide more accuracy in + our interpolation. */ + +double BicubicInterpolate1d( + double v0, + double v1, + double v2, + double v3, + double position) +{ + /* + Bicubic interpolation constructs a cubic polynomial through all + four points passed in: (-1.0, v0) (0.0, v1) (1.0, v2) (2.0, v3) + and then evaluates it for position. + + The polynomial can be represented as: + + Ax^3 + Bx^2 + Cx + D + + I precalculated the values for A, B, C, and D based on v0-v3. We + calculate these constants and then plug them into the equation. + */ + + double D = v1; + double B = (v0 + v2) / 2.0 - v1; + double A = (v3 - v2 - 3 * B) / 7.0; + double C = v2 - A - B - D; + + return clamp(A * position * position * position + + B * position * position + + C * position + D, 0, 255.0); + +} + +double BicubicInterpolate2d( + double v0, double v1, double v2, double v3, + double v4, double v5, double v6, double v7, + double v8, double v9, double v10, double v11, + double v12, double v13, double v14, double v15, + double hPosition, + double vPosition) +{ + return BicubicInterpolate1d( + BicubicInterpolate1d(v0, v1, v2, v3, hPosition), + BicubicInterpolate1d(v4, v5, v6, v7, hPosition), + BicubicInterpolate1d(v8, v9, v10, v11, hPosition), + BicubicInterpolate1d(v12, v13, v14, v15, hPosition), + vPosition); +} diff --git a/src/xbus/libxplanemp/src/PlatformUtils.h b/src/xbus/libxplanemp/src/PlatformUtils.h new file mode 100644 index 000000000..e94219cef --- /dev/null +++ b/src/xbus/libxplanemp/src/PlatformUtils.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef _PlatformUtils_h_ +#define _PlatformUtils_h_ + +#error who uses this? + +/* + * PlatformUtils + * + * This file declares all of the platform specific code for the converter. + * + * PlatformUtils.mac.c contains the mac implementation; this code must be rewritten + * for the PC. + * + */ + +/* The directory separator is a macro and should be cased by some kind of compiler + #define or something. */ + +#if IBM + #define DIR_CHAR '\\' + #define DIR_STR "\\" +#elif (APL && __MACH__) || LIN + #define DIR_CHAR '/' + #define DIR_STR "/" +#elif APL + #define DIR_CHAR ':' + #define DIR_STR ":" +#else + #error PLATFORM NOT DEFINED +#endif + +/* + * These routines convert between little endian and native endian. This means they + * swap byte order on the Mac and do nothing on the PC. These are good for reading + * PC File structures, but EndianUtils.h contains more powerful stuff. + * + */ + +void EndianFlipShort(short * ioShort); +void EndianFlipLong(long * ioLong); + +/* + * This routine returns a fully qualified path to the application. + * + */ +const char * GetApplicationPath(void); + +#if APL +/* + * Convert an HFS path to a POSIX Path. Returns 0 for success, -1 for failure. + * WARNING: this only works for EXISTING FILES!!!!!!!!!!!!!!!!! + * + */ +int HFS2PosixPath(const char *path, char *result, int resultLen); +int Posix2HFSPath(const char *path, char *result, int resultLen); +#endif + +/* + * Takes a path and replaces the dir chars with the 'native' ones. + * Note that this is intended for PARTIAL paths. + * + */ +void MakePartialPathNative(char * ioBegin, char * ioEnd); // Takes a char range + + +/* + * GetFilePathFromUser takes a prompting C-string and fills in the buffer with a path + * to a picked file. It returns 1 if a file was picked, 0 if the user canceled. + * + */ +enum { + getFile_Open, + getFile_Save, + getFile_PickFolder +}; +int GetFilePathFromUser( + int inType, + const char * inPrompt, + const char * inAction, + int inID, + char * outFileName); + +/* + * DoUserAlert puts up an alert dialog box with the message and an OK button. + * + */ +void DoUserAlert(const char * inMsg); + +/* + * ShowProgressMessage puts up a dialog box with a progress message. Calling it + * repeatedly changes the progress message. + * + */ +void ShowProgressMessage(const char * inMsg, float * progress); + +/* + * ConfirmMessage puts up a dialog box with a message and two buttons. The proceed + * button is the default one. Pass in the message and the text of the two buttons. + * Returns 1 if the user clicks the proceed button, 0 if the user cancels. + * + */ +int ConfirmMessage(const char * inMsg, const char * proceedBtn, const char * cancelBtn); + +#endif diff --git a/src/xbus/libxplanemp/src/PlatformUtils.lin.cpp b/src/xbus/libxplanemp/src/PlatformUtils.lin.cpp new file mode 100644 index 000000000..316a3cc82 --- /dev/null +++ b/src/xbus/libxplanemp/src/PlatformUtils.lin.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "PlatformUtils.h" +#include + + +void EndianFlipShort(short * ioShort) + +{ + // Not necessary on WINTEL machines. +} + +void EndianFlipLong(long * ioLong) + +{ + // Not necessary on WINTEL machines. +} + + +int GetFilePathFromUser( + int inType, + const char * inPrompt, + const char * inAction, + int inID, + char * outFileName) +{ +} + + +void DoUserAlert(const char * inMsg) +{ +} + + +void ShowProgressMessage(const char * inMsg, float * inProgress) +{ + +} + + +int ConfirmMessage(const char * inMsg, const char * proceedBtn, const char * cancelBtn) +{ +} + +void MakePartialPathNative(char * ioBegin, char * ioEnd) +{ + for (char * p = ioBegin; p != ioEnd; ++p) + { + if (*p == '/' || *p == ':' || *p == '\\') + *p = DIR_CHAR; + } +} + +// getting the application path on Linux systems is impossible. +// Thus, we return a link to a directory in the users home +// (this is better anyway - this way, the password won't be stored +// on a common system) +const char * GetApplicationPath(void) +{ + return "./dummy"; +} diff --git a/src/xbus/libxplanemp/src/PlatformUtils.mac.cpp b/src/xbus/libxplanemp/src/PlatformUtils.mac.cpp new file mode 100644 index 000000000..3da406687 --- /dev/null +++ b/src/xbus/libxplanemp/src/PlatformUtils.mac.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "PlatformUtils.h" + +//#define _STDINT_H_ +//#include +//#include +//#include +//#include +//#include + +#include + + + +static OSErr FSSpecToPathName(const FSSpec * inFileSpec, char * outPathname); + +/* Endian routines for the Mac use Apple's Endian macros. */ + +void EndianFlipShort(short * ioShort) +{ + *ioShort = Endian16_Swap(*ioShort); +} + +void EndianFlipLong(long * ioLong) +{ + *ioLong = Endian32_Swap(*ioLong); +} + +/* Get FilePathFromUser puts up a nav services dialog box and converts the results + to a C string path. */ + +pascal void event_proc(NavEventCallbackMessage callBackSelector, NavCBRecPtr callBackParms, void *callBackUD) +{ +} + +template +struct CFSmartPtr { + CFSmartPtr(T p) : p_(p) { } + ~CFSmartPtr() { if (p_) CFRelease(p_); } + operator T () { return p_; } + T p_; +}; + +int HFS2PosixPath(const char *path, char *result, int resultLen) +{ + bool is_dir = (path[strlen(path)-1] == ':'); + + CFSmartPtr inStr(CFStringCreateWithCString(kCFAllocatorDefault, path ,kCFStringEncodingMacRoman)); + if (inStr == NULL) return -1; + + CFSmartPtr url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inStr, kCFURLHFSPathStyle,0)); + if (url == NULL) return -1; + + CFSmartPtr outStr(CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)); + if (outStr == NULL) return -1; + + if (!CFStringGetCString(outStr, result, resultLen, kCFStringEncodingMacRoman)) + return -1; + + if(is_dir) strcat(result, "/"); + + return 0; +} + +int Posix2HFSPath(const char *path, char *result, int resultLen) +{ + CFSmartPtr inStr(CFStringCreateWithCString(kCFAllocatorDefault, path ,kCFStringEncodingMacRoman)); + if (inStr == NULL) return -1; + + CFSmartPtr url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inStr, kCFURLPOSIXPathStyle,0)); + if (url == NULL) return -1; + + CFSmartPtr outStr(CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle)); + if (outStr == NULL) return -1; + + if (!CFStringGetCString(outStr, result, resultLen, kCFStringEncodingMacRoman)) + return -1; + + return 0; +} + +void MakePartialPathNative(char * ioBegin, char * ioEnd) +{ + for (char * p = ioBegin; p != ioEnd; ++p) + { + if (*p == '/' || *p == ':' || *p == '\\') + *p = DIR_CHAR; + } +} + + + +const char * GetApplicationPath(void) +{ + static char pathBuf[1024]; + ProcessInfoRec pir; + FSSpec spec; + Str255 pStr; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + pir.processInfoLength = sizeof(pir); + pir.processAppSpec = &spec; + pir.processName = pStr; + GetProcessInformation(&psn, &pir); + OSErr err = FSSpecToPathName(&spec, pathBuf); + if (err != noErr) + return NULL; + return pathBuf; +} + +int GetFilePathFromUser( + int inType, + const char * inPrompt, + const char * inAction, + int inID, + char * outFileName) +{ + OSErr err; + NavReplyRecord reply; + NavDialogOptions options; + FSSpec fileSpec; + + reply.version = kNavReplyRecordVersion; + err = NavGetDefaultDialogOptions(&options); + if (err != noErr) + return 0; + + if (inType == getFile_Save) + { + options.savedFileName[0] = strlen(outFileName); + memcpy(options.savedFileName+1, outFileName, options.savedFileName[0]); + } + + options.message[0] = strlen(inPrompt); + memcpy(options.message+1,inPrompt, options.message[0]); + options.actionButtonLabel[0] = strlen(inAction); + memcpy(options.actionButtonLabel+1,inAction, options.actionButtonLabel[0]); + options.dialogOptionFlags &= ~kNavAllowMultipleFiles; + options.preferenceKey = inID; + NavEventUPP eventUPP = NewNavEventUPP(event_proc); + + switch(inType) { + case getFile_Open: + err = NavGetFile(NULL, &reply, &options, eventUPP, NULL, NULL, NULL, NULL); + if ((err != noErr) && (err != userCanceledErr)) + return 0; + break; + case getFile_Save: + err = NavPutFile(NULL, &reply, &options, eventUPP, 0, 0, NULL); + if ((err != noErr) && (err != userCanceledErr)) + return 0; + break; + case getFile_PickFolder: + err = NavChooseFolder(NULL, &reply, &options, eventUPP, NULL, NULL); + if ((err != noErr) && (err != userCanceledErr)) + return 0; + break; + } + DisposeNavEventUPP(eventUPP); + if (!reply.validRecord) + goto bail; + + /* Convert the result from an AEDesc to a Mac file spec. */ + err = AEGetNthPtr(&reply.selection, 1, typeFSS, NULL, NULL, &fileSpec, sizeof(fileSpec), NULL); + if (err != noErr) + goto bail; + + /* Then convert the FSSpec to a full path. */ + err = FSSpecToPathName(&fileSpec, outFileName); + if (err != noErr) + goto bail; + + NavDisposeReply(&reply); + return 1; + +bail: + NavDisposeReply(&reply); + return 0; + + +} + +void DoUserAlert(const char * inMsg) +{ + Str255 p1,p2; + size_t sl; + + sl = strlen(inMsg); + if (sl > 255) + sl = 255; + + p1[0] = sl; + memcpy(p1+1, inMsg, sl); + + p2[0]=0; // Ben says: GCC doesn't understand "\p". + + StandardAlert(kAlertStopAlert, p1, p2, NULL, NULL); +} + +void ShowProgressMessage(const char * inMsg, float * progress) +{ + static WindowRef wind = NULL; + Rect windBounds = { 0, 0, 250, 500 }; + if (wind == NULL) + { + if (CreateNewWindow(kMovableAlertWindowClass, kWindowStandardHandlerAttribute, &windBounds, &wind) != noErr) return; + if (wind == NULL) return; + RepositionWindow(wind, NULL,kWindowCenterOnMainScreen); + ShowWindow(wind); + } + + SetPortWindowPort(wind); + CFStringRef ref = CFStringCreateWithCString(NULL, inMsg, smSystemScript); + EraseRect(&windBounds); + InsetRect(&windBounds, 20, 15); + DrawThemeTextBox(ref, kThemeSystemFont, kThemeStateActive, true, &windBounds, teJustLeft, NULL); + CFRelease(ref); + + if (progress) + { + float p = *progress; + ThemeTrackDrawInfo info; + info.kind = (p >= 0.0) ? kThemeMediumProgressBar : kThemeMediumIndeterminateBar; + SetRect(&info.bounds, 20, 210, 480, 230); + info.min = 0; + info.max = (p >= 0.0) ? 1000.0 : 0.0; + info.value = (p >= 0.0) ? (p * 1000.0) : 0; + info.reserved = 0; + info.attributes = kThemeTrackHorizontal; + info.enableState = kThemeTrackActive; + info.filler1 = 0; + static UInt8 nPhase = 0; + info.trackInfo.progress.phase = nPhase; + nPhase++; + DrawThemeTrack(&info, NULL, NULL, 0); + } + QDFlushPortBuffer(GetWindowPort(wind), NULL); +} + +int ConfirmMessage(const char * inMsg, const char * proceedBtn, const char * cancelBtn) +{ + Str255 pStr, proStr, clcStr, p2; + AlertStdAlertParamRec params; + short itemHit; + + pStr[0] = strlen(inMsg); + memcpy(pStr+1,inMsg,pStr[0]); + proStr[0] = strlen(proceedBtn); + memcpy(proStr+1, proceedBtn, proStr[0]); + clcStr[0] = strlen(cancelBtn); + memcpy(clcStr+1, cancelBtn, clcStr[0]); + + params.movable = false; + params.helpButton = false; + params.filterProc = NULL; + params.defaultText = proStr; + params.cancelText = clcStr; + params.otherText = NULL; + params.defaultButton = 1; + params.cancelButton = 2; + params.position = kWindowDefaultPosition; + + p2[0]=0; + + StandardAlert(kAlertCautionAlert, pStr, p2, ¶ms, &itemHit); + + return (itemHit == 1); +} + +/* + * FSSpecToPathName + * + * This routine builds a full path from a file spec by recursing up the directory + * tree to the route, prepending each directory name. + * + */ + +OSErr FSSpecToPathName(const FSSpec * inFileSpec, char * outPathname) +{ + short vRefNum = inFileSpec->vRefNum; + long startDirID = inFileSpec->parID; + + CInfoPBRec paramRec; + Str255 dirName; /* This will contain the name of the directory we get info about. */ + OSErr err = noErr; + + paramRec.dirInfo.ioCompletion = nil; + paramRec.dirInfo.ioNamePtr = (StringPtr)(&dirName); + paramRec.dirInfo.ioDrParID = startDirID; + + /* Start by putting a directory separator and the file name on the path. */ + outPathname[0] = ':'; + memcpy(outPathname+1, inFileSpec->name+1, inFileSpec->name[0]); + outPathname[inFileSpec->name[0]+1] = 0; + + do { + paramRec.dirInfo.ioVRefNum = vRefNum; + paramRec.dirInfo.ioFDirIndex = -1; + paramRec.dirInfo.ioDrDirID = paramRec.dirInfo.ioDrParID; + + if (!(err = PBGetCatInfoSync(¶mRec))) + { + /* For each directory we get info about, prepend a : and the directory name. + But for the root directory, do NOT prepend the colon. */ + short newPart = dirName[0] + ((paramRec.dirInfo.ioDrDirID != fsRtDirID) ? 1 : 0); + memmove(outPathname+newPart, outPathname, strlen(outPathname)+1); + if (paramRec.dirInfo.ioDrDirID != fsRtDirID) + { + outPathname[0] = ':'; + memcpy(outPathname+1, dirName+1, dirName[0]); + } else + memcpy(outPathname, dirName+1, dirName[0]); + } + } while ((err == noErr) && (paramRec.dirInfo.ioDrDirID != fsRtDirID)); + return err; +} + diff --git a/src/xbus/libxplanemp/src/PlatformUtils.win.cpp b/src/xbus/libxplanemp/src/PlatformUtils.win.cpp new file mode 100644 index 000000000..6bafe1a05 --- /dev/null +++ b/src/xbus/libxplanemp/src/PlatformUtils.win.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include +#include +#include "PlatformUtils.h" +#include +#include + + +void EndianFlipShort(short * ioShort) + +{ + // Not necessary on WINTEL machines. +} + +void EndianFlipLong(long * ioLong) + +{ + // Not necessary on WINTEL machines. +} + +const char * GetApplicationPath(void) + +{ + static char pathBuf[1024]; + if (GetModuleFileName(NULL, pathBuf, sizeof(pathBuf))) + return pathBuf; + else + return NULL; +} + +int GetFilePathFromUser( + int inType, + const char * inPrompt, + const char * inAction, + int inID, + char * outFileName) +{ + BROWSEINFO bif = { 0 }; + OPENFILENAME ofn = { 0 }; + + BOOL result; + + switch(inType) { + case getFile_Open: + case getFile_Save: + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = "All Files\000*.*\000"; + ofn.nFilterIndex = 1; // Start with .acf files + ofn.lpstrFile = outFileName; + if (inType != getFile_Save) + outFileName[0] = 0; // No initialization for open. + ofn.nMaxFile = 512; // Guess string length? + ofn.lpstrFileTitle = NULL; // Don't want file name w/out path + ofn.lpstrTitle = inPrompt; + result = (inType == getFile_Open) ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn); + return (result) ? 1 : 0; + + case getFile_PickFolder: + bif.hwndOwner = NULL; + bif.pidlRoot = NULL; + bif.pszDisplayName = NULL; + bif.lpszTitle = inPrompt; + bif.ulFlags = 0; + bif.lpfn = NULL; + bif.lParam = NULL; + LPITEMIDLIST items = SHBrowseForFolder(&bif); + if (items == NULL) return 0; + result = 0; + if (SHGetPathFromIDList (items, outFileName)) + { + result = 1; + strcat(outFileName, "\\"); + } + IMalloc * imalloc = 0; + if ( SUCCEEDED( SHGetMalloc ( &imalloc )) ) + { + imalloc->Free ( items ); + imalloc->Release ( ); + } + return result ? 1 : 0; + } + + return 0; +} + + +void DoUserAlert(const char * inMsg) +{ + MessageBox(NULL, inMsg, "Alert", MB_OK + MB_ICONWARNING); +} + + +static TCHAR sWindowClass[] = "__XPlaneInstallerWindowClass"; + + +static char progBuf[2048] = { 0 }; + + +LRESULT CALLBACK PaintProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch(msg) { + case WM_PAINT: + { + RECT r; + GetClientRect(wnd, &r); + PAINTSTRUCT paint; + HDC dc = BeginPaint(wnd, &paint); + r.left += 20; + r.right -= 20; + r.top += 15; + r.bottom -= 40; + DrawText(dc, progBuf, strlen(progBuf), &r, DT_LEFT | DT_WORDBREAK); + EndPaint(wnd, &paint); + return 0; + } + break; + default: + return DefWindowProc(wnd, msg, wparam, lparam); + } +} + + + +void ShowProgressMessage(const char * inMsg, float * inProgress) +{ + strcpy(progBuf, inMsg); + + + static HWND wind = NULL; + static HWND prog = NULL; + if (wind == NULL) + { + + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = PaintProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = NULL; // LoadIcon(hInstance, (LPCTSTR)IDI_TESTDND); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = sWindowClass; + wcex.hIconSm = NULL; // LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); + + + RegisterClassEx(&wcex); + + + wind = CreateWindowEx( + WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, + sWindowClass, + "X-Plane Installer", + WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_CLIPCHILDREN , + 50, 100, 500, 250, + NULL, + NULL, + GetModuleHandle(NULL), + NULL); + + + InitCommonControls(); + RECT rcClient; + ShowWindow(wind, SW_SHOWNORMAL); + GetClientRect(wind, &rcClient); + int cyVScroll = GetSystemMetrics(SM_CYVSCROLL); + prog = CreateWindowEx(0, PROGRESS_CLASS, + (LPSTR) NULL, WS_CHILD | WS_VISIBLE, + rcClient.left + 20, rcClient.bottom - 20 - + cyVScroll, rcClient.right - 40, cyVScroll, + wind, NULL, GetModuleHandle(NULL), NULL); + SendMessage(prog, PBM_SETRANGE, 0, + MAKELPARAM(0, 1000)); + SendMessage(prog, PBM_SETPOS, (WPARAM) 0, 0); + } + + + RECT br; + br.left = 0; + br.right = 500; + br.bottom = 210; + br.top = 0; + InvalidateRect(wind, &br, TRUE); + + + if (inProgress != NULL) + { + float v = *inProgress; + ShowWindow(prog,SW_SHOWNORMAL); + if (v >= 0.0) + { + int n = v * 1000.0; +// SendMessage(prog, PBM_SETMARQUEE, (WPARAM) 0, 0); + SendMessage(prog, PBM_SETPOS, (WPARAM) n, 0); + } else { +// SendMessage(prog, PBM_SETMARQUEE, (WPARAM) 1, 0); + SendMessage(prog, PBM_SETPOS, (WPARAM) 0, 0); + } + } else + ShowWindow(prog,SW_HIDE); + + + MSG msg; + if (PeekMessage(&msg, wind, 0, 0, PM_REMOVE)) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + + +int ConfirmMessage(const char * inMsg, const char * proceedBtn, const char * cancelBtn) +{ + int result = MessageBox( + NULL, // No Parent HWND + inMsg, + "X-Plane 8", // Dialog caption +// MB_OKCANCEL + + MB_YESNO + +// MB_ICONWARNING + + MB_USERICON + + MB_DEFBUTTON1); + + + return (result == IDOK || result == IDYES) ? 1 : 0; +} + +void MakePartialPathNative(char * ioBegin, char * ioEnd) +{ + for (char * p = ioBegin; p != ioEnd; ++p) + { + if (*p == '/' || *p == ':' || *p == '\\') + *p = DIR_CHAR; + } +} + diff --git a/src/xbus/libxplanemp/src/TexUtils.cpp b/src/xbus/libxplanemp/src/TexUtils.cpp new file mode 100644 index 000000000..3e6460e4f --- /dev/null +++ b/src/xbus/libxplanemp/src/TexUtils.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "XPLMGraphics.h" +#include "TexUtils.h" +#include "BitmapUtils.h" +#include "XPLMUtilities.h" +#include +#include +#include +#include +#include +#if IBM +#include +#include +#include +#elif APL +#include +#include +#else +#include +#include +#endif + +using std::swap; + +static void HalfBitmap(ImageInfo& ioImage) +{ + int row_b = (ioImage.width * ioImage.channels) + ioImage.pad; + + unsigned char * srcp1 = ioImage.data; + unsigned char * srcp2 = ioImage.data + row_b; + unsigned char * dstp = ioImage.data; + + ioImage.height /= 2; + ioImage.width /= 2; + int yr = ioImage.height; + int t1, t2, t3, t4; + + if (ioImage.channels == 3) + { + while (yr--) + { + int xr = ioImage.width; + while (xr--) + { + t1 = *srcp1++; + t2 = *srcp1++; + t3 = *srcp1++; + + t1 += *srcp1++; + t2 += *srcp1++; + t3 += *srcp1++; + + t1 += *srcp2++; + t2 += *srcp2++; + t3 += *srcp2++; + + t1 += *srcp2++; + t2 += *srcp2++; + t3 += *srcp2++; + + t1 >>= 2; + t2 >>= 2; + t3 >>= 2; + + *dstp++ = t1; + *dstp++ = t2; + *dstp++ = t3; + } + + srcp1 += row_b; + srcp1 += ioImage.pad; + srcp2 += row_b; + srcp2 += ioImage.pad; + } + } else { + + while (yr--) + { + int xr = ioImage.width; + while (xr--) + { + t1 = *srcp1++; + t2 = *srcp1++; + t3 = *srcp1++; + t4 = *srcp1++; + + t1 += *srcp1++; + t2 += *srcp1++; + t3 += *srcp1++; + t4 += *srcp1++; + + t1 += *srcp2++; + t2 += *srcp2++; + t3 += *srcp2++; + t4 += *srcp2++; + + t1 += *srcp2++; + t2 += *srcp2++; + t3 += *srcp2++; + t4 += *srcp2++; + + t1 >>= 2; + t2 >>= 2; + t3 >>= 2; + t4 >>= 2; + + *dstp++ = t1; + *dstp++ = t2; + *dstp++ = t3; + *dstp++ = t4; + } + + srcp1 += row_b; + srcp1 += ioImage.pad; + srcp2 += row_b; + srcp2 += ioImage.pad; + } + } + ioImage.pad = 0; + +} + +bool LoadTextureFromFile(const char * inFileName, int inTexNum, bool magentaAlpha, bool inWrap, bool mipmap, int * outWidth, int * outHeight, int inDeres) +{ + bool ok = false; + struct ImageInfo im; + long count = 0; +#if 1 + unsigned char * p; +#endif + int result = CreateBitmapFromPNG(inFileName, &im); + if (result) result = CreateBitmapFromFile(inFileName, &im); + if (result == 0) + { + while (inDeres > 0) + { + HalfBitmap(im); + --inDeres; + } + + if (!magentaAlpha || ConvertBitmapToAlpha(&im) == 0) + { + if (im.pad == 0) + { + XPLMBindTexture2d(inTexNum, 0); + if (magentaAlpha) + { +#if 1 + p = im.data; + count = im.width * im.height; + while (count--) + { + std::swap(p[0], p[2]); +// swap(p[1], p[2]); + p += 4; + } +#endif + if (mipmap) + gluBuild2DMipmaps(GL_TEXTURE_2D, 4, im.width, im.height, GL_RGBA, GL_UNSIGNED_BYTE, im.data); + else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, im.width ,im.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, im.data); + if (outWidth) *outWidth = im.width; + if (outHeight) *outHeight = im.height; + } else { +#if 1 + p = im.data; + count = im.width * im.height; + while (count--) + { + std::swap(p[0], p[2]); + p += 3; + } +#endif + if (mipmap) + gluBuild2DMipmaps(GL_TEXTURE_2D, 3, im.width, im.height, GL_RGB, GL_UNSIGNED_BYTE, im.data); + else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, im.width ,im.height, 0, GL_RGB, GL_UNSIGNED_BYTE, im.data); + if (outWidth) *outWidth = im.width; + if (outHeight) *outHeight = im.height; + } + ok = true; + } + } + + DestroyBitmap(&im); + } + + if (ok) + { + // BAS note: for some reason on my WinXP system with GF-FX, if + // I do not set these explicitly to linear, I get no drawing at all. + // Who knows what default state the card is in. :-( + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmap ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR); + + static const char * ver_str = (const char *) glGetString(GL_VERSION); + static const char * ext_str = (const char *) glGetString(GL_EXTENSIONS); + + static bool tex_clamp_avail = + strstr(ext_str,"GL_SGI_texture_edge_clamp" ) || + strstr(ext_str,"GL_SGIS_texture_edge_clamp" ) || + strstr(ext_str,"GL_ARB_texture_edge_clamp" ) || + strstr(ext_str,"GL_EXT_texture_edge_clamp" ) || + strncmp(ver_str,"1.2", 3) || + strncmp(ver_str,"1.3", 3) || + strncmp(ver_str,"1.4", 3) || + strncmp(ver_str,"1.5", 3); + + + if(inWrap ){glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT ); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT );} + else if(tex_clamp_avail){glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);} + else {glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP ); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP );} + + } + int err = glGetError(); + if (err) + { + char buf[256]; + sprintf(buf, "Texture load got OGL err: %d\n", err); + XPLMDebugString(buf); + } + return ok; +} diff --git a/src/xbus/libxplanemp/src/TexUtils.h b/src/xbus/libxplanemp/src/TexUtils.h new file mode 100644 index 000000000..068b44623 --- /dev/null +++ b/src/xbus/libxplanemp/src/TexUtils.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef TEXUTILS_H +#define TEXUTILS_H + +bool LoadTextureFromFile(const char * inFileName, int inTexNum, bool magentaAlpha, bool inWrap, bool inMipmap, int * outWidth, int * outHeight, int inDeres); + + + +#endif diff --git a/src/xbus/libxplanemp/src/XOGLUtils.cpp b/src/xbus/libxplanemp/src/XOGLUtils.cpp new file mode 100644 index 000000000..99e6d0e43 --- /dev/null +++ b/src/xbus/libxplanemp/src/XOGLUtils.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + #include "XOGLUtils.h" + +// This had to be renamed because on Linux, namespaces appear to be shared between ourselves and XPlane +// so i would end up overwritting XPlanes function pointer! +#if APL +//PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL; +#endif +#if IBM +PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL; +PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL; +PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL; +PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL; +PFNGLMULTITEXCOORD2FVARBPROC glMultiTexCoord2fvARB = NULL; +#endif + +/************************************************** +Nasty Mac Specific Stuff to Load OGL DLL Extensions +***************************************************/ + +#if APL + +#include +#include + +CFBundleRef gBundleRefOpenGL = NULL; + + +// Utility routine to get a bundle from the system folder by file name....typically used to find OpenGL to get extension functions. +int load_bundle_by_filename (const char * in_filename, CFBundleRef * io_bundle_ref) +{ + OSStatus err = noErr; + FSRef framework_fs; + CFURLRef framework_url = NULL; + CFURLRef bundle_url = NULL; + CFStringRef bundle_name = NULL; + CFBundleRef bundle_ref = NULL; + + bundle_name = CFStringCreateWithCString(kCFAllocatorDefault, in_filename, kCFStringEncodingUTF8); + if (bundle_name == NULL) { + err = paramErr; + goto bail; } + + err = FSFindFolder(kSystemDomain, kFrameworksFolderType, false, &framework_fs); + if (noErr != err) { + err = dirNFErr; + goto bail; } + + // create URL to folder + framework_url = CFURLCreateFromFSRef (kCFAllocatorDefault, &framework_fs); + if(framework_url == NULL) { + err = ioErr; + goto bail; } + + bundle_url = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, bundle_name, kCFURLPOSIXPathStyle, false, framework_url); + if(bundle_url == NULL) { + err = fnfErr; + goto bail; } + + bundle_ref = CFBundleCreate (kCFAllocatorDefault, bundle_url); + if(bundle_ref == NULL) { + err = permErr; + goto bail; } + + if (!CFBundleLoadExecutable (bundle_ref)) { + err = bdNamErr; + goto bail; + } + + if (io_bundle_ref) { *io_bundle_ref = bundle_ref; bundle_ref = NULL; } +bail: + if(bundle_ref) CFRelease(bundle_ref); + if(bundle_name) CFRelease(bundle_name); + if(bundle_url) CFRelease(bundle_url); + if(framework_url) CFRelease(framework_url); + + return err; +} + + +OSStatus aglInitEntryPoints (void) +{ + return load_bundle_by_filename ("OpenGL.framework", &gBundleRefOpenGL); +} + +void * aglGetProcAddress (char * pszProc) +{ + static bool first_time = true; + if (first_time) + { + first_time = false; + if (aglInitEntryPoints() != noErr) + return NULL; + } + return CFBundleGetFunctionPointerForName (gBundleRefOpenGL, + CFStringCreateWithCStringNoCopy (NULL, + pszProc, CFStringGetSystemEncoding (), NULL)); +} + +#define wglGetProcAddress(x) aglGetProcAddress(x) + +#endif + +#if LIN + +#define wglGetProcAddress(x) glXGetProcAddressARB((GLubyte*) (x)) + +#endif + +/************************************************** + Utilities Initialization +***************************************************/ + +bool OGL_UtilsInit() +{ + static bool firstTime = true; + if(firstTime) + { + // Initialize all OGL Function Pointers +#if APL +// glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB" ); +#endif +#if IBM + glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB" ); + glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB" ); + glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB"); + glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC ) wglGetProcAddress("glMultiTexCoord2fARB" ); + glMultiTexCoord2fvARB = (PFNGLMULTITEXCOORD2FVARBPROC ) wglGetProcAddress("glMultiTexCoord2fvARB" ); +#endif + firstTime = false; + } + + // Make sure everything got initialized + if(glBindBufferARB && + glActiveTextureARB && + glClientActiveTextureARB && + glMultiTexCoord2fARB && + glMultiTexCoord2fvARB) + { + return true; + } + else + return false; + +} diff --git a/src/xbus/libxplanemp/src/XOGLUtils.h b/src/xbus/libxplanemp/src/XOGLUtils.h new file mode 100644 index 000000000..9d1f86b31 --- /dev/null +++ b/src/xbus/libxplanemp/src/XOGLUtils.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _XOGLUtils_h_ +#define _XOGLUtils_h_ + +#if IBM + #include + #include + #include + #include +#elif LIN + #define GLX_GLXEXT_PROTOTYPES + #define GL_GLEXT_PROTOTYPES + #include + #include + #include + #include +#elif APL + + #include + #include + #include + #endif + + #if LIN + #define GLX_GLXEXT_PROTOTYPES + #endif + +// Open GL Extension Defintion +#if APL + #define APIENTRY +#endif +#if APL || LIN + #define GL_ARRAY_BUFFER_ARB 0x8892 + #define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#endif + +typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC ) (GLenum, GLuint); +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC ) (GLenum); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC ) (GLenum, GLfloat, GLfloat); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum, const GLfloat *); + +#if APL +//extern PFNGLBINDBUFFERARBPROC glBindBufferARB; +#endif +#if IBM +extern PFNGLBINDBUFFERARBPROC glBindBufferARB; +extern PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB; +extern PFNGLMULTITEXCOORD2FVARBPROC glMultiTexCoord2fvARB; +extern PFNGLACTIVETEXTUREARBPROC glActiveTextureARB; +extern PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB; +#endif + +bool OGL_UtilsInit(); + +#endif diff --git a/src/xbus/libxplanemp/src/XObjDefs.cpp b/src/xbus/libxplanemp/src/XObjDefs.cpp new file mode 100644 index 000000000..f8af9d7df --- /dev/null +++ b/src/xbus/libxplanemp/src/XObjDefs.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "XObjDefs.h" +#include + +cmd_info gCmds[] = { + +{ obj_End, type_None, "end", 0 }, +{ obj_Light, type_PtLine, "light", 1 }, +{ obj_Line, type_PtLine, "line", 2 }, +{ obj_Tri, type_Poly, "tri", 3 }, +{ obj_Quad, type_Poly, "quad", 4 }, +{ obj_Quad_Cockpit, type_Poly, "quad_cockpit", 4 }, +{ obj_Quad_Hard, type_Poly, "quad_hard", 4 }, +{ obj_Smoke_Black, type_Attr, "smoke_black", 4 }, +{ obj_Smoke_White, type_Attr, "smoke_white", 4 }, +{ obj_Movie, type_Poly, "quad_movie", 4 }, +{ obj_Polygon, type_Poly, "polygon", 0 }, +{ obj_Quad_Strip, type_Poly, "quad_strip", 0 }, +{ obj_Tri_Strip, type_Poly, "tri_strip", 0 }, +{ obj_Tri_Fan, type_Poly, "tri_fan", 0 }, +{ attr_Shade_Flat, type_Attr, "ATTR_shade_flat", 0 }, +{ attr_Shade_Smooth, type_Attr, "ATTR_shade_smooth",0 }, +{ attr_Shade_Flat, type_Attr, "shade_flat", 0 }, +{ attr_Shade_Smooth, type_Attr, "shade_smooth", 0 }, +{ attr_Ambient_RGB, type_Attr, "ATTR_ambient_rgb", 3 }, +{ attr_Diffuse_RGB, type_Attr, "ATTR_difuse_rgb", 3 }, +{ attr_Emission_RGB, type_Attr, "ATTR_emission_rgb",3 }, +{ attr_Specular_RGB, type_Attr, "ATTR_specular_rgb",3 }, +{ attr_Shiny_Rat, type_Attr, "ATTR_shiny_rat", 1 }, +{ attr_No_Depth, type_Attr, "ATTR_no_depth", 0 }, +{ attr_Depth, type_Attr, "ATTR_depth", 0 }, +{ attr_LOD, type_Attr, "ATTR_LOD", 2 }, +{ attr_Reset, type_Attr, "ATTR_reset", 0 }, +{ attr_Cull, type_Attr, "ATTR_cull", 0 }, +{ attr_NoCull, type_Attr, "ATTR_no_cull", 0 }, +{ attr_Offset, type_Attr, "ATTR_poly_os", 1 }, +{ attr_Max, type_None, NULL, 0 } +}; + + +int FindObjCmd(const char * inToken) +{ + int n = 0; + while (gCmds[n].name) + { + if (!strcmp(inToken, gCmds[n].name)) + return n; + ++n; + } + + return attr_Max; +} + +int FindIndexForCmd(int inCmd) +{ + int n = 0; + while (gCmds[n].name) + { + if (gCmds[n].cmd_id == inCmd) + return n; + ++n; + } + return 0; +} diff --git a/src/xbus/libxplanemp/src/XObjDefs.h b/src/xbus/libxplanemp/src/XObjDefs.h new file mode 100644 index 000000000..a9d266436 --- /dev/null +++ b/src/xbus/libxplanemp/src/XObjDefs.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef XOBJDEFS_H +#define XOBJDEFS_H + +#include +#include +using namespace std; + +enum { + + type_None = 0, + type_PtLine, + type_Poly, + type_Attr + +}; + +enum { + obj_End = 0, + obj_Light, + obj_Line, + obj_Tri, + obj_Quad, + obj_Quad_Hard, + obj_Quad_Cockpit, + obj_Smoke_Black, + obj_Smoke_White, + obj_Movie, + obj_Polygon, + obj_Quad_Strip, + obj_Tri_Strip, + obj_Tri_Fan, + attr_Shade_Flat, + attr_Shade_Smooth, + attr_Ambient_RGB, + attr_Diffuse_RGB, + attr_Emission_RGB, + attr_Specular_RGB, + attr_Shiny_Rat, + attr_No_Depth, + attr_Depth, + attr_LOD, + attr_Reset, + attr_Cull, + attr_NoCull, + attr_Offset, + attr_Max +}; + +struct cmd_info { + int cmd_id; + int cmd_type; + const char * name; + int elem_count; +}; + +extern cmd_info gCmds[]; + +struct vec_tex { + float v[3]; + float st[2]; +}; + +struct vec_rgb { + float v[3]; + float rgb[3]; +}; + +struct XObjCmd { + + int cmdType; // Are we a line, poly or attribute? + int cmdID; // What command are we? + + vector attributes; + vector st; + vector rgb; + +}; + +struct XObj { + + string texture; + vector cmds; + +}; + +int FindObjCmd(const char * inToken); + +int FindIndexForCmd(int inCmd); + +#endif \ No newline at end of file diff --git a/src/xbus/libxplanemp/src/XObjReadWrite.cpp b/src/xbus/libxplanemp/src/XObjReadWrite.cpp new file mode 100644 index 000000000..b35ef6ddb --- /dev/null +++ b/src/xbus/libxplanemp/src/XObjReadWrite.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "XObjReadWrite.h" +#include "XObjDefs.h" +#include "XUtils.h" +#include +#include + +#if APL + #define CRLF "\r" +#elif WIN + #define CRLF "\r\n" +#else + #define CRLF "\n" +#endif + +/* + TODO: err checking diagnostics + TODO: fix scientific notation support. +*/ + +bool XObjRead(const char * inFile, XObj& outObj) +{ + vector tokens; + string ascii, vers, tag, line; + int cmd_id, count, obj2_op; + int version = 1; + vec_tex vst; + vec_rgb vrgb; + + float scanned_st_rgb[4][3]={0,0,0 , 0,0,0,// [corner][color or st] + 0,0,0 , 0,0,0}; + + outObj.cmds.clear(); + + /********************************************************************* + * READ HEADER + *********************************************************************/ + + StTextFileScanner f(inFile, true); + if (f.done()) return false; + + // First we should have some kind of token telling us whether we're Mac or PC + // line endings. But we don't care that much. + line = f.get(); + BreakString(line, tokens); + if (tokens.empty()) return false; + ascii = tokens[0]; + f.next(); + if (f.done()) return false; + + // Read the version string. We expect either '2' or '700'. + line = f.get(); + + BreakString(line, tokens); + if (tokens.empty()) return false; + vers = tokens[0]; + f.next(); + if (f.done()) return false; + + if (vers == "700") version = 7; + else if (vers == "2" ) version = 2; + else version = 1; + + // If we're OBJ7, another token 'OBJ' follows...this is because + // all XP7 files start with the previous two lines. + if (version == 7) + { + line = f.get(); + BreakString(line, tokens); + if (tokens.empty()) return false; + tag = tokens[0]; + f.next(); + if (f.done()) return false; + } + + // The last line of the header is the texture file name. + if (version != 1) + { + line = f.get(); + BreakString(line, tokens); + if (tokens.empty()) return false; + outObj.texture = tokens[0]; + f.next(); + if (f.done()) return false; + } + + /************************************************************ + * READ GEOMETRIC COMMANDS + ************************************************************/ + bool first_loop = true; + while (!f.done()) + { + XObjCmd cmd; + + // Special case, don't pull from the file for v-1...that + // version string is really an obj command. + if (version != 1 || !first_loop) + { + line = f.get(); + f.next(); + } + first_loop = false; + + BreakString(line, tokens); + if (tokens.empty()) continue; + + /************************************************************ + * OBJ2 SCANNING + ************************************************************/ + if (version != 7) + { + obj2_op = atoi(tokens[0].c_str()); + switch(obj2_op) { + case 1: + case 2: + // Points and lines. The header line contains the color x10. + // The type (pt or line) tell how much geometry follows. + cmd.cmdID = (obj2_op == 1) ? obj_Light : obj_Line; + cmd.cmdType = type_PtLine; + count = obj2_op; + if (tokens.size() < 4) return false; + scanned_st_rgb[0][0]=scanned_st_rgb[1][0]=static_cast(atof(tokens[1].c_str()))*0.1f; // r + scanned_st_rgb[0][1]=scanned_st_rgb[1][1]=static_cast(atof(tokens[2].c_str()))*0.1f; // g + scanned_st_rgb[0][2]=scanned_st_rgb[1][2]=static_cast(atof(tokens[3].c_str()))*0.1f; // b + + // Sets of x,y,z follows. + for (int t = 0; t < count; ++t) + { + line = f.get(); + f.next(); + BreakString(line, tokens); + if (tokens.size() < 3) return false; + vrgb.v[0] = static_cast(atof(tokens[0].c_str())); + vrgb.v[1] = static_cast(atof(tokens[1].c_str())); + vrgb.v[2] = static_cast(atof(tokens[2].c_str())); + vrgb.rgb[0] = scanned_st_rgb[t][0]; + vrgb.rgb[1] = scanned_st_rgb[t][1]; + vrgb.rgb[2] = scanned_st_rgb[t][2]; + cmd.rgb.push_back(vrgb); + } + outObj.cmds.push_back(cmd); + break; + + case 3: + case 4: + case 5: + // Finite-size polygons. The header line contains s1, s2, t1, t2. + cmd.cmdID = (obj2_op == 5) ? obj_Quad_Hard : obj_Quad; + if (obj2_op == 3) cmd.cmdID = obj_Tri; + cmd.cmdType = type_Poly; + count = obj2_op; + if (count == 5) count = 4; + if (tokens.size() < 5 && version == 2) return false; + // Make sure to 'spread' the 4 S/T coords to 8 points. This is + // because + if (version == 2) + { + scanned_st_rgb[2][0]=scanned_st_rgb[3][0]=static_cast(atof(tokens[1].c_str())); // s1 + scanned_st_rgb[0][0]=scanned_st_rgb[1][0]=static_cast(atof(tokens[2].c_str())); // s2 + scanned_st_rgb[1][1]=scanned_st_rgb[2][1]=static_cast(atof(tokens[3].c_str())); // t1 + scanned_st_rgb[0][1]=scanned_st_rgb[3][1]=static_cast(atof(tokens[4].c_str())); // t2 + } else { + scanned_st_rgb[2][0]=scanned_st_rgb[3][0]=0.0; + scanned_st_rgb[0][0]=scanned_st_rgb[1][0]=0.0; + scanned_st_rgb[1][1]=scanned_st_rgb[2][1]=0.0; + scanned_st_rgb[0][1]=scanned_st_rgb[3][1]=0.0; + } + // Read sets of 3 points. + for (int t = 0; t < count; ++t) + { + line = f.get(); + f.next(); + BreakString(line, tokens); + if (tokens.size() < 3) return false; + + vst.v[0] = static_cast(atof(tokens[0].c_str())); + vst.v[1] = static_cast(atof(tokens[1].c_str())); + vst.v[2] = static_cast(atof(tokens[2].c_str())); + vst.st[0] = scanned_st_rgb[t][0]; + vst.st[1] = scanned_st_rgb[t][1]; + cmd.st.push_back(vst); + } + outObj.cmds.push_back(cmd); + break; + + case 99: + // 99 is the end token for obj2 files. + return true; + default: + // Negative numbers equal positive + // quad strips. The count is the number + // of vertex pairs, since they are always even. + if (obj2_op >= 0) + return false; + count = -obj2_op; + cmd.cmdID = obj_Quad_Strip; + cmd.cmdType = type_Poly; + + // Read a pair of x,y,z,s,t coords. + while (count--) + { + line = f.get(); + f.next(); + BreakString(line, tokens); + if (tokens.size() < 10) return false; + vst.v[0] = static_cast(atof(tokens[0].c_str())); + vst.v[1] = static_cast(atof(tokens[1].c_str())); + vst.v[2] = static_cast(atof(tokens[2].c_str())); + vst.st[0] = static_cast(atof(tokens[6].c_str())); + vst.st[1] = static_cast(atof(tokens[8].c_str())); + cmd.st.push_back(vst); + vst.v[0] = static_cast(atof(tokens[3].c_str())); + vst.v[1] = static_cast(atof(tokens[4].c_str())); + vst.v[2] = static_cast(atof(tokens[5].c_str())); + vst.st[0] = static_cast(atof(tokens[7].c_str())); + vst.st[1] = static_cast(atof(tokens[9].c_str())); + cmd.st.push_back(vst); + } + outObj.cmds.push_back(cmd); + break; + } + + } else { + + /************************************************************ + * OBJ7 SCANNING + ************************************************************/ + + cmd_id = FindObjCmd(tokens[0].c_str()); + + cmd.cmdType = gCmds[cmd_id].cmd_type; + cmd.cmdID = gCmds[cmd_id].cmd_id; + count = gCmds[cmd_id].elem_count; + + switch(gCmds[cmd_id].cmd_type) { + case type_None: + if (cmd_id == obj_End) + return true; + else + return false; + case type_PtLine: + + if ((count == 0) && (tokens.size() > 1)) + count = atoi(tokens[1].c_str()); + while (count-- && !f.done()) + { + line = f.get(); + f.next(); + BreakString(line, tokens); + if (tokens.size() > 5) + { + vrgb.v[0] = static_cast(atof(tokens[0].c_str())); + vrgb.v[1] = static_cast(atof(tokens[1].c_str())); + vrgb.v[2] = static_cast(atof(tokens[2].c_str())); + vrgb.rgb[0] = static_cast(atof(tokens[3].c_str())); + vrgb.rgb[1] = static_cast(atof(tokens[4].c_str())); + vrgb.rgb[2] = static_cast(atof(tokens[5].c_str())); + + cmd.rgb.push_back(vrgb); + } else + return false; + } + outObj.cmds.push_back(cmd); + break; + + case type_Poly: + + if ((count == 0) && (tokens.size() > 1)) + count = atoi(tokens[1].c_str()); + while (count-- && !f.done()) + { + line = f.get(); + f.next(); + BreakString(line, tokens); + if (tokens.size() > 4) + { + vst.v[0] = static_cast(atof(tokens[0].c_str())); + vst.v[1] = static_cast(atof(tokens[1].c_str())); + vst.v[2] = static_cast(atof(tokens[2].c_str())); + vst.st[0] = static_cast(atof(tokens[3].c_str())); + vst.st[1] = static_cast(atof(tokens[4].c_str())); + + cmd.st.push_back(vst); + + if (tokens.size() > 9) + { + --count; + vst.v[0] = static_cast(atof(tokens[5].c_str())); + vst.v[1] = static_cast(atof(tokens[6].c_str())); + vst.v[2] = static_cast(atof(tokens[7].c_str())); + vst.st[0] = static_cast(atof(tokens[8].c_str())); + vst.st[1] = static_cast(atof(tokens[9].c_str())); + + cmd.st.push_back(vst); + } + + } else + return false; + } + outObj.cmds.push_back(cmd); + break; + case type_Attr: + + if (tokens.size() > static_cast(count)) + { + for (int n = 0; n < count; ++n) + cmd.attributes.push_back(static_cast(atof(tokens[n+1].c_str()))); + } else + return false; + + outObj.cmds.push_back(cmd); + break; + } + + } // Obj 7 case + + } // While loop + return true; +} + +#if !defined(APL) +#define APL 0 +#endif + +bool XObjWrite(const char * inFile, const XObj& inObj) +{ + FILE * fi = fopen(inFile, "w"); + if (!fi) return false; + + fprintf(fi,"%c" CRLF, APL ? 'A' : 'I'); + fprintf(fi,"700" CRLF); + fprintf(fi,"OBJ" CRLF CRLF); + fprintf(fi,"%s\t\t//" CRLF CRLF, inObj.texture.c_str()); + + for (vector::const_iterator iter = inObj.cmds.begin(); iter != inObj.cmds.end(); ++iter) + { + int index = FindIndexForCmd(iter->cmdID); + switch(iter->cmdType) { + case type_PtLine: + + if (gCmds[index].elem_count == 0) + fprintf(fi,"%s %zd\t\t//" CRLF, gCmds[index].name, iter->rgb.size()); + else + fprintf(fi,"%s\t\t//" CRLF, gCmds[index].name); + + for (vector::const_iterator riter = iter->rgb.begin(); + riter != iter->rgb.end(); ++riter) + { + fprintf(fi,"%f %f %f %f %f %f" CRLF, + riter->v[0], riter->v[1], riter->v[2], + riter->rgb[0], riter->rgb[1], riter->rgb[2]); + } + fprintf(fi,CRLF); + break; + + + case type_Poly: + + if (gCmds[index].elem_count == 0) + fprintf(fi,"%s %zd\t\t//" CRLF, gCmds[index].name, iter->st.size()); + else + fprintf(fi,"%s\t\t//" CRLF, gCmds[index].name); + + for (vector::const_iterator siter = iter->st.begin(); + siter != iter->st.end(); ++siter) + { + fprintf(fi,"%f %f %f %f %f" CRLF, + siter->v[0], siter->v[1], siter->v[2], + siter->st[0], siter->st[1]); + } + fprintf(fi,CRLF); + break; + + + case type_Attr: + fprintf(fi,"%s",gCmds[index].name); + for (vector::const_iterator aiter = iter->attributes.begin(); + aiter != iter->attributes.end(); ++aiter) + { + fprintf(fi," %f", *aiter); + } + fprintf(fi, "\t\t//" CRLF CRLF); + break; + } + } + + fprintf(fi,"end\t\t//" CRLF); + + fclose(fi); + return true; +} diff --git a/src/xbus/libxplanemp/src/XObjReadWrite.h b/src/xbus/libxplanemp/src/XObjReadWrite.h new file mode 100644 index 000000000..ba32782a5 --- /dev/null +++ b/src/xbus/libxplanemp/src/XObjReadWrite.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef XOBJREADWRITE_H +#define XOBJREADWRITE_H + +struct XObj; + +bool XObjRead(const char * inFile, XObj& outObj); +bool XObjWrite(const char * inFile, const XObj& inObj); + +#endif \ No newline at end of file diff --git a/src/xbus/libxplanemp/src/XPCAircraft.cpp b/src/xbus/libxplanemp/src/XPCAircraft.cpp new file mode 100644 index 000000000..104a56664 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPCAircraft.cpp @@ -0,0 +1,34 @@ +#include "XPCAircraft.h" + +XPCAircraft::XPCAircraft( + const char * inICAOCode, + const char * inAirline, + const char * inLivery) +{ + mPlane = XPMPCreatePlane(inICAOCode, inAirline, inLivery, AircraftCB, + reinterpret_cast(this)); +} + +XPCAircraft::~XPCAircraft() +{ + XPMPDestroyPlane(mPlane); +} + +XPMPPlaneCallbackResult XPCAircraft::AircraftCB( + XPMPPlaneID /*inPlane*/, + XPMPPlaneDataType inDataType, + void * ioData, + void * inRefcon) +{ + XPCAircraft * me = reinterpret_cast(inRefcon); + switch(inDataType) { + case xpmpDataType_Position: + return me->GetPlanePosition((XPMPPlanePosition_t *) ioData); + case xpmpDataType_Surfaces: + return me->GetPlaneSurfaces((XPMPPlaneSurfaces_t *) ioData); + case xpmpDataType_Radar: + return me->GetPlaneRadar((XPMPPlaneRadar_t *) ioData); + default: + return xpmpData_Unavailable; + } +} diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayer.cpp b/src/xbus/libxplanemp/src/XPMPMultiplayer.cpp new file mode 100644 index 000000000..6601e915e --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayer.cpp @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2004, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XPMPMultiplayer.h" +#include "XPMPMultiplayerVars.h" +#include "XPMPPlaneRenderer.h" +#include "XPMPMultiplayerCSL.h" + +#include +#include +#include +#include + +#include "XPLMProcessing.h" +#include "XPLMPlanes.h" +#include "XPLMDataAccess.h" +#include "XPLMDisplay.h" +#include "XPLMPlugin.h" +#include "XPLMUtilities.h" + +#include "XOGLUtils.h" +//#include "PlatformUtils.h" + +#include +#include +#include + +// This prints debug info on our process of loading Austin's planes. +#define DEBUG_MANUAL_LOADING 0 + + +/****************************************************************************** + + T H E T C A S H A C K + + The 1.0 SDK provides no way to add TCAS blips to a panel - we simply don't know + where on the panel to draw. The only way to get said blips is to manipulate + Austin's "9 planes" plane objects, which he refers to when drawing the moving + map. + + But how do we integrate this with our system, which relies on us doing + the drawing (either by calling Austin's low level drawing routine or just + doing it ourselves with OpenGL)? + + The answer is the TCAS hack. Basically we set Austin's number of multiplayer + planes to zero while 3-d drawing is happening so he doesn't draw. Then during + 2-d drawing we pop this number back up to the number of planes that are + visible on TCAS and set the datarefs to move them, so that they appear on TCAS. + + One note: since all TCAS blips are the same, we do no model matching for + TCAS - we just place the first 9 planes at the right place. + + Our rendering loop records for us in gEnableCount how many TCAS planes should + be visible. + + ******************************************************************************/ + + + + + +static XPMPPlanePtr XPMPPlaneIsValid( + XPMPPlaneID inID, + XPMPPlaneVector::iterator * outIter); + +// This drawing hook is called once per frame to do the real drawing. +static int XPMPRenderMultiplayerPlanes( + XPLMDrawingPhase inPhase, + int inIsBefore, + void * inRefcon); + +// This drawing hook is called twice per frame to control how many planes +// should be visible. +static int XPMPControlPlaneCount( + XPLMDrawingPhase inPhase, + int inIsBefore, + void * inRefcon); + + + + +/******************************************************************************** + * SETUP + ********************************************************************************/ + + +const char * XPMPMultiplayerInitLegacyData( + const char * inCSLFolder, const char * inRelatedPath, + const char * inTexturePath, const char * inDoc8643, + const char * inDefaultPlane, + int (* inIntPrefsFunc)(const char *, const char *, int), + float (* inFloatPrefsFunc)(const char *, const char *, float)) +{ + gDefaultPlane = inDefaultPlane; + gIntPrefsFunc = inIntPrefsFunc; + gFloatPrefsFunc = inFloatPrefsFunc; + + bool problem = false; + + if (!CSL_LoadCSL(inCSLFolder, inRelatedPath, inDoc8643)) + problem = true; + + if (!CSL_Init(inTexturePath)) + problem = true; + + if (problem) return "There were problems initializing XSquawkBox. Please examine X-Plane's error.out file for detailed information."; + else return ""; +} + +const char * XPMPMultiplayerInit( + int (* inIntPrefsFunc)(const char *, const char *, int), + float (* inFloatPrefsFunc)(const char *, const char *, float)) +{ + gIntPrefsFunc = inIntPrefsFunc; + gFloatPrefsFunc = inFloatPrefsFunc; + //char myPath[1024]; + //char airPath[1024]; + //char line[256]; + //char sysPath[1024]; + //FILE * fi; + + bool problem = false; + +// TODO - FORM GOOD DIAGNOSTIC MESSAGES HERE! + + // Initialize our OpenGL Utilities + OGL_UtilsInit(); + + XPMPInitDefaultPlaneRenderer(); + + // Register the plane control calls. + XPLMRegisterDrawCallback(XPMPControlPlaneCount, + xplm_Phase_Gauges, 0, /* after*/ 0 /* hide planes*/); + + XPLMRegisterDrawCallback(XPMPControlPlaneCount, + xplm_Phase_Gauges, 1, /* before */ (void *) -1 /* show planes*/); + + // Register the actual drawing func. + XPLMRegisterDrawCallback(XPMPRenderMultiplayerPlanes, + xplm_Phase_Airplanes, 0, /* after*/ 0 /* refcon */); + + if (problem) return "There were problems initializing XSquawkBox. Please examine X-Plane's error.out file for detailed information."; + else return ""; +} + +void XPMPMultiplayerCleanup(void) +{ + XPLMUnregisterDrawCallback(XPMPControlPlaneCount, xplm_Phase_Gauges, 0, 0); + XPLMUnregisterDrawCallback(XPMPControlPlaneCount, xplm_Phase_Gauges, 1, (void *) -1); + XPLMUnregisterDrawCallback(XPMPRenderMultiplayerPlanes, xplm_Phase_Airplanes, 0, 0); +} + + +// We use this array to track Austin's planes, since we have to mess with them. +static vector gPlanePaths; + +const char * XPMPMultiplayerEnable(void) +{ + // First build up a list of all of Austin's planes, and assign + // their effective index numbers. + gPlanePaths.clear(); + std::vector ptrs; + gPlanePaths.push_back(""); + + for (size_t p = 0; p < gPackages.size(); ++p) + { + for (size_t pp = 0; pp < gPackages[p].planes.size(); ++pp) + { + if (gPackages[p].planes[pp].plane_type == plane_Austin) + { + gPackages[p].planes[pp].austin_idx = static_cast(gPlanePaths.size()); + char buf[1024]; + strcpy(buf,gPackages[p].planes[pp].file_path.c_str()); + #if APL + Posix2HFSPath(buf,buf,1024); + #endif + gPlanePaths.push_back(buf); + } + } + } + + // Copy the list into something that's not permanent, but is needed by the XPLM. + for (size_t n = 0; n < gPlanePaths.size(); ++n) + { +#if DEBUG_MANUAL_LOADING + char strbuf[1024]; + sprintf(strbuf, "Plane %d = '%s'\n", static_cast(n), gPlanePaths[n].c_str()); + XPLMDebugString(strbuf); +#endif + ptrs.push_back((char *) gPlanePaths[n].c_str()); + } + ptrs.push_back(NULL); + + + // Attempt to grab multiplayer planes, then analyze. + int result = XPLMAcquirePlanes(&(*ptrs.begin()), NULL, NULL); + if (result) + XPLMSetActiveAircraftCount(1); + //else + // XPLMDebugString("WARNING: XSquawkBox did not acquire multiplayer planes!!\n"); + + int total, active; + XPLMPluginID who; + + XPLMCountAircraft(&total, &active, &who); + if (result == 0) + { + return "XSquawkBox was not able to start up multiplayer visuals because another plugin is controlling aircraft."; + } else + return ""; +} + +void XPMPMultiplayerDisable(void) +{ + XPLMReleasePlanes(); +} + + +const char * XPMPLoadCSLPackage( + const char * inCSLFolder, const char * inRelatedPath, const char * inDoc8643) +{ + bool problem = false; + + if (!CSL_LoadCSL(inCSLFolder, inRelatedPath, inDoc8643)) + problem = true; + + if (problem) return "There were problems initializing XSquawkBox. Please examine X-Plane's error.out file for detailed information."; + else return ""; +} + +// This routine checks plane loading and grabs anyone we're missing. +void XPMPLoadPlanesIfNecessary(void) +{ + int active, models; + XPLMPluginID owner; + XPLMCountAircraft(&models, &active, &owner); + if (owner != XPLMGetMyID()) + return; + + if (models > static_cast(gPlanePaths.size())) + models = static_cast(gPlanePaths.size()); + for (int n = 1; n < models; ++n) + { + if (!gPlanePaths[n].empty()) + { + const char * ourPath = gPlanePaths[n].c_str(); + char realPath[512]; + char fileName[256]; + XPLMGetNthAircraftModel(n, fileName, realPath); + if (strcmp(ourPath, realPath)) + { +#if DEBUG_MANUAL_LOADING + XPLMDebugString("Manually Loading plane: "); + XPLMDebugString(ourPath); + XPLMDebugString("\n"); +#endif + XPLMSetAircraftModel(n, ourPath); + } + } + } + +} + +/******************************************************************************** + * PLANE OBJECT SUPPORT + ********************************************************************************/ + +XPMPPlaneID XPMPCreatePlane( + const char * inICAOCode, + const char * inAirline, + const char * inLivery, + XPMPPlaneData_f inDataFunc, + void * inRefcon) +{ + XPMPPlanePtr plane = new XPMPPlane_t; + plane->icao = inICAOCode; + plane->livery = inLivery; + plane->airline = inAirline; + plane->dataFunc = inDataFunc; + plane->ref = inRefcon; + plane->model = CSL_MatchPlane(inICAOCode, inAirline, inLivery, &plane->good_livery, true); + + plane->pos.size = sizeof(plane->pos); + plane->surface.size = sizeof(plane->surface); + plane->radar.size = sizeof(plane->radar); + plane->posAge = plane->radarAge = plane->surfaceAge = -1; + + gPlanes.push_back(plane); + + for (XPMPPlaneNotifierVector::iterator iter = gObservers.begin(); iter != + gObservers.end(); ++iter) + { + iter->first.first(plane, xpmp_PlaneNotification_Created, iter->first.second); + } + return plane; +} + +void XPMPDestroyPlane(XPMPPlaneID inID) +{ + XPMPPlaneVector::iterator iter; + XPMPPlanePtr plane = XPMPPlaneIsValid(inID, &iter); + if (plane == NULL) + return; + + for (XPMPPlaneNotifierVector::iterator iter2 = gObservers.begin(); iter2 != + gObservers.end(); ++iter2) + { + iter2->first.first(plane, xpmp_PlaneNotification_Destroyed, iter2->first.second); + } + gPlanes.erase(iter); + + delete plane; +} + +void XPMPChangePlaneModel( + XPMPPlaneID inPlaneID, + const char * inICAOCode, + const char * inAirline, + const char * inLivery) +{ + XPMPPlanePtr plane = XPMPPlaneIsValid(inPlaneID, NULL); + if (plane) + { + plane->icao = inICAOCode; + plane->airline = inAirline; + plane->livery = inLivery; + plane->model = CSL_MatchPlane(inICAOCode, inAirline, inLivery, &plane->good_livery, true); + + } + + for (XPMPPlaneNotifierVector::iterator iter2 = gObservers.begin(); iter2 != + gObservers.end(); ++iter2) + { + iter2->first.first(plane, xpmp_PlaneNotification_ModelChanged, iter2->first.second); + } + +} + +void XPMPSetDefaultPlaneICAO( + const char * inICAO) +{ + gDefaultPlane = inICAO; +} + +long XPMPCountPlanes(void) +{ + return static_cast(gPlanes.size()); +} + +XPMPPlaneID XPMPGetNthPlane( + long index) +{ + if ((index < 0) || (index >= static_cast(gPlanes.size()))) + return NULL; + + return gPlanes[index]; +} + + +void XPMPGetPlaneICAOAndLivery( + XPMPPlaneID inPlane, + char * outICAOCode, // Can be NULL + char * outLivery) +{ + XPMPPlanePtr plane = XPMPPlaneIsValid(inPlane, NULL); + if (plane == NULL) + return; + + if (outICAOCode) + strcpy(outICAOCode,plane->icao.c_str()); + if (outLivery) + strcpy(outLivery,plane->livery.c_str()); +} + +void XPMPRegisterPlaneNotifierFunc( + XPMPPlaneNotifier_f inFunc, + void * inRefcon) +{ + gObservers.push_back(XPMPPlaneNotifierTripple(XPMPPlaneNotifierPair(inFunc, inRefcon), XPLMGetMyID())); +} + +void XPMPUnregisterPlaneNotifierFunc( + XPMPPlaneNotifier_f inFunc, + void * inRefcon) +{ + XPMPPlaneNotifierVector::iterator iter = std::find( + gObservers.begin(), gObservers.end(), XPMPPlaneNotifierTripple(XPMPPlaneNotifierPair(inFunc, inRefcon), XPLMGetMyID())); + if (iter != gObservers.end()) + gObservers.erase(iter); +} + +int XPMPGetPlaneData( + XPMPPlaneID inPlane, + XPMPPlaneDataType inDataType, + void * outData) +{ + XPMPPlanePtr plane = XPMPPlaneIsValid(inPlane, NULL); + if (plane == NULL) + return -1; + + int now = XPLMGetCycleNumber(); + + switch(inDataType) { + case xpmpDataType_Position: + { + if (plane->posAge != now) + { + XPMPPlaneCallbackResult result = + plane->dataFunc(plane, inDataType, &plane->pos, plane->ref); + if (result == xpmpData_NewData) + plane->posAge = now; + } + + XPMPPlanePosition_t * posD = (XPMPPlanePosition_t *) outData; + memcpy(posD, &plane->pos, XPMP_TMIN(posD->size, plane->pos.size)); + + return plane->posAge; + } + case xpmpDataType_Surfaces: + { + if (plane->surfaceAge != now) + { + XPMPPlaneCallbackResult result = + plane->dataFunc(plane, inDataType, &plane->surface, plane->ref); + if (result == xpmpData_NewData) + plane->surfaceAge = now; + } + + XPMPPlaneSurfaces_t * surfD = (XPMPPlaneSurfaces_t *) outData; + memcpy(surfD, &plane->surface, XPMP_TMIN(surfD->size, plane->surface.size)); + return plane->surfaceAge; + } + case xpmpDataType_Radar: + { + if (plane->radarAge != now) + { + XPMPPlaneCallbackResult result = + plane->dataFunc(plane, inDataType, &plane->radar, plane->ref); + if (result == xpmpData_NewData) + plane->radarAge = now; + } + + XPMPPlaneRadar_t * radD = (XPMPPlaneRadar_t *) outData; + memcpy(radD, &plane->radar, XPMP_TMIN(radD->size, plane->radar.size)); + return plane->radarAge; + } + } + return -1; +} + +XPMPPlanePtr XPMPPlaneIsValid(XPMPPlaneID inID, XPMPPlaneVector::iterator * outIter) +{ + XPMPPlanePtr ptr = (XPMPPlanePtr) inID; + XPMPPlaneVector::iterator iter = std::find(gPlanes.begin(), gPlanes.end(), ptr); + if (iter == gPlanes.end()) + return NULL; + if (outIter) + *outIter = iter; + return ptr; +} + +void XPMPSetPlaneRenderer( + XPMPRenderPlanes_f inRenderer, + void * inRef) +{ + gRenderer = inRenderer; + gRendererRef = inRef; +} + +/******************************************************************************** + * RENDERING + ********************************************************************************/ + +// This callback ping-pongs the multiplayer count up and back depending +// on whether we're drawing the TCAS gauges or not. +int XPMPControlPlaneCount( + XPLMDrawingPhase /*inPhase*/, + int /*inIsBefore*/, + void * inRefcon) +{ + if (inRefcon == NULL) + { + XPLMSetActiveAircraftCount(1); + } else { + XPLMSetActiveAircraftCount(gEnableCount); + } + return 1; +} + + +// This routine draws the actual planes. +int XPMPRenderMultiplayerPlanes( + XPLMDrawingPhase /*inPhase*/, + int /*inIsBefore*/, + void * /*inRefcon*/) +{ + static int is_blend = 0; + + static XPLMDataRef wrt = XPLMFindDataRef("sim/graphics/view/world_render_type"); + static XPLMDataRef prt = XPLMFindDataRef("sim/graphics/view/plane_render_type"); + + int is_shadow = wrt != NULL && XPLMGetDatai(wrt) != 0; + + if(prt) + is_blend = XPLMGetDatai(prt) == 2; + + if (gRenderer) + gRenderer(is_shadow ? 0 : is_blend,gRendererRef); + else + XPMPDefaultPlaneRenderer(is_shadow ? 0 : is_blend); + if(!is_shadow) + is_blend = 1 - is_blend; + return 1; +} + +bool XPMPIsICAOValid( + const char * inICAO) + { + return CSL_MatchPlane(inICAO, "", "", NULL, false) != NULL; + } + +void XPMPDumpOneCycle(void) +{ + CSL_Dump(); + gDumpOneRenderCycle = true; +} diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.cpp b/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.cpp new file mode 100644 index 000000000..0778224e1 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.cpp @@ -0,0 +1,1155 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XPMPMultiplayerCSL.h" +#include "XPLMUtilities.h" +#include "XPMPMultiplayerObj.h" +#include "XOGLUtils.h" +#include +#include +//#include "PlatformUtils.h" +#include +#include + +using std::max; + +#if APL +#include +#endif + +// Set this to 1 to get TONS of diagnostics on what the lib is doing. +#define DEBUG_CSL_LOADING 1 + +// Set this to 1 to cause AIRLINE and LIVERY to create ICAO codes automatically +#define USE_DEFAULTING 0 + +enum { + pass_Depend, + pass_Load, + pass_Count +}; + +/************************************************************************ + * UTILITY ROUTINES + ************************************************************************/ + +#if APL + +template +struct CFSmartPtr { + CFSmartPtr(T p) : p_(p) { } + ~CFSmartPtr() { if (p_) CFRelease(p_); } + operator T () { return p_; } + T p_; +}; + +int Posix2HFSPath(const char *path, char *result, int resultLen) +{ + CFSmartPtr inStr(CFStringCreateWithCString(kCFAllocatorDefault, path ,kCFStringEncodingMacRoman)); + if (inStr == NULL) return -1; + + CFSmartPtr url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inStr, kCFURLPOSIXPathStyle,0)); + if (url == NULL) return -1; + + CFSmartPtr outStr(CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle)); + if (outStr == NULL) return -1; + + if (!CFStringGetCString(outStr, result, resultLen, kCFStringEncodingMacRoman)) + return -1; + + return 0; +} + +int HFS2PosixPath(const char *path, char *result, int resultLen) +{ + bool is_dir = (path[strlen(path)-1] == ':'); + + CFSmartPtr inStr(CFStringCreateWithCString(kCFAllocatorDefault, path ,kCFStringEncodingMacRoman)); + if (inStr == NULL) return -1; + + CFSmartPtr url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inStr, kCFURLHFSPathStyle,0)); + if (url == NULL) return -1; + + CFSmartPtr outStr(CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)); + if (outStr == NULL) return -1; + + if (!CFStringGetCString(outStr, result, resultLen, kCFStringEncodingMacRoman)) + return -1; + + if(is_dir) strcat(result, "/"); + + return 0; +} + +#endif + +static void MakePartialPathNativeObj(string& io_str) +{ +// char sep = *XPLMGetDirectorySeparator(); + for(size_t i = 0; i < io_str.size(); ++i) + if(io_str[i] == '/' || io_str[i] == ':' || io_str[i] == '\\') + io_str[i] = '/'; +} + +struct XPLMDump { + XPLMDump() { } + + XPLMDump(const string& inFileName, int lineNum, const char * line) { + XPLMDebugString("xbus WARNING: Parse Error in file "); + XPLMDebugString(inFileName.c_str()); + XPLMDebugString(" line "); + char buf[32]; + sprintf(buf,"%d", lineNum); + XPLMDebugString(buf); + XPLMDebugString(".\n "); + XPLMDebugString(line); + XPLMDebugString(".\n"); + } + + XPLMDump& operator<<(const char * rhs) { + XPLMDebugString(rhs); + return *this; + } + XPLMDump& operator<<(const std::string& rhs) { + XPLMDebugString(rhs.c_str()); + return *this; + } + XPLMDump& operator<<(int n) { + char buf[255]; + sprintf(buf, "%d", n); + XPLMDebugString(buf); + return *this; + } + XPLMDump& operator<<(size_t n) { + char buf[255]; + sprintf(buf, "%u", static_cast(n)); + XPLMDebugString(buf); + return *this; + } +}; + + +static char * fgets_multiplatform(char * s, int n, FILE * file); +static void BreakStringPvt(const char * inString, std::vector& outStrings, int maxBreak, const std::string& inSeparators); +static bool DoPackageSub(std::string& ioPath); + +bool DoPackageSub(std::string& ioPath) +{ + for (std::map::iterator i = gPackageNames.begin(); i != gPackageNames.end(); ++i) + { + if (strncmp(i->first.c_str(), ioPath.c_str(), i->first.size()) == 0) + { + ioPath.erase(0, i->first.size()); + ioPath.insert(0, i->second); + return true; + } + } + return false; +} + + +// This routine gets one line, but handles any platforms crlf setup. +char * fgets_multiplatform(char * s, int n, FILE * file) +{ + char * p = s; + int c; + int c1; + + // Save one slot for the null. If we do not have enough memory + // to do this, bail. + if (--n < 0) + return(NULL); + + // Only bother to read if we have enough space in the char buf. + if (n) + do + { + c = getc(file); + + // EOF: this could mean I/O error or end of file. + if (c == EOF) + { + if (feof(file) && p != s) // We read something and now the file's done, ok. + break; + else + { + // Haven't read yet? I/O error? Return NULL! + return(NULL); + } + } + + *p++ = c; + } + // Stop when we see either newline char or the buffer is full. + // Note that the \r\n IS written to the line. + while (c != '\n' && c != '\r' && --n); + + // Ben's special code: eat a \n if it follows a \r, etc. Mac stdio + // swizzles these guys a bit, so we will consolidate BOTH \r\n and \n\r into + // just the first. + if (c == '\r') + { + c1 = getc(file); + if (c1 != '\n') ungetc(c1, file); + } + if (c == '\n') + { + c1 = getc(file); + if (c1 != '\r') ungetc(c1, file); + } + + // Unless we're bailing with NULL, we MUST null terminate. + *p = 0; + + return(s); +} + +// This routine breaks a line into one or more tokens based on delimitors. +void BreakStringPvt(const char * inString, std::vector& outStrings, + int maxBreak, const std::string& inSeparators) +{ + outStrings.clear(); + + const char * endPos = inString + strlen(inString); + const char * iter = inString; + while (iter < endPos) + { + while ((iter < endPos) && (inSeparators.find(*iter) != std::string::npos)) + ++iter; + if (iter < endPos) + { + if (maxBreak && (maxBreak == static_cast(outStrings.size()+1))) + { + outStrings.push_back(std::string(iter, endPos)); + return; + } + const char * wordEnd = iter; + while ((wordEnd < endPos) && (inSeparators.find(*wordEnd) == std::string::npos)) + ++wordEnd; + + outStrings.push_back(std::string(iter, wordEnd)); + + iter = wordEnd; + } + } +} + + +/************************************************************************ + * CSL LOADING + ************************************************************************/ + +static bool LoadOnePackage(const string& inPath, int pass); + +bool CSL_Init( + const char* inTexturePath) +{ + obj_init(); + bool ok = OBJ_Init(inTexturePath); + if (!ok) + XPLMDump() << "xbus WARNING: we failed to find xpmp's custom lighting texture at " << inTexturePath << ".\n"; + return ok; +} + +// This routine loads one CSL package. +bool LoadOnePackage(const string& inPath, int pass) +{ + string group, icao, livery, airline; + bool parse_err = false; + char line[1024*4]; + int sim, xplm; + XPLMHostApplicationID host; + +#if DEBUG_CSL_LOADING + XPLMDump() << "LoadOnePackage was passed inPath of: " << inPath << ".\n"; +#endif + // First locate and attempt to load the xsb_aircraft.txt file from th is package. + string path(inPath); + path += "/"; //XPLMGetDirectorySeparator(); + path += "xsb_aircraft.txt"; +#if DEBUG_CSL_LOADING + XPLMDump() << "LoadOnePackage attempting to open: " << path << ".\n"; +#endif + + FILE * fi = fopen(path.c_str(), "r"); + + XPLMGetVersions(&sim, &xplm, &host); + int lineNum = 0; + + if (fi != NULL) + { + if (pass == pass_Load) + XPLMDump() << "xbus: Loading package: " << path << "\n"; + + if (pass == pass_Load) + gPackages.push_back(CSLPackage_t()); + CSLPackage_t * pckg = (pass == pass_Load) ? &gPackages.back() : NULL; + if (pass == pass_Load) + pckg->name = path; + + std::vector tokens; + + // BEN SEZ: we need to understand why thsi hack would be needed! + // I dont know why - but this seems to fix a Linux STL issue, somehow -Martin +// tokens.push_back(""); +// tokens.push_back(""); +// tokens.push_back(""); +// tokens.push_back(""); +// tokens.push_back(""); + + // Go through the file and handle each token. + while(!feof(fi)) + { + if (!fgets_multiplatform(line, sizeof(line), fi)) + break; + ++lineNum; + + if (line[0] == '#') continue; + + char * p = line; + while (*p) + { + if (*p == '\n' || *p == '\r') *p = 0; + ++p; + } + + BreakStringPvt(line, tokens, 4, " \t\r\n"); + + //---------------------------------------------------------------------------------------------------- + // PACKAGE MANAGEMENT + //---------------------------------------------------------------------------------------------------- + + // EXPORT_NAME + if (!tokens.empty() && tokens[0] == "EXPORT_NAME" && pass == pass_Depend) + { + if (tokens.size() == 2) + { + if (gPackageNames.count(tokens[1]) == 0) + { + gPackageNames[tokens[1]] = inPath; + } else { + //parse_err = true; // warning, not error + XPLMDump(path, lineNum, line) << "xbus WARNING: Package name " << tokens[1].c_str() << " already in use by "<< gPackageNames[tokens[1]].c_str() << " reqested by use by " << inPath.c_str() << "'\n"; + } + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: EXPORT_NAME command requires 1 argument.\n"; + } + } + + + // DEPENDENCY + if (!tokens.empty() && tokens[0] == "DEPENDENCY" && pass == pass_Load) + { + if (tokens.size() == 2) + { + if (gPackageNames.count(tokens[1]) == 0) + { + XPLMDump(path, lineNum, line) << "xbus WARNING: required package " << tokens[1] << " not found. Aborting processing of this package.\n"; + fclose(fi); + return true; + } + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: DEPENDENCY command needs 1 argument.\n"; + } + } + + //---------------------------------------------------------------------------------------------------- + // AUSTIN OLD SCHOOL ACFS + //---------------------------------------------------------------------------------------------------- + + // AIRCAFT + if (!tokens.empty() && tokens[0] == "AIRCRAFT" && pass == pass_Load) + { + if (tokens.size() == 4) + { + if (sim >= atoi(tokens[1].c_str()) && + sim <= atoi(tokens[2].c_str())) + { + std::string fullPath = tokens[3]; + MakePartialPathNativeObj(fullPath); + if (!DoPackageSub(fullPath)) + { + XPLMDump(path, lineNum, line) << "xbus WARNING: package not found.\n"; + parse_err = true; + } + pckg->planes.push_back(CSLPlane_t()); + pckg->planes.back().plane_type = plane_Austin; + pckg->planes.back().file_path = fullPath; + pckg->planes.back().moving_gear = true; + pckg->planes.back().austin_idx = -1; +#if DEBUG_CSL_LOADING + XPLMDebugString(" Got Airplane: "); + XPLMDebugString(fullPath.c_str()); + XPLMDebugString("\n"); +#endif + + } + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: AIRCRAFT command takes 3 arguments.\n"; + } + } + + //---------------------------------------------------------------------------------------------------- + // OBJ7 DRAWN WITH OUR CODE + //---------------------------------------------------------------------------------------------------- + + // OBJECT + if (!tokens.empty() && tokens[0] == "OBJECT" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 2, " \t\r\n"); + if (tokens.size() == 2) + { + std::string fullPath = tokens[1]; + MakePartialPathNativeObj(fullPath); + if (!DoPackageSub(fullPath)) + { + XPLMDump(path, lineNum, line) << "xbus WARNING: package not found.\n"; + parse_err = true; + } + pckg->planes.push_back(CSLPlane_t()); + pckg->planes.back().plane_type = plane_Obj; + pckg->planes.back().file_path = fullPath; + pckg->planes.back().moving_gear = true; + pckg->planes.back().texID = 0; + pckg->planes.back().texLitID = 0; + pckg->planes.back().obj_idx = OBJ_LoadModel(fullPath.c_str()); + if (pckg->planes.back().obj_idx == -1) + { + XPLMDump(path, lineNum, line) << "xbus WARNING: the model " << fullPath << " failed to load.\n"; + parse_err = true; + } +#if DEBUG_CSL_LOADING + XPLMDebugString(" Got Object: "); + XPLMDebugString(fullPath.c_str()); + XPLMDebugString("\n"); +#endif + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: OBJECT command takes 1 argument.\n"; + } + } + + // TEXTURE + if (!tokens.empty() && tokens[0] == "TEXTURE" && pass == pass_Load) + { + if(tokens.size() != 2) + { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: TEXTURE command takes 1 argument.\n"; + } else { + // Load regular texture + string texPath = tokens[1]; + MakePartialPathNativeObj(texPath); + + if (!DoPackageSub(texPath)) + { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: package not found.\n"; + } + pckg->planes.back().texID = OBJ_LoadTexture(texPath.c_str(), false); + if (pckg->planes.back().texID == -1) + { + parse_err = true; + XPLMDump(path, lineNum, line) << "Texture " << texPath << " failed to load.\n"; + } + // Load the lit texture + string texLitPath = texPath; + string::size_type pos2 = texLitPath.find_last_of("."); + if(pos2 != string::npos) + { + texLitPath.insert(pos2, "LIT"); + pckg->planes.back().texLitID = OBJ_LoadTexture(texLitPath.c_str(), false); + } + } + } + + //---------------------------------------------------------------------------------------------------- + // OBJ8 MULTI-OBJ WITH SIM RENDERING + //---------------------------------------------------------------------------------------------------- + + // OBJ8_AIRCRAFT + if (!tokens.empty() && tokens[0] == "OBJ8_AIRCRAFT" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 2, " \t\r\n"); + + if(tokens.size() == 2) + { + pckg->planes.push_back(CSLPlane_t()); + pckg->planes.back().plane_type = plane_Obj8; + pckg->planes.back().file_path = tokens[1]; // debug str + pckg->planes.back().moving_gear = true; + pckg->planes.back().texID = 0; + pckg->planes.back().texLitID = 0; + pckg->planes.back().obj_idx = -1; + } + else + { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: OBJ8_AIRCARFT command takes 1 argument.\n"; + } + } + + // OBJ8 + if (!tokens.empty() && tokens[0] == "OBJ8" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 4, " \t\r\n"); + + if(tokens.size() == 4) + { + if(pckg->planes.empty() || pckg->planes.back().plane_type != plane_Obj8) + { + // err - obj8 record at stupid place in file + } + else + { + + obj_for_acf att; + + if(tokens[1] == "GLASS") + att.draw_type = draw_glass; + else if(tokens[1] == "LIGHTS") + att.draw_type = draw_lights; + else if(tokens[1] == "LOW_LOD") + att.draw_type = draw_low_lod; + else if(tokens[1] == "SOLID") + att.draw_type = draw_solid; + else { + // err crap enum + } + + if(tokens[2] == "YES") + att.needs_animation = true; + else if(tokens[2] == "NO") + att.needs_animation = false; + else + { + // crap flag + } + std::string fullPath = tokens[3]; + + MakePartialPathNativeObj(fullPath); + if (!DoPackageSub(fullPath)) + { + XPLMDump(path, lineNum, line) << "xbus WARNING: package not found.\n"; + parse_err = true; + } + + char xsystem[1024]; + XPLMGetSystemPath(xsystem); + + #if APL + HFS2PosixPath(xsystem, xsystem, 1024); + #endif + + size_t sys_len = strlen(xsystem); + if(fullPath.size() > sys_len) + fullPath.erase(fullPath.begin(),fullPath.begin() + sys_len); + else + { + // should probaby freak out here. + } + + att.handle = NULL; + att.file = fullPath; + + pckg->planes.back().attachments.push_back(att); + } + } + else + { + // err - f---ed line. + } + } + + //---------------------------------------------------------------------------------------------------- + // MATCHING CRAP AND OTHER COMMON META-DATA + //---------------------------------------------------------------------------------------------------- + + + // HASGEAR YES|NO + // This line specifies whether the previous plane has retractable gear. + // Useful for preventing XSB from rolling up a C152's gear on takeoff! + if (!tokens.empty() && tokens[0] == "HASGEAR" && pass == pass_Load) + { + if (tokens.size() != 2 || (tokens[1] != "YES" && tokens[1] != "NO")) + { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: HASGEAR takes one argument that must be YES or NO.\n"; + } else { + if (tokens[1] == "YES") + pckg->planes.back().moving_gear = true; + else if (tokens[1] == "NO") + pckg->planes.back().moving_gear = false; + else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: HASGEAR must have a YES or NO argument, but we got " << tokens[1] << ".\n"; + } + } + } + + // ICAO + // This line maps one ICAO code to the previous airline, without + // specifying an airline or livery. + if (!tokens.empty() && tokens[0] == "ICAO" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 0, " \t"); + if (tokens.size() == 2) + { + icao = tokens[1]; + group = gGroupings[icao]; + if (pckg->matches[match_icao].count(icao) == 0) + pckg->matches[match_icao] [icao] = static_cast(pckg->planes.size()) - 1; + if (!group.empty()) + if (pckg->matches[match_group].count(group) == 0) + pckg->matches[match_group] [group] = static_cast(pckg->planes.size()) - 1; + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: ICAO command takes 1 argument.\n"; + } + } + + // AIRLINE + // This line maps one ICAO code to the previous airline, with + // an airline but without a livery. This will also create + // an ICAO-only association for non-airline-specific matching. + if (!tokens.empty() && tokens[0] == "AIRLINE" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 0, " \t"); + if (tokens.size() == 3) + { + icao = tokens[1]; + airline = tokens[2]; + group = gGroupings[icao]; + if (pckg->matches[match_icao_airline].count(icao + " " + airline) == 0) + pckg->matches[match_icao_airline] [icao + " " + airline] = static_cast(pckg->planes.size()) - 1; +#if USE_DEFAULTING + if (pckg->matches[match_icao ].count(icao ) == 0) + pckg->matches[match_icao ] [icao ] = pckg->planes.size() - 1; +#endif + if (!group.empty()) + { +#if USE_DEFAULTING + if (pckg->matches[match_group ].count(group ) == 0) + pckg->matches[match_group ] [group ] = pckg->planes.size() - 1; +#endif + if (pckg->matches[match_group_airline].count(group + " " + airline) == 0) + pckg->matches[match_group_airline] [group + " " + airline] = static_cast(pckg->planes.size()) - 1; + } + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: AIRLINE command takes two arguments.\n"; + } + } + + // LIVERY + // This line maps one ICAO code to the previous airline, with + // an airline and livery. This will also create + // an ICAO-only and ICAO/airline association for non-airline-specific + // matching. + if (!tokens.empty() && tokens[0] == "LIVERY" && pass == pass_Load) + { + BreakStringPvt(line, tokens, 0, " \t"); + if (tokens.size() == 4) + { + icao = tokens[1]; + airline = tokens[2]; + livery = tokens[3]; + group = gGroupings[icao]; +#if USE_DEFAULTING + if (pckg->matches[match_icao ].count(icao ) == 0) + pckg->matches[match_icao ] [icao ] = pckg->planes.size() - 1; + if (pckg->matches[match_icao ].count(icao ) == 0) + pckg->matches[match_icao_airline ] [icao + " " + airline ] = pckg->planes.size() - 1; +#endif + if (pckg->matches[match_icao_airline_livery ].count(icao + " " + airline + " " + livery) == 0) + pckg->matches[match_icao_airline_livery ] [icao + " " + airline + " " + livery] = static_cast(pckg->planes.size()) - 1; + if (!group.empty()) + { +#if USE_DEFAULTING + if (pckg->matches[match_group ].count(group ) == 0) + pckg->matches[match_group ] [group ] = pckg->planes.size() - 1; + if (pckg->matches[match_group_airline ].count(group + " " + airline ) == 0) + pckg->matches[match_group_airline ] [group + " " + airline ] = pckg->planes.size() - 1; +#endif + if (pckg->matches[match_group_airline_livery ].count(group + " " + airline + " " + livery) == 0) + pckg->matches[match_group_airline_livery ] [group + " " + airline + " " + livery] = static_cast(pckg->planes.size()) - 1; + } + } else { + parse_err = true; + XPLMDump(path, lineNum, line) << "xbus WARNING: LIVERY command takes two arguments.\n"; + } + } + } + fclose(fi); + } else { + XPLMDump() << "xbus WARNING: package '" << inPath << "' could not be opened. Error was: " << strerror(errno) << ".\n"; + } + return parse_err; +} + +// This routine loads the related.txt file and also all packages. +bool CSL_LoadCSL(const char * inFolderPath, const char * inRelatedFile, const char * inDoc8643) +{ + bool ok = true; + + // read the list of aircraft codes + FILE * aircraft_fi = fopen(inDoc8643, "r"); + + if (gIntPrefsFunc("debug", "model_matching", 0)) + XPLMDebugString(string(string(inDoc8643) + " returned " + (aircraft_fi ? "valid" : "invalid") + " fp\n").c_str()); + + if (aircraft_fi) + { + char buf[1024]; + while (fgets_multiplatform(buf, sizeof(buf), aircraft_fi)) + { + vector tokens; + BreakStringPvt(buf, tokens, 0, "\t\r\n"); + + /* + if (gIntPrefsFunc("debug", "model_matching", 0)) { + char str[20]; + sprintf(str, "size: %i", tokens.size()); + string s = string(str) + string(": ") + buf; + XPLMDebugString(s.c_str()); + } + */ + + // Sample line. Fields are separated by tabs + // ABHCO SA-342 Gazelle GAZL H1T - + + if(tokens.size() < 5) continue; + CSLAircraftCode_t entry; + entry.icao = tokens[2]; + entry.equip = tokens[3]; + entry.category = tokens[4][0]; + + // Debugging stuff + /* + if (gIntPrefsFunc("debug", "model_matching", 0)) { + XPLMDebugString("Loaded entry: icao code "); + XPLMDebugString(entry.icao.c_str()); + XPLMDebugString(" equipment "); + XPLMDebugString(entry.equip.c_str()); + XPLMDebugString(" category "); + switch(entry.category) { + case 'L': XPLMDebugString(" light"); break; + case 'M': XPLMDebugString(" medium"); break; + case 'H': XPLMDebugString(" heavy"); break; + default: XPLMDebugString(" other"); break; + } + XPLMDebugString("\n"); + } + */ + + gAircraftCodes[entry.icao] = entry; + } + fclose(aircraft_fi); + } else { + XPLMDump() << "xbus WARNING: could not open ICAO document 8643 at " << inDoc8643 << "\n"; + ok = false; + } + + // First grab the related.txt file. + FILE * related_fi = fopen(inRelatedFile, "r"); + if (related_fi) + { + char buf[1024]; + while (fgets_multiplatform(buf, sizeof(buf), related_fi)) + { + if (buf[0] != ';') + { + vector tokens; + BreakStringPvt(buf, tokens, 0, " \t\r\n"); + string group; + for (size_t n = 0; n < tokens.size(); ++n) + { + if (n != 0) group += " "; + group += tokens[n]; + } + for (size_t n = 0; n < tokens.size(); ++n) + { + gGroupings[tokens[n]] = group; + } + } + } + fclose(related_fi); + } else { + XPLMDump() << "xbus WARNING: could not open related.txt at " << inRelatedFile << "\n"; + ok = false; + } + + // Iterate through all directories using the XPLM and load them. + + char * name_buf = (char *) malloc(16384); + char ** index_buf = (char **) malloc(65536); + int total, ret; + + char folder[1024]; + +#if APL + Posix2HFSPath(inFolderPath, folder, sizeof(folder)); +#else + strcpy(folder,inFolderPath); +#endif + XPLMGetDirectoryContents(folder, 0, name_buf, 16384, index_buf, 65536 / sizeof(char*), + &total, &ret); + + vector pckgs; + for (int r = 0; r < ret; ++r) + { +#if APL + if (index_buf[r][0] == '.') + continue; +#endif + char * foo = index_buf[r]; + string path(inFolderPath); + path += "/";//XPLMGetDirectorySeparator(); + path += foo; + pckgs.push_back(path); + } + free(name_buf); + free(index_buf); + + for (int pass = 0; pass < pass_Count; ++pass) + for (size_t n = 0; n < pckgs.size(); ++n) + { + if (LoadOnePackage(pckgs[n], pass)) + ok = false; + } + +#if 0 + ::Microseconds((UnsignedWide*) &t2); + double delta = (t2 - t1); + delta /= 1000000.0; + char buf[256]; + sprintf(buf,"CSL full load took: %lf\n", delta); + XPLMDebugString(buf); +#endif + return ok; +} + +/************************************************************************ + * CSL MATCHING + ************************************************************************/ + + // Here's the basic idea: there are six levels of matching we can get, + // from the best (direct match of ICAO, airline and livery) to the worst + // (match an airplane's ICAO group but not ICAO, no livery or airline). + // So we will make six passes from best to worst, trying to match. For + // each pass we try each package in turn from highest to lowest priority. + +// These structs tell us how to build the matching keys for a given pass. +static int kUseICAO[] = { 1, 1, 0, 0, 1, 0 }; +static int kUseLivery[] = { 1, 0, 1, 0, 0, 0 }; +static int kUseAirline[] = { 0, 1, 0, 1, 0, 0 }; + +CSLPlane_t * CSL_MatchPlane(const char * inICAO, const char * inAirline, const char * inLivery, bool * got_livery, bool use_default) +{ + XPLMPluginID who; + int total, active; + XPLMCountAircraft(&total, &active, &who); + + // First build up our various keys and info we need to do the match. + string icao(inICAO); + string airline(inAirline ? inAirline : ""); + string livery(inLivery ? inLivery : ""); + string group; + string key; + + map::iterator group_iter = gGroupings.find(inICAO); + if (group_iter != gGroupings.end()) + group = group_iter->second; + + char buf[4096]; + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + sprintf(buf,"xbus MATCH - ICAO=%s AIRLINE=%s LIVERY=%s GROUP=%s\n", icao.c_str(), airline.c_str(), livery.c_str(), group.c_str()); + XPLMDebugString(buf); + } + + // Now we go through our six passes. + for (int n = 0; n < match_count; ++n) + { + // Build up the right key for this pass. + key = kUseICAO[n] ? icao : group; + if (kUseLivery[n]) + { + key += " "; + key += airline; + key += " "; + key += livery; + } + if (kUseAirline[n]) + { + key += " "; + key += airline; + } + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + sprintf(buf,"xbus MATCH - Group %d key %s\n", n, key.c_str()); + XPLMDebugString(buf); + } + + // Now go through each group and see if we match. + for (size_t p = 0; p < gPackages.size(); ++p) + { + map::iterator iter = gPackages[p].matches[n].find(key); + if (iter != gPackages[p].matches[n].end()) + if (gPackages[p].planes[iter->second].plane_type != plane_Austin || // Special check - do NOT match a plane that isn't loaded. + (gPackages[p].planes[iter->second].austin_idx != -1 && gPackages[p].planes[iter->second].austin_idx < total)) + if (gPackages[p].planes[iter->second].plane_type != plane_Obj || + gPackages[p].planes[iter->second].obj_idx != -1) + { + if (got_livery) *got_livery = (kUseLivery[n] || kUseAirline[n]); + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + sprintf(buf, "xbus MATCH - Found: %s\n", gPackages[p].planes[iter->second].file_path.c_str()); + XPLMDebugString(buf); + } + + return &gPackages[p].planes[iter->second]; + } + } + } + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + XPLMDebugString("xbus MATCH - No match.\n"); + } + + + + // try the next step: + // For each aircraft, we know the equiment type "L2T" and the WTC category. + // try to find a model that has the same equipment type and WTC + + std::map::const_iterator model_it = gAircraftCodes.find(icao); + if(model_it != gAircraftCodes.end()) { + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + XPLMDebugString("xbus MATCH/acf - Looking for a "); + switch(model_it->second.category) { + case 'L': XPLMDebugString(" light "); break; + case 'M': XPLMDebugString(" medium "); break; + case 'H': XPLMDebugString(" heavy "); break; + default: XPLMDebugString(" funny "); break; + } + XPLMDebugString(model_it->second.equip.c_str()); + XPLMDebugString(" aircraft\n"); + } + + // 1. match WTC, full configuration ("L2P") + // 2. match WTC, #engines and enginetype ("2P") + // 3. match WTC, #egines ("2") + // 4. match WTC, enginetype ("P") + // 5. match WTC + for(int pass = 1; pass <= 5; ++pass) { + + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + switch(pass) { + case 1: XPLMDebugString("xbus Match/acf - matching WTC and configuration\n"); break; + case 2: XPLMDebugString("xbus Match/acf - matching WTC, #engines and enginetype\n"); break; + case 3: XPLMDebugString("xbus Match/acf - matching WTC, #engines\n"); break; + case 4: XPLMDebugString("xbus Match/acf - matching WTC, enginetype\n"); break; + case 5: XPLMDebugString("xbus Match/acf - matching WTC\n"); break; + } + } + + for (size_t p = 0; p < gPackages.size(); ++p) + { + std::map::const_iterator it = gPackages[p].matches[4].begin(); + while(it != gPackages[p].matches[4].end()) { + + if (gPackages[p].planes[it->second].plane_type != plane_Austin || // Special check - do NOT match a plane that isn't loaded. + (gPackages[p].planes[it->second].austin_idx != -1 && gPackages[p].planes[it->second].austin_idx < total)) + if (gPackages[p].planes[it->second].plane_type != plane_Obj || + gPackages[p].planes[it->second].obj_idx != -1) + { + // we have a candidate, lets see if it matches our criteria + std::map::const_iterator m = gAircraftCodes.find(it->first); + if(m != gAircraftCodes.end()) { + // category + bool match = (m->second.category == model_it->second.category); + + // make sure we have a valid equip type if we need it + if(pass < 5 && m->second.equip.length() != 3) match = false; + + // engine type + if(match && (pass <= 2 || pass == 4)) + match = (m->second.equip[2] == model_it->second.equip[2]); + + // #engines + if(match && pass <= 3) + match = (m->second.equip[1] == model_it->second.equip[1]); + + // full configuration string + if(match && pass == 1) + match = (m->second.equip == model_it->second.equip); + + if(match) { + // bingo + if (gIntPrefsFunc("debug", "model_matching", 0)) + { + XPLMDebugString("xbus MATCH/acf - found: "); + XPLMDebugString(it->first.c_str()); + XPLMDebugString("\n"); + } + + return &gPackages[p].planes[it->second]; + } + } + } + + ++it; + } + } + } + } + + if (gIntPrefsFunc("debug", "model_matching", 0)) { + XPLMDebugString(string("gAircraftCodes.find(" + icao + ") returned no match.").c_str()); + } + + if (!strcmp(inICAO, gDefaultPlane.c_str())) return NULL; + if (!use_default) return NULL; + return CSL_MatchPlane(gDefaultPlane.c_str(), "", "", got_livery, false); +} + +void CSL_Dump(void) +{ + // DIAGNOSTICS - print out everything we know. + for (size_t n = 0; n < gPackages.size(); ++n) + { + XPLMDump() << "xbus CSL: Package " << n << " path = " << gPackages[n].name << "\n"; + for (size_t p = 0; p < gPackages[n].planes.size(); ++p) + { + XPLMDump() << "xbus CSL: Plane " << p << " = " << gPackages[n].planes[p].file_path << "\n"; + } + for (int t = 0; t < 6; ++t) + { + XPLMDump() << "xbus CSL: Table " << t << "\n"; + for (map::iterator i = gPackages[n].matches[t].begin(); i != gPackages[n].matches[t].end(); ++i) + { + XPLMDump() << "xbus CSL: " << i->first << " -> " << i->second << "\n"; + } + } + } +} + +/************************************************************************ + * CSL DRAWING + ************************************************************************/ + +int CSL_GetOGLIndex(CSLPlane_t * model) +{ + switch(model->plane_type) { + case plane_Austin: + return model->austin_idx; + case plane_Obj: + if (model->texID != 0) + return model->texID; + return OBJ_GetModelTexID(model->obj_idx); + default: + return 0; + } +} + +// Plane drawing couldn't be simpler - it's just a "switch" between all +// of our drawing techniques. +void CSL_DrawObject( + CSLPlane_t * model, + float distance, + double x, + double y, + double z, + double pitch, + double roll, + double heading, + int type, + int full, + xpmp_LightStatus lights, + XPLMPlaneDrawState_t * state) +{ + // Setup OpenGL for this plane render + if(type != plane_Obj8) + { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(static_cast(x), static_cast(y), static_cast(z)); + glRotatef(static_cast(heading), 0.0, -1.0, 0.0); + glRotatef(static_cast(pitch), 01.0, 0.0, 0.0); + glRotatef(static_cast(roll), 0.0, 0.0, -1.0); + } + + switch (type) + { + case plane_Austin: + { + XPLMPluginID who; + int total, active; + XPLMCountAircraft(&total, &active, &who); + if (model->austin_idx > 0 && model->austin_idx < active) + XPLMDrawAircraft(model->austin_idx, + static_cast(x), static_cast(y), static_cast(z), + static_cast(pitch), static_cast(roll), static_cast(heading), + full, state); + } + break; + case plane_Obj: + if (model->obj_idx != -1) + OBJ_PlotModel(model->obj_idx, model->texID, model->texLitID, full ? distance : max(distance, 10000.0f), + x, y ,z, pitch, roll, heading); + break; + case plane_Lights: + if (model->obj_idx != -1) + OBJ_DrawLights(model->obj_idx, distance, + x, y ,z, pitch, roll, heading, lights); + + break; + case plane_Obj8: + obj_schedule_one_aircraft( + model, + x, + y, + z, + pitch, + roll, + heading, + full, // + lights, + state); + break; + } + + if(type != plane_Obj8) + glPopMatrix(); +} diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.h b/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.h new file mode 100644 index 000000000..b5c72bce4 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerCSL.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef XPLMMULTIPLAYERCSL_H +#define XPLMMULTIPLAYERCSL_H + +/* + * XPLMMultiplayerCSL + * + * This unit is the master switch for managing aircraft models. It loads up all CSL packages and + * does the matching logic for finding aircraft. + * + */ + +#include "XPLMPlanes.h" +#include "XPMPMultiplayerVars.h" + +/* + * CSL_Init + * + * This routine Initializes the Multiplayer object section and sets up the lighting texture. + * + */ +bool CSL_Init( + const char* inTexturePath); +/* + * CSL_LoadCSL + * + * This routine loads all CSL packages and the related.txt file. + * + */ +bool CSL_LoadCSL( + const char * inFolderPath, // Path to CSL folder + const char * inRelated, // Path to related.txt - used by renderer for model matching + const char * inIcao8643); // Path to ICAO document 8643 (list of aircraft) + +/* + * CSL_MatchPlane + * + * Given an ICAO and optionally a livery and airline, this routine returns the best plane match, or + * NULL if there is no good plane match. If got_livery is not null, it is filled in with whether + * the match has a valid paint job or not. + * + */ +CSLPlane_t * CSL_MatchPlane(const char * inICAO, const char * inAirline, const char * inLivery, bool * got_livery, bool use_default); + +/* + * CSL_Dump + * + * This routine dumps the total state of the CSL system to the error.out file. + * + */ +void CSL_Dump(void); + +/* + * CSL_GetOGLIndex + * + * Given a model, this routine returns some kind of index number such that + * sorting all models by these indices gives the ideal OpenGL draw order. + * In other words, this index number sorts models by OGL state. + * + */ +int CSL_GetOGLIndex(CSLPlane_t * model); + +/* + * CSL_DrawObject + * + * Given a plane model rep and the params, this routine does the real drawing. The coordinate system must be pre-shifted + * to the plane's location. (This just dispatches to the appropriate drawing method). + * + */ +void CSL_DrawObject( + CSLPlane_t * model, + float distance, + double x, + double y, + double z, + double pitch, + double roll, + double heading, + int type, + int full, + xpmp_LightStatus lights, + XPLMPlaneDrawState_t * state); + + +#if APL +int Posix2HFSPath(const char *path, char *result, int resultLen); +int HFS2PosixPath(const char *path, char *result, int resultLen); +#endif + +#endif /* XPLMMULTIPLAYERCSL_H */ diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerObj.cpp b/src/xbus/libxplanemp/src/XPMPMultiplayerObj.cpp new file mode 100644 index 000000000..3238ab5f7 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerObj.cpp @@ -0,0 +1,853 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XPMPMultiplayerObj.h" +#include "XPMPMultiplayerVars.h" + +//#include "PlatformUtils.h" +#include "XObjReadWrite.h" +#include "TexUtils.h" +#include "XOGLUtils.h" + +#include +#include +#include + +#include "XPLMGraphics.h" +#include "XPLMUtilities.h" +#include "XPLMDataAccess.h" +#include "XPLMProcessing.h" + +#define DEBUG_NORMALS 0 +#define DISABLE_SHARING 0 +#define BLEND_NORMALS 1 + +using namespace std; + +const double kMetersToNM = 0.000539956803; +// Some color constants +//const float kNavLightRed[] = {1.0, 0.0, 0.2, 0.6}; +//const float kNavLightGreen[] = {0.0, 1.0, 0.3, 0.6}; +//const float kLandingLight[] = {1.0, 1.0, 0.7, 0.6}; +//const float kStrobeLight[] = {1.0, 1.0, 1.0, 0.4}; +const float kNavLightRed[] = {1.0f, 0.0f, 0.2f, 0.5f}; +const float kNavLightGreen[] = {0.0f, 1.0f, 0.3f, 0.5f}; +const float kLandingLight[] = {1.0f, 1.0f, 0.7f, 0.6f}; +const float kStrobeLight[] = {1.0f, 1.0f, 1.0f, 0.7f}; + +static int sLightTexture = -1; + +static void MakePartialPathNativeObj(string& io_str) +{ +// char sep = *XPLMGetDirectorySeparator(); + for(size_t i = 0; i < io_str.size(); ++i) + if(io_str[i] == '/' || io_str[i] == ':' || io_str[i] == '\\') + io_str[i] = '/'; +} + +static XPLMDataRef sFOVRef = XPLMFindDataRef("sim/graphics/view/field_of_view_deg"); +static float sFOV = 60.0; + +bool NormalizeVec(float vec[3]) +{ + float len=sqrt(vec[0]*vec[0]+vec[1]*vec[1]+vec[2]*vec[2]); + if (len>0.0) + { + len = 1.0f / len; + vec[0] *= len; + vec[1] *= len; + vec[2] *= len; + return true; + } + return false; +} + +void CrossVec(float a[3], float b[3], float dst[3]) +{ + dst[0] = a[1] * b[2] - a[2] * b[1] ; + dst[1] = a[2] * b[0] - a[0] * b[2] ; + dst[2] = a[0] * b[1] - a[1] * b[0] ; +} + + +/***************************************************** + Ben's Crazy Point Pool Class +******************************************************/ + +class OBJ_PointPool { +public: + OBJ_PointPool(){}; + ~OBJ_PointPool(){}; + + int AddPoint(float xyz[3], float st[2]); + void PreparePoolToDraw(); + void CalcTriNormal(int idx1, int idx2, int idx3); + void NormalizeNormals(void); + void DebugDrawNormals(); + void Purge() { mPointPool.clear(); } + int Size() { return static_cast(mPointPool.size()); } +private: + vector mPointPool; +}; + + + +// Adds a point to our pool and returns it's index. +// If one already exists in the same location, we +// just return that index +int OBJ_PointPool::AddPoint(float xyz[3], float st[2]) +{ +#if !DISABLE_SHARING + // Use x as the key...see if we can find it + for(size_t n = 0; n < mPointPool.size(); n += 8) + { + if((xyz[0] == mPointPool[n]) && + (xyz[1] == mPointPool[n+1]) && + (xyz[2] == mPointPool[n+2]) && + (st[0] == mPointPool[n+3]) && + (st[1] == mPointPool[n+4])) + return static_cast(n/8); // Clients care about point # not array index + } +#endif + + // If we're here, no match was found so we add it to the pool + // Add XYZ + mPointPool.push_back(xyz[0]); mPointPool.push_back(xyz[1]); mPointPool.push_back(xyz[2]); + // Add ST + mPointPool.push_back(st[0]); mPointPool.push_back(st[1]); + // Allocate some space for the normal later + mPointPool.push_back(0.0); mPointPool.push_back(0.0); mPointPool.push_back(0.0); + return (static_cast(mPointPool.size())/8)-1; +} + +// This function sets up OpenGL for our point pool +void OBJ_PointPool::PreparePoolToDraw() +{ + // Setup our triangle data (20 represents 5 elements of 4 bytes each + // namely s,t,xn,yn,zn) + glVertexPointer(3, GL_FLOAT, 32, &(*mPointPool.begin())); + // Set our texture data (24 represents 6 elements of 4 bytes each + // namely xn, yn, zn, x, y, z. We start 3 from the beginning to skip + // over x, y, z initially. + glClientActiveTextureARB(GL_TEXTURE1); + glTexCoordPointer(2, GL_FLOAT, 32, &(*(mPointPool.begin() + 3))); + glClientActiveTextureARB(GL_TEXTURE0); + glTexCoordPointer(2, GL_FLOAT, 32, &(*(mPointPool.begin() + 3))); + // Set our normal data... + glNormalPointer(GL_FLOAT, 32, &(*(mPointPool.begin() + 5))); +} + +void OBJ_PointPool::CalcTriNormal(int idx1, int idx2, int idx3) +{ + if (mPointPool[idx1*8 ]==mPointPool[idx2*8 ]&& + mPointPool[idx1*8+1]==mPointPool[idx2*8+1]&& + mPointPool[idx1*8+2]==mPointPool[idx2*8+2]) return; + if (mPointPool[idx1*8 ]==mPointPool[idx3*8 ]&& + mPointPool[idx1*8+1]==mPointPool[idx3*8+1]&& + mPointPool[idx1*8+2]==mPointPool[idx3*8+2]) return; + if (mPointPool[idx2*8 ]==mPointPool[idx3*8 ]&& + mPointPool[idx2*8+1]==mPointPool[idx3*8+1]&& + mPointPool[idx2*8+2]==mPointPool[idx3*8+2]) return; + + // idx2->idx1 cross idx1->idx3 = normal product + float v1[3], v2[3], n[3]; + v1[0] = mPointPool[idx2*8 ] - mPointPool[idx1*8 ]; + v1[1] = mPointPool[idx2*8+1] - mPointPool[idx1*8+1]; + v1[2] = mPointPool[idx2*8+2] - mPointPool[idx1*8+2]; + + v2[0] = mPointPool[idx2*8 ] - mPointPool[idx3*8 ]; + v2[1] = mPointPool[idx2*8+1] - mPointPool[idx3*8+1]; + v2[2] = mPointPool[idx2*8+2] - mPointPool[idx3*8+2]; + + // We do NOT normalize the cross product; we want larger triangles + // to make bigger normals. When we blend them, bigger sides will + // contribute more to the normals. We'll normalize the normals + // after the blend is done. + CrossVec(v1, v2, n); + mPointPool[idx1*8+5] += n[0]; + mPointPool[idx1*8+6] += n[1]; + mPointPool[idx1*8+7] += n[2]; + + mPointPool[idx2*8+5] += n[0]; + mPointPool[idx2*8+6] += n[1]; + mPointPool[idx2*8+7] += n[2]; + + mPointPool[idx3*8+5] += n[0]; + mPointPool[idx3*8+6] += n[1]; + mPointPool[idx3*8+7] += n[2]; +} + +inline void swapped_add(float& a, float& b) +{ + float a_c = a; + float b_c = b; + a += b_c; + b += a_c; +} + +void OBJ_PointPool::NormalizeNormals(void) +{ + // Average all normals of same-point, different texture points? Why is this needed? + // Well...the problem is that we get non-blended normals around the 'seam' where the ACF fuselage + // is put together...at this point the separate top and bottom texes touch. Their color will + // be the same but the S&T coords won't. If we have slightly different normals and the sun is making + // shiney specular hilites, the discontinuity is real noticiable. +#if BLEND_NORMALS + for (size_t n = 0; n < mPointPool.size(); n += 8) + { + for (size_t m = 0; m < mPointPool.size(); m += 8) + if (mPointPool[n ]==mPointPool[m ] && + mPointPool[n+1]==mPointPool[m+1] && + mPointPool[n+2]==mPointPool[m+2] && + m != n) + { + swapped_add(mPointPool[n+5], mPointPool[m+5]); + swapped_add(mPointPool[n+6], mPointPool[m+6]); + swapped_add(mPointPool[n+7], mPointPool[m+7]); + } + } +#endif + for (size_t n = 5; n < mPointPool.size(); n += 8) + { + NormalizeVec(&mPointPool[n]); + } +} + +// This is a debug routine that will draw each vertex's normals +void OBJ_PointPool::DebugDrawNormals() +{ + XPLMSetGraphicsState(0, 0, 0, 0, 0, 1, 0); + glColor3f(1.0, 0.0, 1.0); + glBegin(GL_LINES); + for(size_t n = 0; n < mPointPool.size(); n+=8) + { + glVertex3f(mPointPool[n], mPointPool[n+1], mPointPool[n+2]); + glVertex3f(mPointPool[n] + mPointPool[n+5], mPointPool[n+1] + mPointPool[n+1+5], + mPointPool[n+2] + mPointPool[n+2+5]); + } + glEnd(); +} + +/***************************************************** + Object and Struct Definitions +******************************************************/ +static map sTexes; + +struct LightInfo_t { + float xyz[3]; + int rgb[3]; +}; + +// One of these structs per LOD read from the OBJ file +struct LODObjInfo_t { + + float nearDist; // The visible range + float farDist; // of this LOD + vector triangleList; + vector lights; + OBJ_PointPool pointPool; + GLuint dl; +}; + +// One of these structs per OBJ file +struct ObjInfo_t { + + string path; + int texnum; + int texnum_lit; + XObj obj; + vector lods; +}; + +static vector sObjects; + +/***************************************************** + Utility functions to handle OBJ stuff +******************************************************/ + +int OBJ_LoadTexture(const char * inFilePath, bool inForceMaxTex) +{ + string path(inFilePath); + if (sTexes.count(path) > 0) + return sTexes[path]; + + int texNum; + XPLMGenerateTextureNumbers(&texNum, 1); + + int derez = 5 - gIntPrefsFunc("planes", "resolution", 3); + if (inForceMaxTex) + derez = 0; + bool ok = LoadTextureFromFile(path.c_str(), texNum, true, false, true, NULL, NULL, derez); + if (!ok) return 0; + + sTexes[path] = texNum; + return texNum; +} + +bool OBJ_Init(const char * inTexturePath) +{ + // Now that we've successfully loaded an aircraft, + // we can load our lighting texture + static bool firstTime = true; + if(firstTime) + { + sLightTexture = OBJ_LoadTexture(inTexturePath, true); + firstTime = false; + } + return sLightTexture != 0; +} + +// Load one model - return -1 if it can't be loaded. +int OBJ_LoadModel(const char * inFilePath) +{ + string path(inFilePath); + + for (size_t n = 0; n < sObjects.size(); ++n) + { + if (path == sObjects[n].path) + return static_cast(n); + } + + sObjects.push_back(ObjInfo_t()); + bool ok = XObjRead(path.c_str(), sObjects.back().obj); + if (!ok) + { + sObjects.pop_back(); + return -1; + } + + MakePartialPathNativeObj(sObjects.back().obj.texture); + + sObjects.back().path = path; + string tex_path(path); + string::size_type p = tex_path.find_last_of("\\:/");//XPLMGetDirectorySeparator()); + tex_path.erase(p+1); + tex_path += sObjects.back().obj.texture; + tex_path += ".png"; + sObjects.back().texnum = OBJ_LoadTexture(tex_path.c_str(), false); + if(sObjects.back().texnum == 0) + printf("WARNING: %s failed to load for %s.\n", tex_path.c_str(),inFilePath); + + tex_path = path; + p = tex_path.find_last_of("\\:/");//XPLMGetDirectorySeparator()); + tex_path.erase(p+1); + tex_path += sObjects.back().obj.texture; + tex_path += "_LIT.png"; + sObjects.back().texnum_lit = OBJ_LoadTexture(tex_path.c_str(), false); + + // We prescan all of the commands to see if there's ANY LOD. If there's + // not then we need to add one ourselves. If there is, we will find it + // naturally later. + bool foundLOD = false; + for (vector::iterator cmd = sObjects.back().obj.cmds.begin(); + cmd != sObjects.back().obj.cmds.end(); ++cmd) + { + if((cmd->cmdType == type_Attr) && (cmd->cmdID == attr_LOD)) + foundLOD = true; + } + if(foundLOD == false) + { + sObjects.back().lods.push_back(LODObjInfo_t()); + sObjects.back().lods.back().nearDist = 0; + sObjects.back().lods.back().farDist = 40000; + } + // Go through all of the commands for this object and filter out the polys + // and the lights. + for (vector::iterator cmd = sObjects.back().obj.cmds.begin(); + cmd != sObjects.back().obj.cmds.end(); ++cmd) + { + switch(cmd->cmdType) { + case type_Attr: + if(cmd->cmdID == attr_LOD) + { + // We've found a new LOD section so save this + // information in a new struct. From now on and until + // we hit this again, all data is for THIS LOD instance. + sObjects.back().lods.push_back(LODObjInfo_t()); + // Save our visible LOD range + sObjects.back().lods.back().nearDist = cmd->attributes[0]; + sObjects.back().lods.back().farDist = cmd->attributes[1]; + } + break; + case type_PtLine: + if(cmd->cmdID == obj_Light) + { + // For each light we've found, copy the data into our + // own light vector + for(size_t n = 0; n < cmd->rgb.size(); n++) + { + sObjects.back().lods.back().lights.push_back(LightInfo_t()); + sObjects.back().lods.back().lights.back().xyz[0] = cmd->rgb[n].v[0]; + sObjects.back().lods.back().lights.back().xyz[1] = cmd->rgb[n].v[1]; + sObjects.back().lods.back().lights.back().xyz[2] = cmd->rgb[n].v[2]; + sObjects.back().lods.back().lights.back().rgb[0] = static_cast(cmd->rgb[n].rgb[0]); + sObjects.back().lods.back().lights.back().rgb[1] = static_cast(cmd->rgb[n].rgb[1]); + sObjects.back().lods.back().lights.back().rgb[2] = static_cast(cmd->rgb[n].rgb[2]); + } + } + break; + case type_Poly: + { + vector indexes; + // First get our point pool setup with all verticies + for(size_t n = 0; n < cmd->st.size(); n++) + { + float xyz[3], st[2]; + int index; + + xyz[0] = cmd->st[n].v[0]; + xyz[1] = cmd->st[n].v[1]; + xyz[2] = cmd->st[n].v[2]; + st[0] = cmd->st[n].st[0]; + st[1] = cmd->st[n].st[1]; + index = sObjects.back().lods.back().pointPool.AddPoint(xyz, st); + indexes.push_back(index); + } + + switch(cmd->cmdID) { + case obj_Tri: + for(size_t n = 0; n < indexes.size(); ++n) + { + sObjects.back().lods.back().triangleList.push_back(indexes[n]); + } + break; + case obj_Tri_Fan: + for(size_t n = 2; n < indexes.size(); n++) + { + sObjects.back().lods.back().triangleList.push_back(indexes[0 ]); + sObjects.back().lods.back().triangleList.push_back(indexes[n-1]); + sObjects.back().lods.back().triangleList.push_back(indexes[n ]); + } + break; + case obj_Tri_Strip: + case obj_Quad_Strip: + for(size_t n = 2; n < indexes.size(); n++) + { + if((n % 2) == 1) + { + sObjects.back().lods.back().triangleList.push_back(indexes[n - 2]); + sObjects.back().lods.back().triangleList.push_back(indexes[n]); + sObjects.back().lods.back().triangleList.push_back(indexes[n - 1]); + } + else + { + sObjects.back().lods.back().triangleList.push_back(indexes[n - 2]); + sObjects.back().lods.back().triangleList.push_back(indexes[n - 1]); + sObjects.back().lods.back().triangleList.push_back(indexes[n]); + } + } + break; + case obj_Quad: + for(size_t n = 3; n < indexes.size(); n += 4) + { + sObjects.back().lods.back().triangleList.push_back(indexes[n-3]); + sObjects.back().lods.back().triangleList.push_back(indexes[n-2]); + sObjects.back().lods.back().triangleList.push_back(indexes[n-1]); + sObjects.back().lods.back().triangleList.push_back(indexes[n-3]); + sObjects.back().lods.back().triangleList.push_back(indexes[n-1]); + sObjects.back().lods.back().triangleList.push_back(indexes[n ]); + } + break; + } + } + break; + } + } + + // Calculate our normals for all LOD's + for (size_t i = 0; i < sObjects.back().lods.size(); i++) + { + for (size_t n = 0; n < sObjects.back().lods[i].triangleList.size(); n += 3) + { + sObjects.back().lods[i].pointPool.CalcTriNormal( + sObjects.back().lods[i].triangleList[n], + sObjects.back().lods[i].triangleList[n+1], + sObjects.back().lods[i].triangleList[n+2]); + } + sObjects.back().lods[i].pointPool.NormalizeNormals(); + sObjects.back().lods[i].dl = 0; + } + sObjects.back().obj.cmds.clear(); + return static_cast(sObjects.size())-1; +} + +/***************************************************** + Aircraft Model Drawing +******************************************************/ +// Note that texID and litTexID are OPTIONAL! They will only be filled +// in if the user wants to override the default texture specified by the +// obj file +void OBJ_PlotModel(int model, int texID, int litTexID, float inDistance, double /*inX*/, + double /*inY*/, double /*inZ*/, double /*inPitch*/, double /*inRoll*/, double /*inHeading*/) +{ + int tex, lit; + // Find out what LOD we need to draw + int lodIdx = -1; + for(size_t n = 0; n < sObjects[model].lods.size(); n++) + { + if((inDistance >= sObjects[model].lods[n].nearDist) && + (inDistance <= sObjects[model].lods[n].farDist)) + { + lodIdx = static_cast(n); + break; + } + } + // If we didn't find a good LOD bin, we don't draw! + if(lodIdx == -1) + return; + + // pointPool is and always was empty! returning early + if(sObjects[model].lods[lodIdx].pointPool.Size()==0 && sObjects[model].lods[lodIdx].dl == 0) + return; + + static XPLMDataRef night_lighting_ref = XPLMFindDataRef("sim/graphics/scenery/percent_lights_on"); + bool use_night = XPLMGetDataf(night_lighting_ref) > 0.25; + + if (model == -1) return; + + if(texID) + { + tex = texID; + lit = litTexID; + } + else + { + tex = sObjects[model].texnum; + lit = sObjects[model].texnum_lit; + } + if (!use_night) lit = 0; + if (tex == 0) lit = 0; + XPLMSetGraphicsState(1, (tex != 0) + (lit != 0), 1, 1, 1, 1, 1); + if (tex != 0) XPLMBindTexture2d(tex, 0); + if (lit != 0) XPLMBindTexture2d(lit, 1); + + if (tex) { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } + if (lit) { glActiveTextureARB(GL_TEXTURE1); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); glActiveTextureARB(GL_TEXTURE0); } + + if (sObjects[model].lods[lodIdx].dl == 0) + { + sObjects[model].lods[lodIdx].dl = glGenLists(1); + + GLint xpBuffer; + // See if the card even has VBO. If it does, save xplane's pointer + // and bind to 0 for us. + if(glBindBufferARB) + { + glGetIntegerv(GL_ARRAY_BUFFER_BINDING_ARB, &xpBuffer); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + } + // Save XPlanes OpenGL state + glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); + // Setup OpenGL pointers to our pool + sObjects[model].lods[lodIdx].pointPool.PreparePoolToDraw(); + // Enable vertex data sucking + glEnableClientState(GL_VERTEX_ARRAY); + // Enable normal array sucking + glEnableClientState(GL_NORMAL_ARRAY); + // Enable texture coordinate data sucking + glClientActiveTextureARB(GL_TEXTURE1); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + // Disable colors - maybe x-plane left it around. + glDisableClientState(GL_COLOR_ARRAY); + + glNewList(sObjects[model].lods[lodIdx].dl, GL_COMPILE); + // Kick OpenGL and draw baby! + glDrawElements(GL_TRIANGLES, static_cast(sObjects[model].lods[lodIdx].triangleList.size()), + GL_UNSIGNED_INT, &(*sObjects[model].lods[lodIdx].triangleList.begin())); + +#if DEBUG_NORMALS + sObjects[model].lods[lodIdx].pointPool.DebugDrawNormals(); + XPLMSetGraphicsState(1, (tex != 0) + (lit != 0), 1, 1, 1, 1, 1); +#endif + + glEndList(); + + // Disable vertex data sucking + glDisableClientState(GL_VERTEX_ARRAY); + // Disable texture coordinate data sucking + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + // Disable normal array sucking + glDisableClientState(GL_NORMAL_ARRAY); + + // Restore Xplane's OpenGL State + glPopClientAttrib(); + + // If we bound before, we need to put xplane back where it was + if(glBindBufferARB) + glBindBufferARB(GL_ARRAY_BUFFER_ARB, xpBuffer); + + sObjects[model].lods[lodIdx].triangleList.clear(); + sObjects[model].lods[lodIdx].pointPool.Purge(); + } + glCallList(sObjects[model].lods[lodIdx].dl); + + +} + +/***************************************************** + Textured Lights Drawing + + Draw one or more lights on our OBJ. + RGB of 11,11,11 is a RED NAV light + RGB of 22,22,22 is a GREEN NAV light + RGB of 33,33,33 is a Red flashing BEACON light + RGB of 44,44,44 is a White flashing STROBE light + RGB of 55,55,55 is a landing light +******************************************************/ +void OBJ_BeginLightDrawing() +{ + sFOV = XPLMGetDataf(sFOVRef); + + // Setup OpenGL for the drawing + XPLMSetGraphicsState(1, 1, 0, 1, 1 , 1, 0); + XPLMBindTexture2d(sLightTexture, 0); +} + +void OBJ_DrawLights(int model, float inDistance, double inX, double inY, + double inZ, double inPitch, double inRoll, double inHeading, + xpmp_LightStatus lights) +{ + bool navLights = lights.navLights == 1; + bool bcnLights = lights.bcnLights == 1; + bool strbLights = lights.strbLights == 1; + bool landLights = lights.landLights == 1; + + int offset = lights.timeOffset; + + // flash frequencies + if(bcnLights) { + bcnLights = false; + int x = (int)(XPLMGetElapsedTime() * 1000 + offset) % 1200; + switch(lights.flashPattern) { + case xpmp_Lights_Pattern_EADS: + // EADS pattern: two flashes every 1.2 seconds + if(x < 120 || ((x > 240 && x < 360))) bcnLights = true; + break; + + case xpmp_Lights_Pattern_GA: + // GA pattern: 900ms / 1200ms + if((((int)(XPLMGetElapsedTime() * 1000 + offset) % 2100) < 900)) bcnLights = true; + break; + + case xpmp_Lights_Pattern_Default: + default: + // default pattern: one flash every 1.2 seconds + if(x < 120) bcnLights = true; + break; + } + + } + if(strbLights) { + strbLights = false; + int x = (int)(XPLMGetElapsedTime() * 1000 + offset) % 1700; + switch(lights.flashPattern) { + case xpmp_Lights_Pattern_EADS: + if(x < 80 || (x > 260 && x < 340)) strbLights = true; + break; + + case xpmp_Lights_Pattern_GA: + // similar to the others.. but a little different frequency :) + x = (int)(XPLMGetElapsedTime() * 1000 + offset) % 1900; + if(x < 100) strbLights = true; + break; + + case xpmp_Lights_Pattern_Default: + default: + if(x < 80) strbLights = true; + break; + } + } + + // Find out what LOD we need to draw + int lodIdx = -1; + for(size_t n = 0; n < sObjects[model].lods.size(); n++) + { + if((inDistance >= sObjects[model].lods[n].nearDist) && + (inDistance <= sObjects[model].lods[n].farDist)) + { + lodIdx = static_cast(n); + break; + } + } + // If we didn't find a good LOD bin, we don't draw! + if(lodIdx == -1) + return; + + GLfloat size; + double distance; + // Where are we looking? + XPLMCameraPosition_t cameraPos; + XPLMReadCameraPosition(&cameraPos); + + // We can have 1 or more lights on each aircraft + for(size_t n = 0; n < sObjects[model].lods[lodIdx].lights.size(); n++) + { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + // First we translate to our coordinate system and move the origin + // to the center of our lights. + glTranslatef(sObjects[model].lods[lodIdx].lights[n].xyz[0], + sObjects[model].lods[lodIdx].lights[n].xyz[1], + sObjects[model].lods[lodIdx].lights[n].xyz[2]); + + // Now we undo the rotation of the plane + glRotated(-inRoll, 0.0, 0.0, -1.0); + glRotated(-inPitch, 1.0, 0.0, 0.0); + glRotated(-inHeading, 0.0, -1.0, 0.0); + + // Now we undo the rotation of the camera + // NOTE: The order and sign of the camera is backwards + // from what we'd expect (the plane rotations) because + // the camera works backwards. If you pan right, everything + // else moves left! + glRotated(cameraPos.heading, 0.0, -1.0, 0.0); + glRotated(cameraPos.pitch, 1.0, 0.0, 0.0); + glRotated(cameraPos.roll, 0.0, 0.0, -1.0); + + // Find our distance from the camera + float dx = cameraPos.x - static_cast(inX); + float dy = cameraPos.y - static_cast(inY); + float dz = cameraPos.z - static_cast(inZ); + distance = sqrt((dx * dx) + (dy * dy) + (dz * dz)); + + // Convert to NM + distance *= kMetersToNM; + + // Scale based on our FOV and Zoom. I did my initial + // light adjustments at a FOV of 60 so thats why + // I divide our current FOV by 60 to scale it appropriately. + distance *= sFOV/60.0; + distance /= cameraPos.zoom; + + // Calculate our light size. This is piecewise linear. I noticed + // that light size changed more rapidly when closer than 3nm so + // I have a separate equation for that. + if(distance <= 3.6) + size = (10.0f * static_cast(distance)) + 1.0f; + else + size = (6.7f * static_cast(distance)) + 12.0f; + + // Finally we can draw our lights + // Red Nav + glBegin(GL_QUADS); + if((sObjects[model].lods[lodIdx].lights[n].rgb[0] == 11) && + (sObjects[model].lods[lodIdx].lights[n].rgb[1] == 11) && + (sObjects[model].lods[lodIdx].lights[n].rgb[2] == 11)) + { + if(navLights) { + glColor4fv(kNavLightRed); + glTexCoord2f(0.0f, 0.5f); glVertex2f(-(size/2.0f), -(size/2.0f)); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-(size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 1.0f); glVertex2f((size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f((size/2.0f), -(size/2.0f)); + } + } + // Green Nav + else if((sObjects[model].lods[lodIdx].lights[n].rgb[0] == 22) && + (sObjects[model].lods[lodIdx].lights[n].rgb[1] == 22) && + (sObjects[model].lods[lodIdx].lights[n].rgb[2] == 22)) + { + if(navLights) { + glColor4fv(kNavLightGreen); + glTexCoord2f(0.0f, 0.5f); glVertex2f(-(size/2.0f), -(size/2.0f)); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-(size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 1.0f); glVertex2f((size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f((size/2.0f), -(size/2.0f)); + } + } + // Beacon + else if((sObjects[model].lods[lodIdx].lights[n].rgb[0] == 33) && + (sObjects[model].lods[lodIdx].lights[n].rgb[1] == 33) && + (sObjects[model].lods[lodIdx].lights[n].rgb[2] == 33)) + { + if(bcnLights) + { + glColor4fv(kNavLightRed); + glTexCoord2f(0.0f, 0.5f); glVertex2f(-(size/2.0f), -(size/2.0f)); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-(size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 1.0f); glVertex2f((size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f((size/2.0f), -(size/2.0f)); + } + } + // Strobes + else if((sObjects[model].lods[lodIdx].lights[n].rgb[0] == 44) && + (sObjects[model].lods[lodIdx].lights[n].rgb[1] == 44) && + (sObjects[model].lods[lodIdx].lights[n].rgb[2] == 44)) + { + if(strbLights) + { + glColor4fv(kStrobeLight); + glTexCoord2f(0.25f, 0.0f); glVertex2f(-(size/1.5f), -(size/1.5f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f(-(size/1.5f), (size/1.5f)); + glTexCoord2f(0.50f, 0.5f); glVertex2f((size/1.5f), (size/1.5f)); + glTexCoord2f(0.50f, 0.0f); glVertex2f((size/1.5f), -(size/1.5f)); + } + } + // Landing Lights + else if((sObjects[model].lods[lodIdx].lights[n].rgb[0] == 55) && + (sObjects[model].lods[lodIdx].lights[n].rgb[1] == 55) && + (sObjects[model].lods[lodIdx].lights[n].rgb[2] == 55)) + { + if(landLights) { + // BEN SEZ: modulate the _alpha to make this dark, not + // the light color. Otherwise if the sky is fairly light the light + // will be darker than the sky, which looks f---ed during the day. + float color[4]; + color[0] = kLandingLight[0]; + if(color[0] < 0.0) color[0] = 0.0; + color[1] = kLandingLight[1]; + if(color[0] < 0.0) color[0] = 0.0; + color[2] = kLandingLight[2]; + if(color[0] < 0.0) color[0] = 0.0; + color[3] = kLandingLight[3] * ((static_cast(distance) * -0.05882f) + 1.1764f); + glColor4fv(color); + glTexCoord2f(0.25f, 0.0f); glVertex2f(-(size/2.0f), -(size/2.0f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f(-(size/2.0f), (size/2.0f)); + glTexCoord2f(0.50f, 0.5f); glVertex2f((size/2.0f), (size/2.0f)); + glTexCoord2f(0.50f, 0.0f); glVertex2f((size/2.0f), -(size/2.0f)); + } + } else { + // rear nav light and others? I guess... + if(navLights) { + glColor3f( + sObjects[model].lods[lodIdx].lights[n].rgb[0] * 0.1f, + sObjects[model].lods[lodIdx].lights[n].rgb[1] * 0.1f, + sObjects[model].lods[lodIdx].lights[n].rgb[2] * 0.1f); + glTexCoord2f(0.0f, 0.5f); glVertex2f(-(size/2.0f), -(size/2.0f)); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-(size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 1.0f); glVertex2f((size/2.0f), (size/2.0f)); + glTexCoord2f(0.25f, 0.5f); glVertex2f((size/2.0f), -(size/2.0f)); + } + } + glEnd(); + // Put OpenGL back how we found it + glPopMatrix(); + } +} + +int OBJ_GetModelTexID(int model) +{ + return sObjects[model].texnum; +} diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerObj.h b/src/xbus/libxplanemp/src/XPMPMultiplayerObj.h new file mode 100644 index 000000000..280dd18b4 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerObj.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef XPLMMULTIPLAYEROBJ_H +#define XPLMMULTIPLAYEROBJ_H + +#include "XPMPMultiplayer.h" // for light status +#include "XObjDefs.h" +#include "XPLMCamera.h" + +bool OBJ_Init(const char * inTexturePath); + +// Load one model - return -1 if it can't be loaded. +int OBJ_LoadModel(const char * inFilePath); + +// MODEL DRAWING +// Note that texID and litTexID are OPTIONAL! They will only be filled +// in if the user wants to override the default texture specified by the +// obj file +void OBJ_PlotModel(int model, int texID, int litTexID, float inDistance, double inX, double inY, + double inZ, double inPitch, double inRoll, double inHeading); + +// TEXTURED LIGHTS DRAWING +void OBJ_BeginLightDrawing(); +void OBJ_DrawLights(int model, float inDistance, double inX, double inY, + double inZ, double inPitch, double inRoll, double inHeading, + xpmp_LightStatus lights); + +// Texture loading +int OBJ_LoadTexture(const char * inFilePath, bool inForceMaxRes); +int OBJ_GetModelTexID(int model); + + +#endif diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.cpp b/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.cpp new file mode 100644 index 000000000..2a77ecbd2 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2013, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XPMPMultiplayerObj8.h" +#include "XPMPMultiplayerVars.h" +#include "XPLMScenery.h" +#include "XPLMUtilities.h" +#include "XPLMDataAccess.h" +#include +#include +using namespace std; + + +struct one_inst { + one_inst * next; + + XPLMDrawInfo_t location; + xpmp_LightStatus lights; + XPLMPlaneDrawState_t * state; + + ~one_inst() { delete next; } +}; + +struct one_obj { + one_obj * next; + obj_for_acf * model; + one_inst * head; + + ~one_obj() { delete head; delete next; } +}; + +static one_obj * s_worklist = NULL; + + +static one_inst * s_cur_plane = NULL; + +enum { + gear_rat = 0, + flap_rat, + spoi_rat, + sbrk_rat, + slat_rat, + swep_rat, + thrs_rat, + ptch_rat, + head_rat, + roll_rat, + + lan_lite_on, + bcn_lite_on, + str_lite_on, + nav_lite_on, + + dref_dim +}; + +const char * dref_names[dref_dim] = { + "libxplanemp/controls/gear_ratio", + "libxplanemp/controls/flap_ratio", + "libxplanemp/controls/spoiler_ratio", + "libxplanemp/controls/speed_brake_ratio", + "libxplanemp/controls/slat_ratio", + "libxplanemp/controls/wing_sweep_ratio", + "libxplanemp/controls/thrust_ratio", + "libxplanemp/controls/yoke_pitch_ratio", + "libxplanemp/controls/yoke_heading_ratio", + "libxplanemp/controls/yoke_roll_ratio", + + "libxplanemp/controls/landing_lites_on", + "libxplanemp/controls/beacon_lites_on", + "libxplanemp/controls/strobe_lites_on", + "libxplanemp/controls/nav_lites_on" +}; + + +static float obj_get_float(void * inRefcon) +{ + if(s_cur_plane == NULL) return 0.0f; + + intptr_t v = reinterpret_cast(inRefcon); + switch(v) + { + case gear_rat: return s_cur_plane->state->gearPosition; break; + case flap_rat: return s_cur_plane->state->flapRatio; break; + case spoi_rat: return s_cur_plane->state->spoilerRatio; break; + case sbrk_rat: return s_cur_plane->state->speedBrakeRatio; break; + case slat_rat: return s_cur_plane->state->slatRatio; break; + case swep_rat: return s_cur_plane->state->wingSweep; break; + case thrs_rat: return s_cur_plane->state->thrust; break; + case ptch_rat: return s_cur_plane->state->yokePitch; break; + case head_rat: return s_cur_plane->state->yokeHeading; break; + case roll_rat: return s_cur_plane->state->yokeRoll; break; + + case lan_lite_on: return static_cast(s_cur_plane->lights.landLights); break; + case bcn_lite_on: return static_cast(s_cur_plane->lights.bcnLights); break; + case str_lite_on: return static_cast(s_cur_plane->lights.strbLights); break; + case nav_lite_on: return static_cast(s_cur_plane->lights.navLights); break; + + default: + return 0.0f; + } +} + +int obj_get_float_array( + void * inRefcon, + float * inValues, + int /*inOffset*/, + int inCount) +{ + if(inValues == NULL) + return 1; + float rv = obj_get_float(inRefcon); + for(int i = 0; i < inCount; ++i) + inValues[i] = rv; + return inCount; +} + + + + + + + + + + +static void (*XPLMLoadObjectAsync_p)( + const char * inPath, + XPLMObjectLoaded_f inCallback, + void * inRefcon)=NULL; + +void obj_init() +{ + int sim, xplm; + XPLMHostApplicationID app; + XPLMGetVersions(&sim,&xplm,&app); + // Ben says: we need the 2.10 SDK (e.g. X-Plane 10) to have async load at all. But we need 10.30 to pick up an SDK bug + // fix where async load crashes if we queue a second load before the first completes. So for users on 10.25, they get + // pauses. + if(sim >= 10300 && xplm >= 210) + { + XPLMLoadObjectAsync_p = (void (*)(const char *, XPLMObjectLoaded_f, void *)) XPLMFindSymbol("XPLMLoadObjectAsync"); + } + + for(int i = 0; i < dref_dim; ++i) + { + XPLMRegisterDataAccessor( + dref_names[i], xplmType_Float|xplmType_FloatArray, 0, + NULL, NULL, + obj_get_float, NULL, + NULL, NULL, + NULL, NULL, + obj_get_float_array, NULL, + NULL, NULL, reinterpret_cast(i), NULL); + } +} + + + +static void draw_objects_for_mode(one_obj * who, int want_translucent) +{ + while(who) + { + obj_draw_type dt = who->model->draw_type; + if((want_translucent && dt == draw_glass) || + (!want_translucent && dt != draw_glass)) + { + for(one_inst * i = who->head; i; i = i->next) + { + s_cur_plane = i; + // set dataref ptr to light + obj sate from "one_inst". + XPLMDrawObjects(who->model->handle, 1, &i->location, 1, 0); + } + } + who = who->next; + } + s_cur_plane = NULL; +} + +void obj_loaded_cb(XPLMObjectRef obj, void * refcon) +{ + XPLMObjectRef * targ = (XPLMObjectRef *) refcon; + *targ = obj; +} + + +void obj_schedule_one_aircraft( + CSLPlane_t * model, + double x, + double y, + double z, + double pitch, + double roll, + double heading, + int /*full*/, // + xpmp_LightStatus lights, + XPLMPlaneDrawState_t * state) +{ + one_obj * iter; + + for(vector::iterator att = model->attachments.begin(); att != model->attachments.end(); ++att) + { + obj_for_acf * model = &*att; + + if(model->handle == NULL && + !model->file.empty()) + { + if(XPLMLoadObjectAsync_p) + XPLMLoadObjectAsync_p(model->file.c_str(),obj_loaded_cb,(void *) &model->handle); + else + model->handle = XPLMLoadObject(model->file.c_str()); + model->file.clear(); + } + + + for(iter = s_worklist; iter; iter = iter->next) + { + if(iter->model == model) + break; + } + if(iter == NULL) + { + iter = new one_obj; + iter->next = s_worklist; + s_worklist = iter; + iter->model = model; + iter->head = NULL; + } + + if(iter->model->handle) + { + one_inst * i = new one_inst; + i->next = iter->head; + iter->head = i; + i->lights = lights; + i->state = state; + i->location.structSize = sizeof(i->location); + i->location.x = static_cast(x); + i->location.y = static_cast(y); + i->location.z = static_cast(z); + i->location.pitch = static_cast(pitch); + i->location.roll = static_cast(roll); + i->location.heading = static_cast(heading); + } + } +} + +void obj_draw_solid() +{ + draw_objects_for_mode(s_worklist, false); +} + +void obj_draw_translucent() +{ + draw_objects_for_mode(s_worklist, true);\ +} + +void obj_draw_done() +{ + delete s_worklist; + s_worklist = NULL; +} diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.h b/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.h new file mode 100644 index 000000000..c78732275 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerObj8.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2013, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef XPMPMultiplayerObj8_h +#define XPMPMultiplayerObj8_h + +#include "XPLMScenery.h" +#include "XPLMPlanes.h" +#include "XPMPMultiplayer.h" +#include + +/* + + OBJ8_AIRCRAFT + OBJ8 LOW_LOD NO foo.obj + OBJ8 GLASS YES bar.obj + AIRLINE DAL + ICAO B732 B733 + + */ + +enum obj_draw_type { + + draw_lights = 0, + draw_low_lod, + draw_solid, + draw_glass + +}; + +struct obj_for_acf { + + std::string file; + XPLMObjectRef handle; + obj_draw_type draw_type; + bool needs_animation; + +}; + +struct CSLPlane_t; + +void obj_init(); + +bool obj_load_one_attached_obj( + const char * file_name, + bool needs_anim, + obj_draw_type draw_type, + obj_for_acf& out_attachment); + +///// + + +void obj_schedule_one_aircraft( + CSLPlane_t * model, + double x, + double y, + double z, + double pitch, + double roll, + double heading, + int full, // + xpmp_LightStatus lights, + XPLMPlaneDrawState_t * state); + + +void obj_draw_solid(); +void obj_draw_translucent(); +void obj_draw_done(); + + + + +#endif \ No newline at end of file diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerVars.cpp b/src/xbus/libxplanemp/src/XPMPMultiplayerVars.cpp new file mode 100644 index 000000000..a268f5356 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerVars.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "XPMPMultiplayerVars.h" +#include + + +int (* gIntPrefsFunc)(const char *, const char *, int) = NULL; +float (* gFloatPrefsFunc)(const char *, const char *, float) = NULL; + +XPMPPlaneVector gPlanes; +XPMPPlaneNotifierVector gObservers; +XPMPRenderPlanes_f gRenderer = NULL; +void * gRendererRef; +int gDumpOneRenderCycle = 0; +int gEnableCount = 1; + +vector gPackages; +map gGroupings; +map gPackageNames; + +string gDefaultPlane; +map gAircraftCodes; diff --git a/src/xbus/libxplanemp/src/XPMPMultiplayerVars.h b/src/xbus/libxplanemp/src/XPMPMultiplayerVars.h new file mode 100644 index 000000000..a7ca3c482 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPMultiplayerVars.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2005, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef XPLMMULTIPLAYERVARS_H +#define XPLMMULTIPLAYERVARS_H + +/* + * This file contains the various internal definitions + * and globals for the model rendering code. + * + */ + +#include +#include +#include +#include + +#include "XObjDefs.h" + +using namespace std; + +#include "XPMPMultiplayer.h" +#include "XPMPMultiplayerObj8.h" // for obj8 attachment info + +template +inline +const T& +XPMP_TMIN(const T& a, const T& b) +{ + return b < a ? b : a; +} + +template +inline +const T& +XPMP_TMAX(const T& a, const T& b) +{ + return a < b ? b : a; +} + + +const double kFtToMeters = 0.3048; +const double kMaxDistTCAS = 40.0 * 6080.0 * kFtToMeters; + + +/****************** MODEL MATCHING CRAP ***************/ + +enum { + plane_Austin, + plane_Obj, + plane_Lights, + plane_Obj8, + plane_Count +}; + +// This plane struct represents one model in one CSL packpage. +// It has a type, a single file path for whatever we have to load, +// and then implementation-specifc stuff. +struct CSLPlane_t { + int plane_type; // What kind are we? + string file_path; // Where do we load from (oz and obj, debug-use-only for OBJ8) + bool moving_gear; // Does gear retract? + + // plane_Austin + int austin_idx; + + // plane_Obj + int obj_idx; + int texID; // can be 0 for no customization + int texLitID; // can be 0 for no customization + + // plane_Obj8 + vector attachments; + +}; + +// These enums define the six levels of matching we might possibly +// make. For each level of matching, we use a single string as a key. +// (The string's contents vary with model - examples are shown.) +enum { + match_icao_airline_livery = 0, // B738 SWA SHAMU + match_icao_airline, // B738 SWA + match_group_airline_livery, // B731 B732 B733 B734 B735 B736 B737 B738 B739 SWA SHAMU + match_group_airline, // B731 B732 B733 B734 B735 B736 B737 B738 B739 SWA + match_icao, // B738 + match_group, // B731 B732 B733 B734 B735 B736 B737 B738 B739 + match_count +}; + +// A CSL package - a vector of planes and six maps from the above matching +// keys to the internal index of the plane. +struct CSLPackage_t { + + string name; + vector planes; + map matches[match_count]; + +}; + +extern vector gPackages; + +extern map gGroupings; + +extern map gPackageNames; + + +/**************** Model matching using ICAO doc 8643 + (http://www.icao.int/anb/ais/TxtFiles/Doc8643.txt) ***********/ + +struct CSLAircraftCode_t { + string icao; // aircraft ICAO code + string equip; // equipment code (L1T, L2J etc) + char category; // L, M, H, V (vertical = helo) +}; + +extern map gAircraftCodes; + +/**************** PLANE OBJECTS ********************/ + +// This plane struct reprents one instance of a +// multiplayer plane. +struct XPMPPlane_t { + + // Modeling properties + string icao; + string airline; + string livery; + CSLPlane_t * model; // May be null if no good match + bool good_livery; // is our paint correctly matched? + + // This callback is used to pull data from the client for posiitons, etc. + XPMPPlaneData_f dataFunc; + void * ref; + + // This is last known data we got for the plane, with timestamps. + int posAge; + XPMPPlanePosition_t pos; + int surfaceAge; + XPMPPlaneSurfaces_t surface; + int radarAge; + XPMPPlaneRadar_t radar; + +}; + +typedef XPMPPlane_t * XPMPPlanePtr; +typedef vector XPMPPlaneVector; + +// Notifiers - clients can install callbacks and be told when a plane's +// data changes. +typedef pair XPMPPlaneNotifierPair; +typedef pair XPMPPlaneNotifierTripple; +typedef vector XPMPPlaneNotifierVector; + +// Prefs funcs - the client provides callbacks to pull ini key values +// for various functioning. + +extern int (* gIntPrefsFunc)(const char *, const char *, int); +extern float (* gFloatPrefsFunc)(const char *, const char *, float); + +extern XPMPPlaneVector gPlanes; // All planes +extern XPMPPlaneNotifierVector gObservers; // All notifiers +extern XPMPRenderPlanes_f gRenderer; // The actual rendering func +extern void * gRendererRef; // The actual rendering func +extern int gDumpOneRenderCycle; // Debug +extern int gEnableCount; // Hack - see TCAS support + +extern string gDefaultPlane; // ICAO of default plane + +#endif diff --git a/src/xbus/libxplanemp/src/XPMPPlaneRenderer.cpp b/src/xbus/libxplanemp/src/XPMPPlaneRenderer.cpp new file mode 100644 index 000000000..6dc46c5e3 --- /dev/null +++ b/src/xbus/libxplanemp/src/XPMPPlaneRenderer.cpp @@ -0,0 +1,702 @@ +/* + * Copyright (c) 2004, Ben Supnik and Chris Serio. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XPMPPlaneRenderer.h" +#include "XPMPMultiplayer.h" +#include "XPMPMultiplayerCSL.h" +#include "XPMPMultiplayerVars.h" +#include "XPMPMultiplayerObj.h" + +#include "XPLMGraphics.h" +#include "XPLMDisplay.h" +#include "XPLMCamera.h" +#include "XPLMPlanes.h" +#include "XPLMUtilities.h" +#include "XPLMDataAccess.h" + +#include +#include + +#if IBM +#include +#elif APL +#include +#else +#include +#endif + +#include +#include +#include +#include + +// Turn this on to get a lot of diagnostic info on who's visible, etc. +#define DEBUG_RENDERER 0 +// Turn this on to put rendering stats in datarefs for realtime observatoin. +#define RENDERER_STATS 0 + +// Maximum altitude difference in feet for TCAS blips +#define MAX_TCAS_ALTDIFF 5000 + +// Even in good weather we don't want labels on things +// that we can barely see. Cut labels at 5 km. +#define MAX_LABEL_DIST 5000.0 + + +std::vector gMultiRef_X; +std::vector gMultiRef_Y; +std::vector gMultiRef_Z; + +bool gDrawLabels = true; + +struct cull_info_t { // This struct has everything we need to cull fast! + float model_view[16]; // The model view matrix, to get from local OpenGL to eye coordinates. + float proj[16]; // Proj matrix - this is just a hack to use for gluProject. + float nea_clip[4]; // Four clip planes in the form of Ax + By + Cz + D = 0 (ABCD are in the array.) + float far_clip[4]; // They are oriented so the positive side of the clip plane is INSIDE the view volume. + float lft_clip[4]; + float rgt_clip[4]; + float bot_clip[4]; + float top_clip[4]; +}; + +static void setup_cull_info(cull_info_t * i) +{ + // First, just read out the current OpenGL matrices...do this once at setup because it's not the fastest thing to do. + glGetFloatv(GL_MODELVIEW_MATRIX ,i->model_view); + glGetFloatv(GL_PROJECTION_MATRIX,i->proj); + + // Now...what the heck is this? Here's the deal: the clip planes have values in "clip" coordinates of: Left = (1,0,0,1) + // Right = (-1,0,0,1), Bottom = (0,1,0,1), etc. (Clip coordinates are coordinates from -1 to 1 in XYZ that the driver + // uses. The projection matrix converts from eye to clip coordinates.) + // + // How do we convert a plane backward from clip to eye coordinates? Well, we need the transpose of the inverse of the + // inverse of the projection matrix. (Transpose of the inverse is needed to transform a plane, and the inverse of the + // projection is the matrix that goes clip -> eye.) Well, that cancels out to the transpose of the projection matrix, + // which is nice because it means we don't need a matrix inversion in this bit of sample code. + + // So this nightmare down here is simply: + // clip plane * transpose (proj_matrix) + // worked out for all six clip planes. If you squint you can see the patterns: + // L: 1 0 0 1 + // R: -1 0 0 1 + // B: 0 1 0 1 + // T: 0 -1 0 1 + // etc. + + i->lft_clip[0] = i->proj[0]+i->proj[3]; i->lft_clip[1] = i->proj[4]+i->proj[7]; i->lft_clip[2] = i->proj[8]+i->proj[11]; i->lft_clip[3] = i->proj[12]+i->proj[15]; + i->rgt_clip[0] =-i->proj[0]+i->proj[3]; i->rgt_clip[1] =-i->proj[4]+i->proj[7]; i->rgt_clip[2] =-i->proj[8]+i->proj[11]; i->rgt_clip[3] =-i->proj[12]+i->proj[15]; + + i->bot_clip[0] = i->proj[1]+i->proj[3]; i->bot_clip[1] = i->proj[5]+i->proj[7]; i->bot_clip[2] = i->proj[9]+i->proj[11]; i->bot_clip[3] = i->proj[13]+i->proj[15]; + i->top_clip[0] =-i->proj[1]+i->proj[3]; i->top_clip[1] =-i->proj[5]+i->proj[7]; i->top_clip[2] =-i->proj[9]+i->proj[11]; i->top_clip[3] =-i->proj[13]+i->proj[15]; + + i->nea_clip[0] = i->proj[2]+i->proj[3]; i->nea_clip[1] = i->proj[6]+i->proj[7]; i->nea_clip[2] = i->proj[10]+i->proj[11]; i->nea_clip[3] = i->proj[14]+i->proj[15]; + i->far_clip[0] =-i->proj[2]+i->proj[3]; i->far_clip[1] =-i->proj[6]+i->proj[7]; i->far_clip[2] =-i->proj[10]+i->proj[11]; i->far_clip[3] =-i->proj[14]+i->proj[15]; +} + +static int sphere_is_visible(const cull_info_t * i, float x, float y, float z, float r) +{ + // First: we transform our coordinate into eye coordinates from model-view. + float xp = x * i->model_view[0] + y * i->model_view[4] + z * i->model_view[ 8] + i->model_view[12]; + float yp = x * i->model_view[1] + y * i->model_view[5] + z * i->model_view[ 9] + i->model_view[13]; + float zp = x * i->model_view[2] + y * i->model_view[6] + z * i->model_view[10] + i->model_view[14]; + + // Now - we apply the "plane equation" of each clip plane to see how far from the clip plane our point is. + // The clip planes are directed: positive number distances mean we are INSIDE our viewing area by some distance; + // negative means outside. So ... if we are outside by less than -r, the ENTIRE sphere is out of bounds. + // We are not visible! We do the near clip plane, then sides, then far, in an attempt to try the planes + // that will eliminate the most geometry first...half the world is behind the near clip plane, but not much is + // behind the far clip plane on sunny day. + if ((xp * i->nea_clip[0] + yp * i->nea_clip[1] + zp * i->nea_clip[2] + i->nea_clip[3] + r) < 0) return false; + if ((xp * i->bot_clip[0] + yp * i->bot_clip[1] + zp * i->bot_clip[2] + i->bot_clip[3] + r) < 0) return false; + if ((xp * i->top_clip[0] + yp * i->top_clip[1] + zp * i->top_clip[2] + i->top_clip[3] + r) < 0) return false; + if ((xp * i->lft_clip[0] + yp * i->lft_clip[1] + zp * i->lft_clip[2] + i->lft_clip[3] + r) < 0) return false; + if ((xp * i->rgt_clip[0] + yp * i->rgt_clip[1] + zp * i->rgt_clip[2] + i->rgt_clip[3] + r) < 0) return false; + if ((xp * i->far_clip[0] + yp * i->far_clip[1] + zp * i->far_clip[2] + i->far_clip[3] + r) < 0) return false; + return true; +} + +static float sphere_distance_sqr(const cull_info_t * i, float x, float y, float z) +{ + float xp = x * i->model_view[0] + y * i->model_view[4] + z * i->model_view[ 8] + i->model_view[12]; + float yp = x * i->model_view[1] + y * i->model_view[5] + z * i->model_view[ 9] + i->model_view[13]; + float zp = x * i->model_view[2] + y * i->model_view[6] + z * i->model_view[10] + i->model_view[14]; + return xp*xp+yp*yp+zp*zp; +} + +static void convert_to_2d(const cull_info_t * i, const float * vp, float x, float y, float z, float w, float * out_x, float * out_y) +{ + float xe = x * i->model_view[0] + y * i->model_view[4] + z * i->model_view[ 8] + w * i->model_view[12]; + float ye = x * i->model_view[1] + y * i->model_view[5] + z * i->model_view[ 9] + w * i->model_view[13]; + float ze = x * i->model_view[2] + y * i->model_view[6] + z * i->model_view[10] + w * i->model_view[14]; + float we = x * i->model_view[3] + y * i->model_view[7] + z * i->model_view[11] + w * i->model_view[15]; + + float xc = xe * i->proj[0] + ye * i->proj[4] + ze * i->proj[ 8] + we * i->proj[12]; + float yc = xe * i->proj[1] + ye * i->proj[5] + ze * i->proj[ 9] + we * i->proj[13]; +// float zc = xe * i->proj[2] + ye * i->proj[6] + ze * i->proj[10] + we * i->proj[14]; + float wc = xe * i->proj[3] + ye * i->proj[7] + ze * i->proj[11] + we * i->proj[15]; + + xc /= wc; + yc /= wc; +// zc /= wc; + + *out_x = vp[0] + (1.0f + xc) * vp[2] / 2.0f; + *out_y = vp[1] + (1.0f + yc) * vp[3] / 2.0f; +} + + +#if RENDERER_STATS + +static int GetRendererStat(void * inRefcon) +{ + return *((int *) inRefcon); +} + +#endif + +static int gTotPlanes = 0; // Counters +static int gACFPlanes = 0; // Number of Austin's planes we drew in full +static int gNavPlanes = 0; // Number of Austin's planes we drew with lights only +static int gOBJPlanes = 0; // Number of our OBJ planes we drew in full + +static XPLMDataRef gVisDataRef = NULL; // Current air visiblity for culling. +static XPLMDataRef gAltitudeRef = NULL; // Current aircraft altitude (for TCAS) + + +void XPMPInitDefaultPlaneRenderer(void) +{ + // SETUP - mostly just fetch datarefs. + + gVisDataRef = XPLMFindDataRef("sim/graphics/view/visibility_effective_m"); + if (gVisDataRef == NULL) gVisDataRef = XPLMFindDataRef("sim/weather/visibility_effective_m"); + if (gVisDataRef == NULL) + XPLMDebugString("WARNING: Default renderer could not find effective visibility in the sim.\n"); + + if(gAltitudeRef == NULL) gAltitudeRef = XPLMFindDataRef("sim/flightmodel/position/elevation"); + +#if RENDERER_STATS + XPLMRegisterDataAccessor("hack/renderer/planes", xplmType_Int, 0, GetRendererStat, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &gTotPlanes, NULL); + XPLMRegisterDataAccessor("hack/renderer/navlites", xplmType_Int, 0, GetRendererStat, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &gNavPlanes, NULL); + XPLMRegisterDataAccessor("hack/renderer/objects", xplmType_Int, 0, GetRendererStat, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &gOBJPlanes, NULL); + XPLMRegisterDataAccessor("hack/renderer/acfs", xplmType_Int, 0, GetRendererStat, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &gACFPlanes, NULL); +#endif + + // We don't know how many multiplayer planes there are - fetch as many as we can. + + int n = 1; + char buf[100]; + XPLMDataRef d; + while (1) + { + sprintf(buf,"sim/multiplayer/position/plane%d_x", n); + d = XPLMFindDataRef(buf); + if (!d) break; + gMultiRef_X.push_back(d); + sprintf(buf,"sim/multiplayer/position/plane%d_y", n); + d = XPLMFindDataRef(buf); + if (!d) break; + gMultiRef_Y.push_back(d); + sprintf(buf,"sim/multiplayer/position/plane%d_z", n); + d = XPLMFindDataRef(buf); + if (!d) break; + gMultiRef_Z.push_back(d); + ++n; + } +} + +// PlaneToRender struct: we prioritize planes radially by distance, so... +// we use this struct to remember one visible plane. Once we've +// found all visible planes, we draw the closest ones. + +struct PlaneToRender_t { + float x; // Positional info + float y; + float z; + float pitch; + float heading; + float roll; + CSLPlane_t * model; // What model are we? + bool full; // Do we need to draw the full plane or just lites? + bool cull; // Are we visible on screen? + bool tcas; // Are we visible on TCAS? + XPLMPlaneDrawState_t state; // Flaps, gear, etc. + float dist; + xpmp_LightStatus lights; // lights status + string label; + +}; +typedef std::map RenderMap; + + +void XPMPDefaultPlaneRenderer(int is_blend) +{ + long planeCount = XPMPCountPlanes(); +#if DEBUG_RENDERER + char buf[50]; + sprintf(buf,"Renderer Planes: %d\n", planeCount); + XPLMDebugString(buf); +#endif + if (planeCount == 0) // Quick exit if no one's around. + { + if (gDumpOneRenderCycle) + { + gDumpOneRenderCycle = false; + XPLMDebugString("No planes this cycle.\n"); + } + return; + } + + cull_info_t gl_camera; + setup_cull_info(&gl_camera); + XPLMCameraPosition_t x_camera; + + XPLMReadCameraPosition(&x_camera); // only for zoom! + + // Culling - read the camera posŤand figure out what's visible. + + double maxDist = XPLMGetDataf(gVisDataRef); + double labelDist = min(maxDist, MAX_LABEL_DIST) * x_camera.zoom; // Labels get easier to see when users zooms. + double fullPlaneDist = x_camera.zoom * (5280.0 / 3.2) * (gFloatPrefsFunc ? gFloatPrefsFunc("planes","full_distance", 3.0) : 3.0); // Only draw planes fully within 3 miles. + int maxFullPlanes = gIntPrefsFunc ? gIntPrefsFunc("planes","max_full_count", 100) : 100; // Draw no more than 100 full planes! + + gTotPlanes = planeCount; + gNavPlanes = gACFPlanes = gOBJPlanes = 0; + + int modelCount, active, plugin, tcas; + XPLMCountAircraft(&modelCount, &active, &plugin); + tcas = modelCount - 1; // This is how many tcas blips we can have! + + RenderMap myPlanes; // Planes - sorted by distance so we can do the closest N and bail + + /************************************************************************************ + * CULLING AND STATE CALCULATION LOOP + ************************************************************************************/ + + if (gDumpOneRenderCycle) + { + XPLMDebugString("Dumping one cycle map of planes.\n"); + char fname[256], bigbuf[1024], foo[32]; + for (int n = 1; n < modelCount; ++n) + { + XPLMGetNthAircraftModel(n, fname, bigbuf); + sprintf(foo, " [%d] - ", n); + XPLMDebugString(foo); + XPLMDebugString(fname); + XPLMDebugString(" - "); + XPLMDebugString(bigbuf); + XPLMDebugString("\n"); + } + } + + // Go through every plane. We're going to figure out if it is visible and if so remember it for drawing later. + for (long index = 0; index < planeCount; ++index) + { + XPMPPlaneID id = XPMPGetNthPlane(index); + + XPMPPlanePosition_t pos; + pos.size = sizeof(pos); + pos.label[0] = 0; + + if (XPMPGetPlaneData(id, xpmpDataType_Position, &pos) != xpmpData_Unavailable) + { + // First figure out where the plane is! + + double x,y,z; + XPLMWorldToLocal(pos.lat, pos.lon, pos.elevation * kFtToMeters, &x, &y, &z); + + float distMeters = sqrt(sphere_distance_sqr(&gl_camera, + static_cast(x), + static_cast(y), + static_cast(z))); + + // If the plane is farther than our TCAS range, it's just not visible. Drop it! + if (distMeters > kMaxDistTCAS) + continue; + + // Only draw if it's in range. + bool cull = (distMeters > maxDist); + + XPMPPlaneRadar_t radar; + radar.size = sizeof(radar); + bool tcas = true; + if (XPMPGetPlaneData(id, xpmpDataType_Radar, &radar) != xpmpData_Unavailable) + if (radar.mode == xpmpTransponderMode_Standby) + tcas = false; + + // check for altitude - if difference exceeds 3000ft, don't show + double acft_alt = XPLMGetDatad(gAltitudeRef) / kFtToMeters; + double alt_diff = pos.elevation - acft_alt; + if(alt_diff < 0) alt_diff *= -1; + if(alt_diff > MAX_TCAS_ALTDIFF) tcas = false; + + // Calculate the heading from the camera to the target (hor, vert). + // Calculate the angles between the camera angles and the real angles. + // Cull if we exceed half the FOV. + + if(!cull && !sphere_is_visible(&gl_camera, static_cast(x), + static_cast(y), + static_cast(z), 50.0)) + { + cull = true; + } + + // Full plane or lites based on distance. + bool drawFullPlane = (distMeters < fullPlaneDist); + +#if DEBUG_RENDERER + + char icao[128], livery[128]; + char debug[512]; + + XPMPGetPlaneICAOAndLivery(id, icao, livery); + sprintf(debug,"Queueing plane %d (%s/%s) at lle %f, %f, %f (xyz=%f, %f, %f) pitch=%f,roll=%f,heading=%f,model=1.\n", index, icao, livery, + pos.lat, pos.lon, pos.elevation, + x, y, z, pos.pitch, pos.roll, pos.heading); + XPLMDebugString(debug); +#endif + + // Stash one render record with the plane's position, etc. + { + PlaneToRender_t renderRecord; + renderRecord.x = static_cast(x); + renderRecord.y = static_cast(y); + renderRecord.z = static_cast(z); + renderRecord.pitch = pos.pitch; + renderRecord.heading = pos.heading; + renderRecord.roll = pos.roll; + renderRecord.model=((XPMPPlanePtr)id)->model; + renderRecord.cull = cull; // NO other planes. Doing so causes a lot of things to go nuts! + renderRecord.tcas = tcas; + + XPMPPlaneSurfaces_t surfaces; + surfaces.size = sizeof(surfaces); + if (XPMPGetPlaneData(id, xpmpDataType_Surfaces, &surfaces) != xpmpData_Unavailable) + { + renderRecord.state.structSize = sizeof(renderRecord.state); + renderRecord.state.gearPosition = surfaces.gearPosition ; + renderRecord.state.flapRatio = surfaces.flapRatio ; + renderRecord.state.spoilerRatio = surfaces.spoilerRatio ; + renderRecord.state.speedBrakeRatio = surfaces.speedBrakeRatio ; + renderRecord.state.slatRatio = surfaces.slatRatio ; + renderRecord.state.wingSweep = surfaces.wingSweep ; + renderRecord.state.thrust = surfaces.thrust ; + renderRecord.state.yokePitch = surfaces.yokePitch ; + renderRecord.state.yokeHeading = surfaces.yokeHeading ; + renderRecord.state.yokeRoll = surfaces.yokeRoll ; + + renderRecord.lights.lightFlags = surfaces.lights.lightFlags; + + } else { + renderRecord.state.structSize = sizeof(renderRecord.state); + renderRecord.state.gearPosition = (pos.elevation < 70) ? 1.0f : 0.0f; + renderRecord.state.flapRatio = (pos.elevation < 70) ? 1.0f : 0.0f; + renderRecord.state.spoilerRatio = renderRecord.state.speedBrakeRatio = renderRecord.state.slatRatio = renderRecord.state.wingSweep = 0.0; + renderRecord.state.thrust = (pos.pitch > 30) ? 1.0f : 0.6f; + renderRecord.state.yokePitch = pos.pitch / 90.0f; + renderRecord.state.yokeHeading = pos.heading / 180.0f; + renderRecord.state.yokeRoll = pos.roll / 90.0f; + + // use some smart defaults + renderRecord.lights.bcnLights = 1; + renderRecord.lights.navLights = 1; + + } + if (renderRecord.model && !renderRecord.model->moving_gear) + renderRecord.state.gearPosition = 1.0; + renderRecord.full = drawFullPlane; + renderRecord.dist = distMeters; + renderRecord.label = pos.label; + + myPlanes.insert(RenderMap::value_type(distMeters, renderRecord)); + + } // State calculation + + } // Plane has data available + + } // Per-plane loop + + if (gDumpOneRenderCycle) + XPLMDebugString("End of cycle dump.\n"); + + /************************************************************************************ + * ACTUAL RENDERING LOOP + ************************************************************************************/ + + // We're going to go in and render the first N planes in full, and the rest as lites. + // We're also going to put the x-plane multiplayer vars in place for the first N + // TCAS-visible planes, so they show up on our moving map. + // We do this in two stages: building up what to do, then doing it in the optimal + // OGL order. + + size_t renderedCounter = 0; + + vector planes_obj_lites; + multimap planes_austin; + multimap planes_obj; + vector planes_obj8; + + vector::iterator planeIter; + multimap::iterator planeMapIter; + + // In our first iteration pass we'll go through all planes and handle TCAS, draw planes that have no + // CSL model, and put CSL planes in the right 'bucket'. + + for (RenderMap::iterator iter = myPlanes.begin(); iter != myPlanes.end(); ++iter) + { + // This is the case where we draw a real plane. + if (!iter->second.cull) + { + // Max plane enforcement - once we run out of the max number of full planes the + // user allows, force only lites for framerate + if (gACFPlanes >= maxFullPlanes) + iter->second.full = false; + +#if DEBUG_RENDERER + char debug[512]; + sprintf(debug,"Drawing plane: %s at %f,%f,%f (%fx%fx%f full=%d\n", + iter->second.model ? iter->second.model->file_path.c_str() : "", iter->second.x, iter->second.y, iter->second.z, + iter->second.pitch, iter->second.roll, iter->second.heading, iter->second.full ? 1 : 0); + XPLMDebugString(debug); +#endif + + if (iter->second.model) + { + if (iter->second.model->plane_type == plane_Austin) + { + planes_austin.insert(multimap::value_type(CSL_GetOGLIndex(iter->second.model), &iter->second)); + } + else if (iter->second.model->plane_type == plane_Obj) + { + planes_obj.insert(multimap::value_type(CSL_GetOGLIndex(iter->second.model), &iter->second)); + planes_obj_lites.push_back(&iter->second); + } + else if(iter->second.model->plane_type == plane_Obj8) + { + planes_obj8.push_back(&iter->second); + } + + } else { + // If it's time to draw austin's planes but this one + // doesn't have a model, we draw anything. + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(iter->second.x, iter->second.y, iter->second.z); + glRotatef(iter->second.heading, 0.0, -1.0, 0.0); + glRotatef(iter->second.pitch, 01.0, 0.0, 0.0); + glRotatef(iter->second.roll, 0.0, 0.0, -1.0); + + // Safety check - if plane 1 isn't even loaded do NOT draw, do NOT draw plane 0. + // Using the user's planes can cause the internal flight model to get f-cked up. + // Using a non-loaded plane can trigger internal asserts in x-plane. + if (modelCount > 1) + if(!is_blend) + XPLMDrawAircraft(1, + (float) iter->second.x, (float) iter->second.y, (float) iter->second.z, + iter->second.pitch, iter->second.roll, iter->second.heading, + iter->second.full ? 1 : 0, &iter->second.state); + + glPopMatrix(); + } + + } + + // TCAS handling - if the plane needs to be drawn on TCAS and we haven't yet, move one of Austin's planes. + if (iter->second.tcas && renderedCounter < gMultiRef_X.size()) + { + XPLMSetDataf(gMultiRef_X[renderedCounter], iter->second.x); + XPLMSetDataf(gMultiRef_Y[renderedCounter], iter->second.y); + XPLMSetDataf(gMultiRef_Z[renderedCounter], iter->second.z); + ++renderedCounter; + } + } + + // PASS 1 - draw Austin's planes. + + if(!is_blend) + for (planeMapIter = planes_austin.begin(); planeMapIter != planes_austin.end(); ++planeMapIter) + { + CSL_DrawObject( planeMapIter->second->model, + planeMapIter->second->dist, + planeMapIter->second->x, + planeMapIter->second->y, + planeMapIter->second->z, + planeMapIter->second->pitch, + planeMapIter->second->roll, + planeMapIter->second->heading, + plane_Austin, + planeMapIter->second->full ? 1 : 0, + planeMapIter->second->lights, + &planeMapIter->second->state); + + if (planeMapIter->second->full) + ++gACFPlanes; + else + ++gNavPlanes; + } + + // PASS 2 - draw OBJs + // Blend for solid OBJ7s? YES! First, in HDR mode, they DO NOT draw to the gbuffer properly - + // they splat their livery into the normal map, which is terrifying and stupid. Then they are also + // pre-lit...the net result is surprisingly not much worse than regular rendering considering how many + // bad things have happened, but for all I know we're getting NaNs somewhere. + // + // Blending isn't going to hurt things in NON-HDR because our rendering is so stupid for old objs - there's + // pretty much never translucency so we aren't going to get Z-order fails. So f--- it...always draw blend.< + if(is_blend) + for (planeMapIter = planes_obj.begin(); planeMapIter != planes_obj.end(); ++planeMapIter) + { + CSL_DrawObject( + planeMapIter->second->model, + planeMapIter->second->dist, + planeMapIter->second->x, + planeMapIter->second->y, + planeMapIter->second->z, + planeMapIter->second->pitch, + planeMapIter->second->roll, + planeMapIter->second->heading, + plane_Obj, + planeMapIter->second->full ? 1 : 0, + planeMapIter->second->lights, + &planeMapIter->second->state); + ++gOBJPlanes; + } + + for(planeIter = planes_obj8.begin(); planeIter != planes_obj8.end(); ++planeIter) + { + CSL_DrawObject( (*planeIter)->model, + (*planeIter)->dist, + (*planeIter)->x, + (*planeIter)->y, + (*planeIter)->z, + (*planeIter)->pitch, + (*planeIter)->roll, + (*planeIter)->heading, + plane_Obj8, + (*planeIter)->full ? 1 : 0, + (*planeIter)->lights, + &(*planeIter)->state); + } + + if(!is_blend) + obj_draw_solid(); + + // PASS 3 - draw OBJ lights. + + if(is_blend) + if (!planes_obj_lites.empty()) + { + OBJ_BeginLightDrawing(); + for (planeIter = planes_obj_lites.begin(); planeIter != planes_obj_lites.end(); ++planeIter) + { + // this thing draws the lights of a model + CSL_DrawObject( (*planeIter)->model, + (*planeIter)->dist, + (*planeIter)->x, + (*planeIter)->y, + (*planeIter)->z, + (*planeIter)->pitch, + (*planeIter)->roll, + (*planeIter)->heading, + plane_Lights, + (*planeIter)->full ? 1 : 0, + (*planeIter)->lights, + &(*planeIter)->state); + } + } + + if(is_blend) + obj_draw_translucent(); + obj_draw_done(); + + // PASS 4 - Labels + if(is_blend) + if ( gDrawLabels ) + { + GLfloat vp[4]; + glGetFloatv(GL_VIEWPORT,vp); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vp[2], 0, vp[3], -1, 1); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + float c[4] = { 1, 1, 0, 1 }; + + + for (RenderMap::iterator iter = myPlanes.begin(); iter != myPlanes.end(); ++iter) + if(iter->first < labelDist) + if(!iter->second.cull) // IMPORTANT - airplane BEHIND us still maps XY onto screen...so we get 180 degree reflections. But behind us acf are culled, so that's good. + { + float x, y; + convert_to_2d(&gl_camera, vp, iter->second.x, iter->second.y, iter->second.z, 1.0, &x, &y); + + float rat = 1.0f - (iter->first / static_cast(labelDist)); + c[0] = c[1] = 0.5f + 0.5f * rat; + c[2] = 0.5f - 0.5f * rat; // gray -> yellow - no alpha in the SDK - foo! + + XPLMDrawString(c, static_cast(x), static_cast(y)+10, (char *) iter->second.label.c_str(), NULL, xplmFont_Basic); + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + } + + + // Final hack - leave a note to ourselves for how many of Austin's planes we relocated to do TCAS. + if (tcas > static_cast(renderedCounter)) + tcas = static_cast(renderedCounter); + gEnableCount = (tcas+1); + + gDumpOneRenderCycle = 0; +} + +void XPMPEnableAircraftLabels() +{ + gDrawLabels = true; +} + +void XPMPDisableAircraftLabels() +{ + gDrawLabels = false; +} + +bool XPMPDrawingAircraftLabels() +{ + return gDrawLabels; +} + diff --git a/src/xbus/libxplanemp/src/XUtils.cpp b/src/xbus/libxplanemp/src/XUtils.cpp new file mode 100644 index 000000000..e56f25dbb --- /dev/null +++ b/src/xbus/libxplanemp/src/XUtils.cpp @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "XUtils.h" +#include +#include +#include "XObjDefs.h" +#include +//#include "PlatformUtils.h" +//#include +#include +#include +#include + +#if !defined(XUTILS_EXCLUDE_MAC_CRAP) && defined(__MACH__) +#define XUTILS_EXCLUDE_MAC_CRAP 1 +#endif + +#if APL +//using namespace Metrowerks; +#endif + +static char * my_fgets(char * s, int n, FILE * file) +{ + char * p = s; + int c; + + if (--n < 0) + return(NULL); + + if (n) + do + { + c = fgetc(file); + + if (c == EOF) + { + if (/*feof(file) &&*/ p != s) + break; + else + { + return(NULL); + } + } + + *p++ = c; + } + while (c != '\n' && c != '\r' && --n); + + *p = 0; + + return(s); +} + + + +StTextFileScanner::StTextFileScanner(const char * file, bool skip) : + mFile(fopen(file, "r")), + mDone(false), + mSkipBlanks(skip) +{ + read_next(); +} + +StTextFileScanner::~StTextFileScanner() +{ + if (mFile) + fclose(mFile); +} + +bool StTextFileScanner::done() +{ + return mDone; +} + +void StTextFileScanner::next() +{ + read_next(); +} + +string StTextFileScanner::get() +{ + return mBuf; +} + +void StTextFileScanner::read_next(void) +{ + mBuf.clear(); + mDone = true; + + char buf[4096]; + + while (mFile && /*!feof(mFile) &&*/ my_fgets(buf, sizeof(buf), mFile)) + { + int len = static_cast(strlen(buf)); + while ((len > 0) && (buf[len-1] == '\r' || buf[len-1] == '\n')) + { + buf[len-1] = 0; + --len; + } + + if (buf[0] == '\r' || buf[0] == '\n') + mBuf = buf+1; + else + mBuf = buf; + + if (!mBuf.empty() || !mSkipBlanks) + { + mDone = false; + return; + } + } +} + +void BreakString(const string& line, vector& words) +{ + words.clear(); + string::const_iterator i = line.begin(); + while (i < line.end()) + { + string::const_iterator s = i; + while (s < line.end() && isspace(*s)) + ++s; + + string::const_iterator e = s; + while (e < line.end() && !isspace(*e)) + ++e; + + if (s < e) + words.push_back(string(s,e)); + + i = e; + } +} + +void StringToUpper(string& s) +{ + for (string::iterator i = s.begin(); i != s.end(); ++i) + *i = toupper(*i); +} + +bool HasExtNoCase(const string& inStr, const char * inExt) +{ + string s(inStr); + string e(inExt); + StringToUpper(s); + StringToUpper(e); + + + if (s.rfind(e) == (s.length() - e.length())) + return true; + return false; +} + +void ChangePolyCmdCW(XObjCmd& ioCmd) +{ + vector v; + for (vector::reverse_iterator riter = ioCmd.st.rbegin(); + riter != ioCmd.st.rend(); ++riter) + { + v.push_back(*riter); + } + ioCmd.st = v; +} + +bool GetNextNoComments(StTextFileScanner& f, string& s) +{ + while(!f.done()) + { + s = f.get(); + f.next(); + if (s.empty() || s[0] != '#') + return true; + } + return false; +} + +double GetObjRadius(const XObj& inObj) +{ + double dist = 0, d; + for (vector::const_iterator c = inObj.cmds.begin(); + c != inObj.cmds.end(); ++c) + { + for (vector::const_iterator v = c->st.begin(); + v != c->st.end(); ++v) + { + d = sqrt(v->v[0] * v->v[0] + + v->v[1] * v->v[1] + + v->v[2] * v->v[2]); + if (d > dist) dist = d; + } + + for (vector::const_iterator p = c->rgb.begin(); + p != c->rgb.end(); ++p) + { + d = sqrt(p->v[0] * p->v[0] + + p->v[1] * p->v[1] + + p->v[2] * p->v[2]); + if (d > dist) dist = d; + } + } + return dist; +} + +int PickRandom(vector& chances) +{ + double v = (double) (rand() % RAND_MAX) / (double) RAND_MAX; + + for (size_t n = 0; n < chances.size(); ++n) + { + if (v < chances[n]) + return static_cast(n); + v -= chances[n]; + } + return static_cast(chances.size()); +} + +bool RollDice(double inProb) +{ + if (inProb <= 0.0) return false; + if (inProb >= 1.0) return true; + double v = (double) (rand() % RAND_MAX) / (double) RAND_MAX; + return v < inProb; +} + +double RandRange(double mmin, double mmax) +{ + if (mmin >= mmax) + return mmin; + double v = (double) rand() / (double) RAND_MAX; + return mmin + ((mmax - mmin) * v); +} + +double RandRangeBias(double mmin, double mmax, double biasRatio, double randomAmount) +{ + double span = mmax - mmin; + double lower_rat = biasRatio * (1.0 - randomAmount); + double upper_rat = lower_rat + randomAmount; + return RandRange(mmin + span * lower_rat,mmin + span * upper_rat); +} + +#if 0 +void StripPath(string& ioPath) +{ + string::size_type sep = ioPath.rfind(DIR_CHAR); + if (sep != ioPath.npos) + ioPath = ioPath.substr(sep+1,ioPath.npos); +} + +void StripPathCP(string& ioPath) +{ + string::size_type sep; + sep = ioPath.rfind(':'); + if (sep != ioPath.npos) + ioPath = ioPath.substr(sep+1,ioPath.npos); + sep = ioPath.rfind('\\'); + if (sep != ioPath.npos) + ioPath = ioPath.substr(sep+1,ioPath.npos); +} + +void ExtractPath(string& ioPath) +{ + string::size_type sep = ioPath.rfind(DIR_CHAR); + if (sep != ioPath.npos) + ioPath = ioPath.substr(0, sep); +} +#endif +#if APL + +#if !defined(XUTILS_EXCLUDE_MAC_CRAP) + +#include + +OSErr FindSuperFolder(const FSSpec& inItem, FSSpec& outFolder) +{ + CInfoPBRec paramBlock; + OSErr err; + + paramBlock.dirInfo.ioCompletion = NULL; + paramBlock.dirInfo.ioNamePtr = (StringPtr) (&(outFolder.name)); + paramBlock.dirInfo.ioVRefNum = inItem.vRefNum; + paramBlock.dirInfo.ioFDirIndex = -1; + paramBlock.dirInfo.ioDrDirID = inItem.parID; + + err = ::PBGetCatInfoSync(¶mBlock); + if (err != noErr) + return err; + + outFolder.vRefNum = paramBlock.dirInfo.ioVRefNum; + outFolder.parID= paramBlock.dirInfo.ioDrParID; + return noErr; +} + +void AppPath(string& outString) +{ + ProcessSerialNumber psn = { 0, kCurrentProcess }; + ProcessInfoRec info = { 0 }; + FSSpec spec; + Str255 name; + info.processInfoLength = sizeof(info); + info.processAppSpec = &spec; + info.processName = name; + GetProcessInformation(&psn, &info); + FSSpec_2_String(spec, outString); +} + +void FSSpec_2_String(const FSSpec& inSpec, string& outString) +{ + outString.clear(); + + FSSpec foo(inSpec); + FSSpec foo2; + + while (1) + { + if (outString.empty()) + outString = std::string(foo.name+1, foo.name+foo.name[0]+1); + else + outString = std::string(foo.name+1, foo.name+foo.name[0]+1) + std::string(":") + outString; + if (FindSuperFolder(foo, foo2) != noErr) + break; + foo = foo2; + } +} + +#endif +#endif + +void ExtractFixedRecordString( + const string& inLine, + int inBegin, + int inEnd, + string& outString) +{ + int sp = inBegin-1; + int ep = inEnd; + if (ep > static_cast(inLine.length())) ep = static_cast(inLine.length()); + if (sp > static_cast(inLine.length())) sp = static_cast(inLine.length()); + + while ((sp < ep) && (inLine[sp] == ' ')) + ++sp; + + while ((ep > sp) && (inLine[ep-1] == ' ')) + --ep; + + outString = inLine.substr(sp, ep - sp); +} + +bool ExtractFixedRecordLong( + const string& inLine, + int inBegin, + int inEnd, + long& outLong) +{ + string foo; + ExtractFixedRecordString(inLine, inBegin, inEnd, foo); + if (foo.empty()) return false; + outLong = strtol(foo.c_str(), NULL, 10); + return true; +} + +bool ExtractFixedRecordUnsignedLong( + const string& inLine, + int inBegin, + int inEnd, + unsigned long& outUnsignedLong) +{ + string foo; + ExtractFixedRecordString(inLine, inBegin, inEnd, foo); + if (foo.empty()) return false; + outUnsignedLong = strtoul(foo.c_str(), NULL, 10); + return true; +} + +//#pragma mark - + +struct XPointPool::XPointPoolImp { + + struct p_info { + float xyz[3]; + float st[2]; + }; + vector pts; + map index; + + void clear() + { + pts.clear(); + index.clear(); + } + + int count(void) + { + return static_cast(pts.size()); + } + + int accumulate(const float xyz[3], const float st[2]) + { + static char buf[256]; + sprintf(buf,"%08X%08X%08X|%08x%08x", + *(reinterpret_cast(xyz+0)), + *(reinterpret_cast(xyz+1)), + *(reinterpret_cast(xyz+2)), + *(reinterpret_cast(st +0)), + *(reinterpret_cast(st +1))); + string key(buf); + map::iterator i = index.find(key); + if (i != index.end()) return i->second; + p_info p; + memcpy(p.xyz, xyz, sizeof(p.xyz)); + memcpy(p.st, st, sizeof(p.st)); + pts.push_back(p); + index.insert(map::value_type(key, static_cast(pts.size()))); + pts.push_back(p); + return static_cast(pts.size())-1; + } + + void get(int i, float xyz[3], float st[2]) + { + p_info& p = pts[i]; + memcpy(xyz,p.xyz,sizeof(p.xyz)); + memcpy(st,p.st,sizeof(p.st)); + } +}; + +XPointPool::XPointPool() +{ + mImp = new XPointPoolImp; +} + +XPointPool::~XPointPool() +{ + delete mImp; +} + +void XPointPool::clear() +{ + mImp->clear(); +} + +int XPointPool::accumulate(const float xyz[3], const float st[2]) +{ + return mImp->accumulate(xyz, st); +} + +void XPointPool::get(int index, float xyz[3], float st[2]) +{ + mImp->get(index,xyz,st); +} + +int XPointPool::count(void) +{ + return mImp->count(); +} + +void DecomposeObjCmd(const XObjCmd& inCmd, vector& outCmds, int maxValence) +{ + XObjCmd c; + c.cmdType = type_Poly; + c.cmdID = obj_Tri; + switch(inCmd.cmdID) { + case obj_Tri: + // Triangles never need breaking down. + outCmds.push_back(inCmd); + break; + case obj_Quad: + case obj_Quad_Hard: + case obj_Smoke_Black: + case obj_Smoke_White: + case obj_Movie: + // Quads - split into triangles if necessary. + if (maxValence > 3) { + outCmds.push_back(inCmd); + outCmds.back().cmdID = obj_Quad; + } else { + outCmds.push_back(inCmd); + outCmds.back().cmdID = obj_Tri; + outCmds.back().st.erase(outCmds.back().st.begin()+3); + outCmds.push_back(inCmd); + outCmds.back().cmdID = obj_Tri; + outCmds.back().st.erase(outCmds.back().st.begin()+1); + } + break; + case obj_Polygon: + // Polygons might be ok. But if we have to break them down, + // we generate N-2 triangles in a fan configuration. + if (maxValence < static_cast(inCmd.st.size())) + { + c.st.push_back(inCmd.st[0]); + c.st.push_back(inCmd.st[1]); + c.st.push_back(inCmd.st[2]); + for (size_t n = 2; n < inCmd.st.size(); ++n) + { + c.st[1] = inCmd.st[n-1]; + c.st[2] = inCmd.st[n ]; + outCmds.push_back(c); + } + } else + outCmds.push_back(inCmd); + break; + case obj_Tri_Strip: + // Triangle strips - every other triangle's vertices + // are backward! + c.st.push_back(inCmd.st[0]); + c.st.push_back(inCmd.st[1]); + c.st.push_back(inCmd.st[2]); + for (size_t n = 2; n < inCmd.st.size(); ++n) + { + if (n%2) + { + c.st[0] = inCmd.st[n-2]; + c.st[1] = inCmd.st[n ]; + c.st[2] = inCmd.st[n-1]; + outCmds.push_back(c); + } else { + c.st[0] = inCmd.st[n-2]; + c.st[1] = inCmd.st[n-1]; + c.st[2] = inCmd.st[n ]; + outCmds.push_back(c); + } + } + break; + case obj_Tri_Fan: + // Tri fan - run around the triangle fan emitting triangles. + c.st.push_back(inCmd.st[0]); + c.st.push_back(inCmd.st[1]); + c.st.push_back(inCmd.st[2]); + for (size_t n = 2; n < inCmd.st.size(); ++n) + { + c.st[1] = inCmd.st[n-1]; + c.st[2] = inCmd.st[n ]; + outCmds.push_back(c); + } + break; + case obj_Quad_Strip: + // Quad strips can become either quads or triangles!! + if (maxValence > 3) + { + c.cmdID = obj_Quad; + c.st.push_back(inCmd.st[0]); + c.st.push_back(inCmd.st[1]); + c.st.push_back(inCmd.st[2]); + c.st.push_back(inCmd.st[3]); + for (size_t n = 2; n < inCmd.st.size(); n += 2) + { + c.st[0] = inCmd.st[n-2]; + c.st[1] = inCmd.st[n-1]; + c.st[2] = inCmd.st[n+1]; + c.st[3] = inCmd.st[n ]; + outCmds.push_back(c); + } + } else { + c.st.push_back(inCmd.st[0]); + c.st.push_back(inCmd.st[1]); + c.st.push_back(inCmd.st[2]); + for (size_t n = 2; n < inCmd.st.size(); ++n) + { + if (n%2) + { + c.st[0] = inCmd.st[n-2]; + c.st[1] = inCmd.st[n ]; + c.st[2] = inCmd.st[n-1]; + outCmds.push_back(c); + } else { + c.st[0] = inCmd.st[n-2]; + c.st[1] = inCmd.st[n-1]; + c.st[2] = inCmd.st[n ]; + outCmds.push_back(c); + } + } + } + break; + default: + outCmds.push_back(inCmd); + } +} + +void DecomposeObj(const XObj& inObj, XObj& outObj, int maxValence) +{ + outObj.cmds.clear(); + outObj.texture = inObj.texture; + for (vector::const_iterator cmd = inObj.cmds.begin(); + cmd != inObj.cmds.end(); ++cmd) + { + vector newCmds; + DecomposeObjCmd(*cmd, newCmds, maxValence); + outObj.cmds.insert(outObj.cmds.end(), newCmds.begin(), newCmds.end()); + } +} diff --git a/src/xbus/libxplanemp/src/XUtils.h b/src/xbus/libxplanemp/src/XUtils.h new file mode 100644 index 000000000..843e98d02 --- /dev/null +++ b/src/xbus/libxplanemp/src/XUtils.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2004, Laminar Research. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS 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. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef XUTILS_H +#define XUTILS_H + +struct XObjCmd; +struct XObj; + +#include +#include +using namespace std; + +class StTextFileScanner { +public: + + StTextFileScanner(const char * inFileName, bool skip_blanks); + ~StTextFileScanner(); + + void skip_blanks(bool skip_blanks); + bool done(); + void next(); + string get(); + +private: + + void read_next(void); + + FILE * mFile; + string mBuf; + bool mDone; + bool mSkipBlanks; +}; + +void BreakString(const string& line, vector& words); + +void StringToUpper(string&); + +bool HasExtNoCase(const string& inStr, const char * inExt); + +void ChangePolyCmdCW(XObjCmd& ioCmd); + +bool GetNextNoComments(StTextFileScanner& f, string& s); + +// WARNING: this is a dumb radius, a radius from 0,0,0. It is not +// the radius of a bounding sphere! Why it is in this translation +// unit is also rather questionable. +double GetObjRadius(const XObj& inObj); + +//void StripPath(string& ioPath); +//void StripPathCP(string& ioPath); +//void ExtractPath(string& ioPath); + +int PickRandom(vector& chances); +bool RollDice(double inProb); +double RandRange(double mmin, double mmax); +double RandRangeBias(double mmin, double mmax, double biasRatio, double randomAmount); + +#if APL && !defined(XUTILS_EXCLUDE_MAC_CRAP) + +#include +#include + +void AppPath(string& outString); +OSErr FindSuperFolder(const FSSpec& inItem, FSSpec& outFolder); +void FSSpec_2_String(const FSSpec& inSpec, string& outString); +#endif + +void ExtractFixedRecordString( + const string& inLine, + int inBegin, + int inEnd, + string& outString); + +bool ExtractFixedRecordLong( + const string& inLine, + int inBegin, + int inEnd, + long& outLong); + +bool ExtractFixedRecordUnsignedLong( + const string& inLine, + int inBegin, + int inEnd, + unsigned long& outUnsignedLong); + +class XPointPool { +public: + + XPointPool(); + ~XPointPool(); + void clear(); + int accumulate(const float xyz[3], const float st[2]); + int count(void); + void get(int index, float xyz[3], float st[2]); + +private: + + XPointPool(const XPointPool&); + XPointPool& operator=(const XPointPool&); + + struct XPointPoolImp; + + XPointPoolImp * mImp; + +}; + +void DecomposeObjCmd(const XObjCmd& inCmd, vector& outCmd, int maxValence); +void DecomposeObj(const XObj& inObj, XObj& outObj, int maxValence); + +#endif \ No newline at end of file