#pragma once #include "AftrConfig.h" #include "IFace.h" #include "AftrOpenGLIncludes.h" #include "AftrSDLIncludes.h" #include #include #include "Vector.h" #include "ModelFwd.h" #include "WorldObjectChildContainer.h" #include "ManagerEnvironmentConfiguration.h" #include "ManagerTex.h" #include "ManagerShader.h" #include "AftrManagers.h" #include #include namespace Aftr { class ManagerSDLTime; class Camera; class AftrGeometryFrustum; class WOGJoint; class AftrGeometryRayVsFace; enum class AftrGeometricTerm : unsigned char; /// Order of OpenGL rendering -- all items in each enum (from left to right) are drawn completely before rendering WOs in the right-adjacent enum group enum class RENDER_ORDER_TYPE : unsigned char { roLIGHT = 0, roOPAQUE, roBLEND_FUNC_GL_ONE, roTRANSPARENT, roOVERLAY, roOVERLAY_2 };// , roNUM_RENDER_ORDER_TYPE }; std::ostream& operator<<( std::ostream& out, const RENDER_ORDER_TYPE& rot ); /** \class WO \author Scott Nykl \brief A WO and all children do not have an associated ODE Body. Therefore, it cannot collide with other objects. WOGhosts can be linked together hierarchically and can be animated ( \see WOHuman ). The WO class is a world object with NO associated ODE physics object. These objects are useful for visual representation of things that do not need to utilize ODE/collision detection. For example, a bird, distant orbiting planet, a particle effect, a "virtual force field" that is merely a transparent plane, an animated walking human, etc. A unique feature of the WO is its ability to create joints between different WO objects. These joints can be animated/modified via the changeOrientationWRTparent... method calls. A common technique to create an 'advanced' object is to use a hierarchical collection of WOGhosts as children of a WOBoundingShell. Remember that a WOBoundingShell inherits from WO (not WO) and therefore has a corresponding ODE body. This way, an animated object, such as a walking human, can be linked combined with ODE's physics. Simply imagine bounding box around an animated human, where the bounding box is used for collision detection, gravity, surface properties, etc, and the children ghost objects animate the human to make it look realistic. \{ */ class WO : public virtual IFace { public: /** Default constructor. Creates a WO with no associated VRML graphical model. This is typically called when the user will new the member variable 'model' as a MGL (Open GL Model) derivative. */ static WO* New(); /** Creates a WO with an associated VRML graphical model. \param modelFileName The path to the VRML model to load and associate with this WO. */ static WO* New( const std::string& modelFileName, Vector scale = Vector(1,1,1), MESH_SHADING_TYPE shadingType = MESH_SHADING_TYPE::mstAUTO ); WO( const WO& toCopy ) = delete; WO( WO&& toMove ) = delete; WO& operator=( const WO& toCopyAssign ) = delete; WO& operator=( WO&& toCopyMove ) = delete; /** /// When a WO's model is loaded asynchronously, multiple threads are employed; however, setting OpenGL /// parameters like Skins and shaders and textures require all the setters to be invoked on the /// main thread which created the OpenGL context. Therefore, after loading the model asynchronously /// completes at some point in the future, the callables submitted here will be invoked on the /// main thread and will be safe to use with OpenGL. /// /// When a Model is loaded asynchronously, some internal entities, like the ModelDataShared /// and its corresponding ModelMeshSkins are not loaded and one cannot therefore call /// wo->getModel()->getSkin().blah(...) because a worker thread is still parsing the file /// in the background. Trying to call or access these variables will result in a nullptr /// exception and crash the program. There is a *very easy* way to work around this -- simply /// call this method! /// /// This method lets the user enqueue a function to execute as soon as the /// internal data of this WO's Model becomes fully available after asychronous loading completes. /// /// Simply wrap your code in a callable. Then submit the callable to be executed after /// loading completes by calling WO::upon_async_model_loaded( myCallable ); /// Here is a full example that you can copy/paste near your WO::New( fileName ) code:\n\n"; /// WO* a10 = WO::New( ManagerEnvironmentConfiguration::getSMM() + "/models/Aircraft/A10/A10_ScaledActualSize/a10_meshDecimated.obj" ); a10->setPosition( 30, 15, 8 ); //it is OKAY to still set position and orientation -- this is part of the Model a10->getModel()->rotateAboutRelZ( 180.0f * Aftr::DEGtoRAD ); a10->setLabel( "A 10" ); a10->renderOrderType = RENDER_ORDER_TYPE::roOPAQUE; woList->push_back( a10 ); //You still *must* push the a10 into this->worldLst()... a10->upon_async_model_loaded( [a10]() //use a LAMBDA to capture the a10. { //This block of code will be invoked *as soon as* the .obj has completed loading and the ModelDataShared is valid! //It is guranteed this will always execute on the main thread on which the OpenGL Context was created - so OpenGL is invocable. //Now we can call commands that access/set OpenGL specific things inside the model (like the ModelDataShared). ModelMeshSkin& a10Skin = a10->getModel()->getModelDataShared()->getModelMeshes().at( 0 )->getSkins().at( 0 ); //Metal surface colors of the body of the A10 a10Skin.setAmbient( aftrColor4f( 0.3f, 0.3f, 0.3f, 1.0f ) ); //Color of object when it is not in any light a10Skin.setDiffuse( aftrColor4f( 1.0f, 1.0f, 1.0f, 1.0f ) ); //Diffuse color components (ie, matte shading color of this object) a10Skin.setSpecular( aftrColor4f( 0.4f, 0.4f, 0.4f, 1.0f ) ); //Specular color component (ie, how "shiney" it is) a10Skin.setSpecularCoefficient( 10 ); // How "sharp" are the specular highlights (bigger is sharper, 1000 is very sharp, 10 is very dull) //make cockpit transparent ModelMeshSkin& glassCockpit = a10->getModel()->getModelDataShared()->getModelMeshes().at( 1 )->getSkins().at( 0 ); float alpha = 0.4f; //Glass cockpit surface colors (notice the alpha is transparency) glassCockpit.setAmbient( aftrColor4f( 0.3f, 0.3f, 0.3f, alpha ) ); //Color of object when it is not in any light glassCockpit.setDiffuse( aftrColor4f( 1.0f, 1.0f, 1.0f, alpha ) ); //Diffuse color components (ie, matte shading color of this object) glassCockpit.setSpecular( aftrColor4f( 0.4f, 0.4f, 0.4f, alpha ) ); //Specular color component (ie, how "shiney" it is) glassCockpit.setSpecularCoefficient( 80 ); // How "sharp" are the specular highlights (bigger is sharper, 1000 is very sharp, 10 is very dull) //You may also access the bounding box here now that we know the dimensions of the objects we loaded... } ); */ void upon_async_model_loaded( task_to_run_upon_Async_Model_loading_completed&& task ); /** Destroys a WO, this explicitly deletes all associated joints, and the implicit destructor call to the WO removes all children and everything else. */ virtual ~WO(); /** \brief Returns the parent of this WO. WorldObjects are hierarchical in that a WO can have one or more children and those children can have one or more of their own children. \return A pointer to the parent WO. */ WO* getParentWorldObject(); /** Sets the parent of this WO. A WO can have at most ONE parent. This should only be used once on creation or problems will occur \param wo The WO* that is this child's parent. */ void setParentWorldObject( WO* wo ); /** Sets this WO's corresponding std::string label to inLabel. This is a user convience; no part of the engine relies on an object's label. */ virtual void setLabel( const std::string& inLabel ); /** Returns an std::string that represents a user-specified identifier. The engine itself does not use this; however, it can be useful for a user to label his WorldObjects. \returns A constant std::string reference that is label of this WO. */ //std::string getLabel() const; const std::string& getLabel() const noexcept; //consteval std::string_view getClassNameImpl(); virtual std::string getClassName( std::source_location const& loc = std::source_location::current() ) const; /** Returns a globally unique identifier for this WO. This value is generated automatically by Aftr and cannot be set by the user. This is helpful for OpenGL Selection and creating display lists corresponding to a WO. \return The globally unique identifier of this WO. */ unsigned int getID() const { return this->id; } /// Returns a string containing information about this WO instance. /// Including woID, label, class type, etc... virtual std::string toString( unsigned int hierarchyDepth = 0 ) const; /// Invokes WO::toString() on this instance an recursively on all children. virtual std::string toStringRec( unsigned int hierarchyDepth = 0 ) const; /** Returns this WO's globally unique identifier as a globally unique 24-bit color. Used by OpenGL Selection. The user passes in 24 bits as three 8-bit colors Red, Green, and Blue, respectively. The lowest 24-bits of the globally unique identifier are placed into red, green, and blue 8-bit bytes. The highest 8-bits of the globally unique identifier are NOT used as there is not enough space. |----------- 32-bit ID --------| xxxxxxxxRRRRRRRRGGGGGGGGBBBBBBBB |-red--||-Green||-Blue-| \param red The Bits 23 through 16 of the globally unique Identifier. \param green The Bits 15 through 8 of the globally unique Identifier. \param blue The Bits 7 through 0 of the globally unique Identifier. */ void convertIDtoUniqueColor( GLubyte& red, GLubyte& green, GLubyte& blue ); /** Returns a reference to the std::vector containing all of this WO's children. \return A Reference to a vector of children WO*. */ WorldObjectChildContainer& getChildren() { return this->children; } WorldObjectChildContainer const& getChildren() const { return this->children; } /** Returns the count of all decendents of this node not including this node itself. */ size_t getCountOfAllDecendants(); /** Returns the number of ancestors this WO has. For example: WO* a = WO::New(); WO* b = WO::New(); WO* c = WO::New(); WO* d = WO::New(); c.getChildren().push_back( d ); //d is child of c b.getChildren().push_back( c ); //c is child of b a.getChildren().push_back( b ); //b is child of a worldLst->push_back( a ); //a is a root node if WO* A has a child B who has a child C who has a child D, then D's depth is 3, C's depth is 2, B's depth is 1, and A's depth is 0. */ size_t getDepthFromRootWO(); /** Returns the internal model of the Model class \return A Pointer to the actual corresponding Model of this WO. */ virtual Model* getModel(); virtual const Model* getModel() const; /** Same as getModel(), but is templated so the user does not need to perform a static_cast on the return type. This is simply a convenience method. */ template< typename T, typename R = std::remove_pointer_t > R* getModelT() { assert( this->model != nullptr ); if constexpr( std::is_pointer_v ) return static_cast( this->model );//Lets wo->getModelT() work as expected. return static_cast( this->model ); //Lets wo->getModelT() work as expected. } template< typename T, typename R = std::remove_pointer_t > const R* getModelT() const noexcept { assert( this->model != nullptr ); if constexpr( std::is_pointer_v ) return static_cast(this->model);//Lets wo->getModelT() work as expected. return static_cast(this->model); //Lets wo->getModelT() work as expected. } /** Sets this WO's model pointer to the in passed model. This WO is now the parent of the model. A model can only be owned by exactly one WO at any time. */ virtual void setModel( Model* model, bool deleteExistingModel = true ); /** Takes as input, an array of 16 floats and copies the current display matrix of the model into that array. The display matrix is a 4x4 matrix such that the upper 3x3 is an orthnormal rotation matrix that transforms a vector from the local model space TO an embedded world space, ws_DCM_local (read from right to left). */ virtual void getDisplayMatrix( float* outParamDispMat4x4 ) const noexcept; virtual const Mat4& getDisplayMatrix() const noexcept; virtual Mat4& getDisplayMatrix() noexcept; virtual void setDisplayMatrix( const Mat4& dcm ) noexcept; /// Returns a Mat4 containing the orientation DCM / rotation matrix / display /// matrix in the upper 3x3 and global position in the 4th column virtual const Mat4 getPose() const noexcept; /// Uses the input param's orientation DCM in the upper 3x3 and position /// in the 4th column to adjust the position & orientation of this model. virtual void setPose( const Mat4& orientation_w_position ) noexcept; /** Returns a Vector representing the X,Y,Z location of this WO in global coordinates. \return A Vector representing the X,Y,Z location of this WO in global coordinates. */ virtual Vector getPosition() const noexcept; /** Sets the X,Y,Z location of this WO in global coordinates. The user typically calls this method inside of GLViewXYZ::LoadMap() to position the WO initially. \param newXYZ A Vector representing the new X,Y,Z location of this WO in global coordinates. */ virtual void setPosition( const Vector& newXYZ ); virtual void setPosition( float x, float y, float z ); /** Sets the X,Y,Z global coordinate of this WO. The children are intentionally left UNCHANGED. This method is invoked by the network subsystem to sync each newly received NetWO's position (from the server) to the local graphical representation of the WO stored locally on this client. \param newXYZ The new X,Y,Z global coordinate of only this WO. All The WO's children are UNCHANGED. */ virtual void setPositionIgnoringAllChildren( const Vector& newXYZ ); /** Similar to WO::setPosition. However, instead of setting this WO's position, this WO is moved from its current location by dXdYdZ. That is, if this WO is at (10,20,30) and moveRelative( Vector( 4,5,6 ) ) is made, this WO's global position shall be set to (14,25,36). \param dXdYdZ A Vector with the corresponding X,Y, and Z values represent the delta X, delta Y, and delta Z movement, respectively. */ virtual void moveRelative( const Vector& dXdYdZ ); virtual void moveRelativeIgnoringAllChildren( const Vector& dXdYdZ ); /** Returns a normalized vector pointing in the direction the WO is "facing". According to convention, the WO's "front" (think nose of airplane) points along the +X-axis, the plane's tail points towards the +Z-axis, and the wings lie along the Y axis. The left wing points along the +Y-axis and the right wing points towards the -Y-axis. \return Normalize vector pointing in the direction this WO is currently facing. */ virtual Vector getLookDirection() const; /** Returns a normalized vector pointing perpendicular to the direction the WO is "facing". According to convention, the WO's "front" (think nose of airplane) points along the +X-axis, the plane's tail points towards the +Z-axis, and the wings lie along the Y axis. The left wing points along the +Y-axis and the right wing points towards the -Y-axis. This method would return a vector pointing along the +Z-axis. \return Normalize vector pointing in the direction perpendicular to where this WO is currently facing. The current roll of the object is accounted for. */ virtual Vector getNormalDirection() const; virtual void rotateToIdentity(); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutRelX( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutRelY( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutRelZ( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutGlobalX( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutGlobalY( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model virtual void rotateAboutGlobalZ( float deltaRadianAngle ); ///< Convenience method that invokes this corresponding method on this WO's model //User Keyboard Input Specific //----------------------------------- /** Fired when this WO is the current actor AND a key is pressed. Note that if the GLView defines a keypress for key x, that will be handled BEFORE this method gets passed key x. This also means that if the GLView returns execution in its onKeyDown method prior to calling this method, this method may never be invoked. For example, the 'q' to quit will never be passed to this method as GLView handles the event first, and then terminates the program. \param key SDL_KeyboardEvent generated by the key press */ virtual void onKeyDown( const SDL_KeyboardEvent& key ); /** Fired when this WO is the current actor AND a key is released. Note that if the GLView defines a key release for key x, that will be handled BEFORE this method gets passed key x. This also means that if the GLView returns execution in its onKeyUp method prior to calling this method, this method may never be invoked. For example, the 'q' to quit will never be passed to this method as GLView handles the event first, and then terminates the program. \param key SDL_KeyboardEvent generated by the key press */ virtual void onKeyUp( const SDL_KeyboardEvent& key ); //----------------------------------- //User Mouse Input Specific (for selection only right now //Overloadable, mouse functions for WorldObjects. /**Invoked when this WO is the current actor and the user presses a mouse button*/ virtual void onMouseDown( const SDL_MouseButtonEvent& mouse ); /**Invoked when this WO is the current actor and the user releases a mouse button*/ virtual void onMouseUp( const SDL_MouseButtonEvent& mouse ); /**Invoked when this WO is the current actor and the user scrolls the mouse wheel*/ virtual void onMouseWheelScroll( const SDL_MouseWheelEvent& e ); /**Invoked when this WO is the current actor and the user moves a mouse*/ virtual void onMouseMove( const SDL_MouseMotionEvent& mouse ); /** Invoked when the user clicks on this WO via the mouse cursor while selection is enabled. For example, if the Left CTRL key is held down while a user clicks on this WO, this method is invoked. */ virtual void onMouseDownSelection( const SDL_MouseButtonEvent& mouse ); /** When a WO is selected via onMouseDownSelection(...), if that WO has a non nullptr parent, the default behavior is to notify the parent that the child was selected. This may propagate up to the "top" WO that may choose to use this information. This is intended to used when a hierarchy of objects (for example, a robotic arm) are composed of a non-specialized type of child such as a WO*. Instead of forcing the programmer to create a special child subclass for each type of child, the programmer only needs to create an intelligent root WO* (the topmost parent). Using this callback, the topmost parent will be notified with a child is selected. All the logic to do some intelligent behavior can then be placed in the parent. This avoids having to create subclasses only to add code in each child class. */ virtual void onMouseDownChildWasSelected( const SDL_MouseButtonEvent& mouse, WO* selectedChild ); //----------------------------------- /** Renders this WO's Graphical Model (this->model). If any children WorldObjects exist, each child's render() method is also invoked. Thus, the parent and all it's children are drawn to the screen. This method is typically invoked inside of the GLView's render() method; which automatically draws renders all WO* that were added to the std::vect< WO* >worldLst inside of GLViewXYZ::LoadMap(). The user should never need to directly call this method. */ virtual void render( const Camera& cam ); /** This method is invoked by the \see GLView::onMouseDownSelection method when the user is in "WO Selection Mode" and clicks somewhere within the SDL Window. Every WO (and its children) are drawn in a slightly different color. Then, the color clicked on by the user identifies which WO* the user clicked on. GLView's \see GLView::lastSelectedWorldObject* shall point to the selected WO* after the GLView::onMouseDownSelection returns. The user should never need to directly call this method. */ virtual void renderSelection( const Camera& cam ); /** Resets the joint transform to identity matrix, and resets all the children's joints to the identity matrix as well. This will effectively reset the WO model to its original orientation before any joint manipulations occurred. */ virtual void resetJoints(); /** After setting a parent of this WO, this locks the distance and orientation between the parent and child. After this, whenever the parent moves and/or rotates, the child shall remain oriented appropriately. If both position and orientation are important to preserve, call WO::lockWRTparent_using_current_relative_pose() instead. */ virtual void lockWRTparent( WOGJoint* joint = nullptr ); /** Captures the relative pose between the parent (this->getParentWorldObject) and child (this WO). This relative Pose includes position + orientation. Using this relative pose, a joint is formed. The joint is colocated with the child's center so rotating the joint's DCM will provide a relative rotation of the child in the parent's frame. Before calling this method: 1) Set the parent's pose to any position and orientation. 2) Set the child's pose (this WO) to any position and orientation. 3) Calling this method will capture the relative position & orientation between the parent and child. Each frame the child's pose will be updated relative to the pose of the parent. */ virtual void lockWRTparent_using_current_relative_pose(); /** Free's this child from being locked w.r.t the parent. This object is STILL a child of the parent. Additionally, the enableChangeableOrientationWRTparent gets reset to false. This object can call 'lockWRTparent()' again at any time to fix this object's new position and orientation w.r.t the parent (or even a different parent, if this object's parent is changed). */ virtual void unLockWRTparent(); /** Returns true if this WO is locked with respect to its parent; false, otherwise. \return true if this is locked with respect to its parent; false, otherwise. */ virtual bool isLockedWRTparent() { return this->IsLockedWRTparent; } /** If this object's joint is not nullptr, this method will mutate the joint's orientation about the corresponding relative (local joint space) axis by the specified number of radians. Mathematically, this method rotates this object's joint transform matrix delta_rel_Z_rad radians about the relative positive Z-axis. This is equivalent to the yaw. \param delta_relY_rad Radian angle by which this object shall be rotated about its joint's relative +z-axis. */ virtual void rotateAboutJoint_relZ( float delta_relZ_rad ); /** If this object's joint is not nullptr, this method will mutate the joint's orientation about the corresponding relative (local joint space) axis by the specified number of radians. Mathematically, this method rotates this object's joint transform matrix delta_rel_Y_rad radians about the relative positive y-axis. This is equivalent to the negative "pitch up" for an aircraft flying flat & level. \param delta_relY_rad Radian angle by which this object shall be rotated about its joint's relative +y-axis. */ virtual void rotateAboutJoint_relY( float delta_relY_rad ); /** If this object's joint is not nullptr, this method will mutate the joint's orientation about the corresponding relative (local joint space) axis by the specified number of radians. Mathematically, this method rotates this object's joint transform matrix delta_rel_X_rad radians about the relative positive X-axis. This is equivalent to a fighter pilot rolling clockwise in a jet fighter. \param delta_relX_rad Radian angle by which this object shall be rotated about its joint's relative +x-axis. */ virtual void rotateAboutJoint_relX( float delta_relX_rad ); /** Returns a pointer to the WOGJoint that attaches this WO to its parent WO \return The WOGJoint that attaches this WO to its parent WO; nullptr, if no joint currently exists. */ virtual WOGJoint* getJoint(); /** Populates output with the nearest point on the ray between tail and head intersects the WO or one of its children, defined by Aftr conventions, i.e. uses composite index and vertex lists. Internally, this method transforms the in passed ray through the inverse of the display matrix and subtracts this WO's position; therefore, the actual ray intersection tests can be done without transforming any of the original face positions (faster). This method will call the WO's Model's Model::getNearestPointWhereLineIntersectsMe(...), which assumes the above transformations have already taken place. The user would typically call this method to intersect a ray at an arbitrary location with a WO at an arbitrary location and orientation. If Vector* statistics is not nullptr; then statistics->x returns the number of OctnodesTested during this call statistics->y returns the number of face linePlaneIntersections tested during this call statistics->z returns the number of isInsideTrangle( pt) tested during this call returns AftrGeometricTerm::geoSUCCESS if a point is found returns AftrGeometricTerm::geoERROR if no point is found */ virtual AftrGeometricTerm getNearestPointWhereLineIntersectsMe(const Vector& rayTail, const Vector& rayHead, Vector& output, bool lineSegment = true, bool useOctreeSearchIfAvailable = true, Vector* statistics = nullptr, AftrGeometryRayVsFace* hitFace = nullptr ); /** Returns true if this WO Model's BoundingBox is inside of the viewing frustum; false, otherwise. \param viewportFrustum The camera's AftrGeometryFrustum that is used against this WO Model's BoundingBox. */ virtual bool isInViewingFrustum( const AftrGeometryFrustum& viewportFrustum ); bool useFrustumCulling; ///< If true, frustum culling is performed on this WO Model's boundingbox to accelerate rendering bool isVisible; ///< If true, the WO's model shall be rendered; otherwise the WO shall not be rendered. void makeMeAndAllMyChildrenInvisible(); ///< Makes this WO and all it's children invisible void makeMeAndAllMyChildrenVisible(); ///< Makes this WO and all it's children visible /** Determines how this object is drawn by OpenGL. Should this WO's model be rendered as opaque, transparent, or is the object a light? \see RENDER_ORDER_TYPE */ RENDER_ORDER_TYPE renderOrderType; /** Called on every WO in the world list during GLView::updateWorld() either: exactly once, or exactly one time per Physics Engine Step. Purpose: This method may be used by the user as a mechanism of internal time keeping and other non-ODE related internal house-keeping actions that need to be performed every physics 'tick'. If no physics engine exists, this method is invoked ONCE per GLView::updateWorld() call. If a physics engine exists, this method is invoked once before EVERY physics step is taken. Thus, if the ratio of 'number of physics steps to number of renders' is 15, then this method shall be invoked on every object in the world list 15 times. Example code block to use this method to keep track of the time since an action dealing with this object occurred: virtual void onPhysicsEngineTick() { this->myPrivateTimeCumulator += ManagerSDLTime::getTimeSinceLastPhysicsIteration(); while( this->myPrivateTimeCumulator >= MY_TIME_THRESHOLD ) { foo(); this->myPrivateTimeCumulator -= MY_TIME_THRESHOLD; } } DESCPTION: If and only if the Physics Engine is nullptr (is not used by the module), this method shall be invoked once per GLView::updateWorld() invocation. If and only if the Physics Engine is NOT nullptr ( IS USED by the module), this method shall be invoked once prior to EACH iteration the Physics Engine takes. Thus, if the ratio of 'number of physics steps to number of renders' is 15, then this method shall be invoked on every object in the world list 15 times. Each invocation occurs just before the GLView calls on the Physics Engine to take a step. */ virtual void onPhysicsEngineTick(); /** This method is invoked within GLView::init() after LoadMap has completed, the physics engine has been created, and the Network Subsystem is fully initialized; that is, the Simulation is fully initialized and ready to begin. No simulation ticks have yet been taken. The simulation is completely ready to begin. Users can overload this method to perform any 'last minute' initialization that may need to be based on any previous init information that may not have been available within the World Object's constructor. */ virtual void onSimulationCompletelyInitialized(); /** Called once per main engine iteration by GLView::updateWorld on each WO. This method should be used to update any state that may be based on time or another variable that is not necessarily directly controlled by that WO. */ virtual void onUpdateWO(); /** \returns the total faces of this object and all it's children */ size_t getFaceCountIncludingChildren(); /** Invoked after GLView::loadMap() on all WOs. This method ensures that each WOODE has a valid model. If a user does not want a Model for a specific WOODE, then he can instantiate that WOODE's model to be an MGLnullptrModel. */ virtual void inspectForNullModel(); protected: WO(); virtual void onCreate(); virtual void onCreate( const std::string& modelFileName, Vector scale = Vector(1,1,1), MESH_SHADING_TYPE shadingType = MESH_SHADING_TYPE::mstAUTO ); void initInstanceData(); ///< Called by the numerous constructors to ensure all protected member variables are always initialized in one place. /// Recursive helper function for WO::getCountOfAllDecendants() size_t getCountOfAllDecendantsRec( WO* wo ); bool IsLockedWRTparent = false; WOGJoint* joint = nullptr; ///< Joint connect to this WO's parentWO; this joint is used to 'lock' this WO WRT its parent; nullptr iff not attached. unsigned int id = 0; ///< globally unique ID for this WO, mainly used by the renderSelection for user selection purposes. std::string label = "";///< Provided as a user convience; he can set this to any std::string desired; ie, "Chuck's Red ball", "Ted's Car", or "Boat 12". Model* model = nullptr; ///< Graphical Model associated with this WO. Currently this can be loaded from a file (3ds, wrl) or procedurally generated WO* parentWorldObject = nullptr; ///< A pointer to this WO's parent; if there is no parent, it is nullptr. WorldObjectChildContainer children; ///< A container which stores pointers to all of this WO's child WorldObjects. }; } //namespace Aftr