//----------------------------------------------------------------------------- // Description: An OpenGL form control capable of drawing a World onto itself // Author: Scott Nykl //----------------------------------------------------------------------------- #pragma once #include "AftrConfig.h" #include "AftrOpenGLIncludes.h" #include "AftrGlobals.h" #include "HandlerMouseState.h" #include "SelectionQueryResult.h" #include "Vector.h" #include "PhysicsEngine.h" #include "ManagerEnvironmentConfiguration.h" #include "ManagerLight.h" #include "ManagerTex.h" #include "ManagerShader.h" //#include "AftrManagers.h" #include "NetMsg_callback_decl.h" #include #include namespace Aftr { class Camera; class WorldContainer; class PhysicsEngine; class WO; class GUI; class UI; class NetMessengerServer; class ManagerTex; class ConnClientGhost; class AftrFrameBufferObject; class IndexedGeometryQuad; class AftrGLRendererBase; class ModelMesh; enum CAM_CHASE_TYPE; using callback_func_on_SDL_Event = std::function; /** \class GLView \author Scott Nykl \brief The abstract GLView. This class is the top-most manager of the module. This should be inherited from and the child shall be named GLViewModuleName. For example, the TideIsland module GLView is called GLViewTideIsland. Important: Since this is an abstract class with no constructor, all child classes are REQUIRED to invoke the GLView::init() method. For example: GLViewNewModule* GLViewNewModule::New( const std::vector< std::string >& args ) { GLViewNewModule* glv = new GLViewNewModule( args ); glv->init( Aftr::GRAVITY, Vector( 0, 0, -1.0f ), "Aftr.conf", 2 ); glv->onCreate(); return glv; } GLViewMyModule::GLViewMyModule( const std::vector< std::string >& args ) { //Initialize member variables here... } This class contains ALL aspects of a module. Several relevant aspects include: -# worldLst - The std::vector of WO*. All WO in your module shall be new'd in loadMap() and pushed into worldLst inside of loadMap(). -# actorLst - All WO* that desire camera tracking and keyboard callbacks shall be pushed into actorLst. -# actor - The WO* pointing to the current actor; only one WO can be the actor at any given time. -# cam - The camera used within this module. -# lastSelectedWorldObject - When a user crtl-clicks a WO in your module, this WO* points to that WO. -# pe - This module's physics engine. It is used within GLView::updateWorld each main loop iteration. \{ */ class GLView { public: //Constructor is protected and only invoked by descendants of the GLView class /** Deletes the: -# GUI -# Camera -# Physics Engine -# All WO* in the worldLst -# worldLst -# actorLst -# Shuts down all static Managers This destroys ALL aspects that define a module. If main.cpp deletes the GLView, all memory ever used within GLView is released. This is actually what the 'RESET' button actually does it deletes the GLView and new's it again (along with reinitializing SDL). */ virtual ~GLView(); /** Returns a pointer to the GLView's UI instance. nullptr if not instantiated. */ UI* getUI(); /** By default, when an engine instance starts, port 12683 opens (TCP and UDP) to listen for NetMessages. This is a method for interprocess communications or cross-network information exchange. The NetMessage server is an asynchronous listener that can serve many simultaneous clients. For example, a Java GUI can send information to the engine to visualize something. This returns a NON-owning pointer */ std::shared_ptr getNetMessengerServer(); /** This method is called within the main loop inside GLView::startWorldSimulationLoop(). After each frame, time is accounted for, updating the framerate over the last second. */ virtual void setCurrentFrameRate( int frameRateOverLastOneSecond ) { this->currFrameRate = frameRateOverLastOneSecond; } /** Returns the number of times this module's world was rendered to the screen during the last ONE second. \return the number of times this module's world was rendered to the screen during the last ONE second. */ int getCurrentFrameRate() { return this->currFrameRate; } /** Returns The Width of the GLView in pixels \return The Width of the GLView in pixels */ int getWindowWidth(); /** Returns The Height of the GLView in pixels \return The Height of the GLView in pixels */ int getWindowHeight(); /** Sets the actor the camera will watch. Also determines which WO* any SDL_Event callbacks, such as keypresses, will be sent to. For example, if the human is the current actor, then the UP ARROW will be processed by the human. This will probably make him do something like walk forward. \param inActor The WO* that shall become the current actor */ virtual void setActor( WO* inActor ); /** Iterates to the next actor in actorLst. */ virtual void trackNextActor(); /** Returns A pointer to the current actor. \return a pointer to the current actor. */ WO* getActor(); /** \returns a pointer to the GLView's actorLst. Note, the actorLst is not populated until after GLView::loadMap() returns. */ WorldContainer* getActorLst() { return this->actorLst; } /** \returns a pointer to the GLView's netLst. Note, the netLst is not populated until after GLView::loadMap() returns. */ WorldContainer* getNetLst() { return this->netLst; } /** Returns a pointer to the GLView's worldLst. \return A pointer to the GLView's worldLst. */ WorldContainer* getWorldContainer(); /** Sets the number of ODE Physics Steps taken between each time the worldLst is rendered to the screen. \see numPhysicsStepsPerRender \param numPhysSteps the number of ODE Physics Steps taken between each time the worldLst is rendered to the screen. */ void setNumPhysicsStepsPerRender( int numPhySteps ) { this->numPhysicsStepsPerRender = numPhySteps; } /** Returns the number of ODE Physics Steps taken between each time the worldLst is rendered to the screen. \return The number of ODE Physics Steps taken between each time the worldLst is rendered to the screen. */ int getNumPhysicsStepsPerRender() const noexcept { return this->numPhysicsStepsPerRender; } CAM_CHASE_TYPE getActorChaseType() const noexcept { return this->ActorChaseType; } void setActorChaseType( CAM_CHASE_TYPE chaseType ); /** Returns a pointer to this GLView's physics engine. \return pe A pointer to this GLView's physics engine. */ PhysicsEngine* getPhysicsEngine(); /** Returns a pointer to the camera used within this module. \return A pointer to the camera used within this module. */ Camera* getCamera() noexcept; const Camera* getCamera() const noexcept; /** Returns a function that returns the current Camera* in the GLView. Helpful when you always want to be able to access the latest camera contain in the GLView, even when it changes between frames. */ std::function< Camera*() > getCamera_functor() noexcept; /** Returns a pointer to a Camera pointer. This may be helpful for objects that do not necessiarly want to store a GLView* but need a pointer to the current Camera stored in the GLView. Each time a camera is recreated, GLView->getCamera() may point to a new memory location; hence storing a local pointer equal to GLView->getCamera() is unsafe. For example, many MGLs only need access to data within the Camera. Since the camera is deleted and new'd each time a new view style changes, storing the GLView's original Camera* will not work after recreating a camera. If the MGL stores a "Camera** myCam" and sets that equal to "GLView->getCameraPtrPtr()", then invoking (*myCam)->getPosition() will return the camera's current position. This method safely stores a pointer to the GLView's current Camera. Another safe method is to store a "GLView* glView" and simply invoke glView->getCamera() to get a pointer to the GLView's current camera. If you are unsure about the above, simply store a GLView* in your own object and call GLView->getCamera() to get a pointer to the current camera. Do not store the pointer returned by GLView->getCamera() since it will be invalid once the camera is deleted and new'd due to camera type change. */ Camera** getCameraPtrPtr() noexcept; //Returns a const pointer to a const pointer to a camera (const version of the method above). Camera const*const* getCameraPtrPtr() const noexcept; /** This method is where the user constructs his entire virtual world. The user performs the following here: -# new the worldLst -# new the actorLst -# new the netLst -# new a WO -# set the WO's attributes such as position, mass, surface properties, parent WO -# push WO into worldLst -# if the WO is an actor, push into actorLst -# Repeat above 4 steps for ALL WO the user will ever use during runtime of this module. */ virtual void loadMap() = 0; /** Begins the simulation corresponding to this module's GLView child. \return The return value has 2 different meanings: -# If 0 is returned, a request to exit program has been issued (Exit). -# If -1 is returned, a request to delete and re-instantiate the module has been issued (Reset). */ virtual int startWorldSimulationLoop(); /** This method is invoked by main.cpp once every main loop iteration. This method takes zero or more ODE physics steps, renders the world, and also invokes the onPhysicsEngineTick callback on all WO* in the worldLst. The number of ODE physics steps taken is equal to the current ratio. That is, if the ratio is 15, then 15 ODE physics steps are taken. Note that this means every WO's ODEupdateForcesAndJoints() and ODEupdateToNewPositionOrientation() are invoked 15 times as well. The WO::onPhysicsEngineTick shall be invoked ratio times iff ratio > 0; else if ratio == 0, WO::onPhysicsEngineTick shall be invoked exactly once. This invocation rule exists to ensure that cumElapsedTime is still updated even if the physics engine is paused. The scene shall be rendered once, after all the ODE physics steps/onPhysicsEngineTick are taken. */ virtual void updateWorld(); /** Fires when the screen is reshaped used to scale the world to fit the screen height and width parameters are the screen size. This also invokes WOGUI widget onResizeWindow() callbacks. */ virtual void onResizeWindow( GLsizei width, GLsizei height ); virtual void resetMouseState(); /** Fired when a mouse button is pressed */ virtual void onMouseDown( const SDL_MouseButtonEvent& e ); /** Fired when a mouse button is released */ virtual void onMouseUp( const SDL_MouseButtonEvent& e ); /** Fired when a mouse scroll wheel is scrolled (either vertically or horizontally */ virtual void onMouseWheelScroll( const SDL_MouseWheelEvent& e ); /** Fired when a mouse button is moved */ virtual void onMouseMove( const SDL_MouseMotionEvent& e ); /** Fired when a key is pressed */ virtual void onKeyDown( const SDL_KeyboardEvent& key ); /** Fired when a key is released */ virtual void onKeyUp( const SDL_KeyboardEvent& key ); /** Fired when motion of a Joystick Axis is encountered */ virtual void onJoyAxisMotion( const SDL_JoyAxisEvent& joy ); virtual void onJoyBallMotion( const SDL_JoyBallEvent& joy ); virtual void onJoyHatMotion( const SDL_JoyHatEvent& joy ); virtual void onJoyButtonDown( const SDL_JoyButtonEvent& joy ); virtual void onJoyButtonUp( const SDL_JoyButtonEvent& joy ); virtual void onJoyDeviceAdded( const SDL_JoyDeviceEvent& joy ); virtual void onJoyDeviceRemoved( const SDL_JoyDeviceEvent& joy ); /** Fired when selection is enabled and a mouse button is pressed */ virtual void onMouseDownSelection( unsigned int xPixelCoord, unsigned int yPixelCoord, Camera& cam ); /** Turns OpenGL selection on */ void enableOpenGLSelection() { openGLSelectionEnabled = true; } /** Turns OpenGL selection off */ void disableOpenGLSelection() { openGLSelectionEnabled = false; } /** Returns true if OpenGL selection is enabled; false, otherwise. */ bool getOpenGLSelectionEnabled() { return openGLSelectionEnabled; } /** Returns a pointer to the WO* selected via OpenGL selection; if nullptr, the last call to selection did not select a WO. \return WO* The last selected WO*. If nullptr, the last selection attempt did not find a WO* */ WO const* getLastSelectedWO() const noexcept; WO* getLastSelectedWO() noexcept; ///** // Sets the lastSelectedWorldObject to the in passed WO*. This should only be called by a user // that knows exactly why he wants to change this value. An example of when this is useful is to // set the lastSlectedWorldObject to nullptr before performing some operation. // \param WO* The WO* that the lastSelectedWorldObject shall be set equal to. //*/ //void setLastSelectedWorldObject( WO* x ); /** Returns a pointer to the global coordinate representing where the last successful selection occured; nullptr is returned if that last selection attempt failed. */ std::optional getLastSelectedCoordinate() const noexcept; /** Returns a pointer to the ModelMesh selected on the previous selection call. If no ModelMesh was selected during that call, this will be set to nullptr */ ModelMesh const* getLastSelectedModelMesh() const noexcept; ModelMesh* getLastSelectedModelMesh() noexcept; ///The selectionQuery is updated during each call to GLView::onMouseDownSelection(...) SelectionQueryResult const& getLastSelectionQuery() const noexcept { return this->selectionQuery; } HandlerMouseState* getHandlerMouseState() { return &this->mouseHandler; } /** This function triggers the engine to be fully shutdown after the current iteration of GLView::startWorldSimulationLoop() completes. This method properly destroys all objects, shutdown all managers, frees all corresponding memory, and exits normally. */ virtual void shutdownEngine(); /** This function triggers the engine to be fully reset after the current iteration of GLView::startWorldSimulationLoop() completes. This method properly destroys all objects, shutdown all managers, frees all corresponding memory, and then restarts the engine reinitializing all components to their original state. */ virtual void resetEngine(); virtual AftrGLRendererBase* getGLRenderer(); /** If a user would like to "hear" all SDL Events that occur -- beyond the events that are normally already routed to WO events (mouse/keyboard/joystick), they may subscribe to the GLView via this method and pass in a bool returning function that takes a const reference to the SDL Event (a unary predicate). If the subscriber consumes the event and does not want it to be propagated further, the predicate will return true. If the predicate returns false, the SDL Event continues propagating through the rest of the event handlers. For example, if integrating an entirely independent GUI system into a module or WO, like Dear ImGui, that GUI system will want to hear all the SDL Events that take place. Generally a normal WO would not need to subscribe to this at all and instead using the virtual SDL Event methods intrinsic to the WO base class. */ void subscribe_to_SDL_Events( callback_func_on_SDL_Event call_on_Event ); /** Register a callback that is invoked when a specific NetMsg arrives. For example, if you have a custom Net Message called NetMsgDoAction, the following code will invoke a user specified lambda whenever a NetMsgDoAction arrives: //msg is of type std::unique_ptr callback_on_NetMsgT_arrived func = []( auto msg ) { fmt::print( "NetMsgDoAction arrived here, yay!\n" ); //use the message, it is of type std::unique_ptr //msg->data ... } HOW TO CALL THIS FUNCTION GIVEN a NetMsg class type and a function to call when that type of NetMsg arrives. This subscription function could be done in the `loadMap()` or at anypoint during runtime. As soon as subscription is complete, the lambda will be invoked and the `onNetMsgArrived()` will no longer be automatically invoked. this->subscribe_NetMsg_to_callback( func ); //easy-peasy //--------------------------------------^ ^ //This is the type of NetMsg to register| | //----------------------------------------------------^ //This is the func that will be invoked when the specific NetMsg arrives. //The signature of func will be `[...]( auto msg ){...};`. msg will //be of the exact type `std::unique_ptr`. When opting in to using this callback, the std::unique_ptr that is passed into func has been *fully* deserialized. It's internal implementation of onNetMsgArrived() has not yet been invoked -- it is now fully up to the `func` function to do whatever is desired by the user. */ template< typename NetMsgT > void subscribe_NetMsg_to_callback( callback_on_NetMsgT_arrived templated_NetMsg_callback_func ) { //eventually, m.getNetMsgID() can be replaced with: sln jan 2023 //callback_NetMsgT_erased erased( templated_NetMsg_callback_func, NetMsgT::getNetMsgID_consteval() ); //beautiful, no? NetMsgT m; //a benefit of this approach is that the linker will *always* pull in any used NetMsg into a module this way callback_NetMsgT_erased erased( templated_NetMsg_callback_func, m.getNetMsgID() ); this->subscribe_NetMsg_to_callback_impl( std::move(erased) ); } ///After the templated function GLView::subscribe_NetMsg_to_callback(...) has type erased the callback, ///this implementation helper actually sends the erased callback to the ManagerSerializeableNetMsgMap. ///It is broken into two functions to avoid the templated function above needing to reference a manager ///and include more header dependencies in GLView.h -- this essentially is a firewall proxy to reduce ///compile times. void subscribe_NetMsg_to_callback_impl( callback_NetMsgT_erased&& erased_callback_func ); protected: /** Constructor. Initializes all core parts of the engine. This is invoked by all modules when the child GLView decedent is instantiated. */ GLView( const std::vector< std::string >& args, std::string const& confFileName = "aftr.conf" ); /** Contains all WO* that will ever be used during this module's runtime. This list shall get newed and populated inside of the child GLView::loadMap() method. Once loadMap() returns, the engine assumes this list is complete and that this list will not change for the rest of the module's runtime. If a WO* is new'ed but not pushed into this list, that WO* will be non-functional and may make your module unstable, if other WO* are associated with that WO*. The worldLst is owned and deleted by GLView. Therefore the user shall never need to call delete worldLst. */ WorldContainer* worldLst = nullptr; /** Contains all WO* that can/will ever be tracked by the camera. There is only one actor at any given time. The WO* actor points at the current actor. When an SDL_Event occurs, such as a user key-press, GLView will forward that SDL_Event to the actor's corresponding callback method. In this case when the event occurred, actor->onKeyDown( SDL_Event& key ) would automatically be invoked. The actorLst is owned and deleted by GLView. Therefore the user shall never need to call delete actorLst. These are pointer into the worldLst. */ WorldContainer* actorLst = nullptr; //pointers into worldLst /** Valid only when TNL is being used. Points to all WO* that shall be used as client controlled avatars throughout the simulation. This list shall get newed and populated inside of the child GLView::loadMap() method. Once loadMap() returns, the engine assumes this list is complete and that this list will not change for the rest of the module's runtime. These are pointer into the worldLst. */ WorldContainer* netLst = nullptr; /** The camera used within the module. \see Camera */ Camera* cam = nullptr; /** Defines how the camera follows the current actor. How the camera follows the current actor. \see CAM_CHASE_TYPE */ CAM_CHASE_TYPE ActorChaseType = CAM_CHASE_TYPE::STANDARDEZNAV; //Populated when SelectionQueryResult selectionQuery; /** This points to the current actor in the running module. If this is nullptr, there is no actor. When an SDL_Event, such as a key press occurs, the actor->onKeyDown( SDL_Event...) shall be invoked. For example, if the Human is the current actor and the user presses the 'Up Arrow' button, then the WOHuman::onKeyDown( SDL_Event& key) shall be invoked, and key shall reference the 'SDL_UP'. Then, the human can start walking forward. This point is not the owner; this is a pointer into worldLst. It can be manipulated via setActor */ WO* actor = nullptr; UI* ui = nullptr; ///< The Aftr Indexed-geometry based GUI, replacing the deprecated GUIChan /** Aftr IPC Mechanism to send / receive messages with other programs / processes over sockets. */ std::shared_ptr netMessengerServer; //#if defined AFTR_CONFIG_USE_ODE || defined AFTR_CONFIG_USE_NV_PHYSX /** The ODE / NVIDIA PhysX physics engine used by this module. This gets populated from all the WO* inside of the worldLst after loadMap() finishes execution. GLView::updateWorld makes use of the physics engine. The user should not have to interact with the physics engine unless he explicitly has specific reason to do so. The PhysicsEngineODE is owned by this class. */ PhysicsEngine* pe = nullptr; //#endif std::vector< std::string > args; ///< The command line arguments passed into this program. int currFrameRate = 60; ///< The number of times this module's world was rendered over the last one second. /** The number of ODE physics steps taken between each call to render the module's world. The default value is 15 physics steps per world render. ~1 physics step per render results in a very smooth "slow motion" behavior. ~15 physics step per render results in a "average world" behavior. ~50 physics step per render results in a very fast "jerky" behavior (on a Core 2 Duo w/ nVidia GeForce 8800) As physics steps grows beyond 15+ (based on hardware), too much CPU time is spent calculating physics steps and the frame rate drops. This results in the module becoming CPU BOUND. On the other hand, if the graphical capabilities are the bottleneck, a ratio of 1 will cause the module to become GRAPHICALLY BOUND. Hence, the GUI has the slider which allows the user to change the ratio during runtime. */ int numPhysicsStepsPerRender = 10; /** This handler keeps track of the current state of the mouse associated with this handler. Multiple handlers can be associated with multiple mice by feeding the SDL Mouse events to a particular HandlerMouseState. */ HandlerMouseState mouseHandler; /** Called by GLView::init(). If the user would like to maintain existing functionality of this method while adding additional functionality, he can simply over load this method and invoke 'GLView::initUserControls();' as the first line. Subsequent lines can then add functionality. */ virtual void initUserControls(); /** Called by GLView::init() This method new's the Camera at the X,Y,Z and sets its phi and theta corresponding to in passed params. */ virtual void initCamera( float xLoc = 15, float yLoc = 15, float zLoc = 10, float phi = 0, float theta = 0 ); /** Called by GLView::init() This method initializes frame buffers and parameters for Stereoscopic vision. This method will use values specified in the module's conf file for specific parameters. */ //virtual void initStereoVision(); /** After all of the WO's are created (upon loadMap() returning) and all other managers / physics engines have been initialzed. This invokes all of the init callbacks inside each WO. This way WOs that reference other WO's that have not yet been created can successfully reference them inside of these callbacks. This is also useful for networked server / client modules. If this is a server, all the server-side init callbacks are invoked. If this is a client-side instance, all client-side init callbacks are invoked. */ virtual void invokeWOinitCallbacks(); virtual void onUpdatePhysicsEngine(); /** The main initialization method for GLView. This method is designed to be invoked immediately after new-ing the GLView subclass instance. See the GLView class documentation at the top for an exact example of how to do this. By requiring the user to invoke GLView::init() as the first line of his child GLView's init() method, the user can avoid the complexities of knowing how to call the listed methods below. Initializes the GLView class -# calls initUserControls -# calls initCamera -# calls initialization functions on all static Managers -# calls LoadMap -# calls initManagerTex -# new's the Physics Engine based on type. 0 is NO Physics Engine. 1 is ODE. 2 is NVIDIA PhysX. */ virtual void init( float gravityScalar = Aftr::GRAVITY, Vector gravityNormalizedVector = Vector(0,0,-1), std::string confFileName = ManagerEnvironmentConfiguration::getDefaultConfFileName(), const PHYSICS_ENGINE_TYPE& physicsEngineType = PHYSICS_ENGINE_TYPE::petODE ); /** When an SDL_Event such as a mouse click occurs, main.cpp first gives the GUI a chance to claim this event as intended for the GUI. This is because the GUI is drawn on TOP of everything else; therefore, if a mouse click occurs on a point where a GUI Button lies, we assume the click was intended for the button, and not for a WO* behind that button. If the GUI does not deal with this event, main.cpp will then pass the event to the GLView's callback methods (ie, onMouseDown(...) ). If the GUI deals with the event, true is returned; false, otherwise. \return true if the GUI dealt with this event; false, otherwise. */ virtual bool handleGUIEvent( const SDL_Event& new_event ); /** This method processes all non-GUI input events that have occurred since the last main loop iteration. This method checks the type of input (mouse,keyboard,joystick,etc), and invokes the properly GLView callback (such as GLView::onKeyDown(...)). If the event is a Window event (such as a resize), this method passes the window event to 'int handleWindowEvent( SDL_WindowEvent& sdlWindowEvent )' for specific window handling. This method has the following return messages: 1 - continue normally (processed properly) 0 - exit module (received exit request) -1 - reset module (received reset request) */ virtual int handleEvent( const SDL_Event& sdlEvent ); virtual int handleWindowEvent( const SDL_WindowEvent& sdlWindowEvent ); virtual int handleSystemSpecificEventOSDependent( const SDL_SysWMEvent& sysEvent ); std::vector< callback_func_on_SDL_Event > subscribers_to_SDL_Events; bool openGLSelectionEnabled = false; ///