#pragma once #include "AftrOpenGLIncludes.h" #include "AftrGlobals.h" #include #include #include #include "ModelMeshRenderDataGenerator.h" #include "ModelMeshRenderData.h" namespace Aftr { class ModelMeshSkin; /** This class encapsulates all the information necessary to render the underlying geometry of a mesh. The associated skins (materials, textures, shaders,etc) are expected to already be bound before this mesh's render is invoked. This geometry is rendered using either flat shading (one normal per face) or smooth shading (one normal per vertex). HOW TO USE THIS CLASS / OVERVIEW: BLUF: This class stores the OpenGL handles to draw exactly one mesh that has three possible variants of GL_PRIM_TYPE, MESH_SHADING_TYPE (affects normal/topology generation), and MESH_RENDER_TYPE (VBO or VA). Very commonly, a mesh will have only one triplet of these options (such as GL_TRIANGLES, mstAUTO, and VBO). In this case a call to renderGL*(...) will enqueue that data to be drawn by the GL. If desired, multiple combinations of the aforementioned triplets can be generated and drawn via the appropriate ModelMeshSkin which stores the GL_PRIM_TYPE, MESH_SHADING_TYPE, and MESH_RENDER_TYPE. This class is generated via a ModelParser (like ASSIMP, or 3ds parser, or obj parser, or the ancient wrl parser). However, *there are TWO ways* parsers may use this class to create a valid MMDS that is drawn by GL. 1) The first way is to use the default constructor OR the constructor consuming a ModelMeshRenderDataGenerator. Using this approach will, at the point where sent to GL, invoke this->generateRenderData() which will use the vertex and index information (but not normals) to generate normals based on the shading type. This is great for files that do not provide normals. 2) Some parsers and file formats are more sophisticated and will provide their own normals (as well as things like tex coords, verts, indicies, tangents, generic attributes, etc). In this case, the parser (such as ASSIMP) will have to directly create the ModelMeshRenderData as it parses the file. Since the parser has beautifully crafted the MMRD, there is no need to make a generator that will build one that may not be exactly what the parser intended. In this case, use the third constructor that consumes a ModelMeshRenderData directly as well as a shading type, and prim type. Subsequently, at the point where sent to GL, this will inovke this-> Upon invocation of generateRenderData(...), the generated shading type is determined by the corresponding skin's shading type. If the skin's shadingType is changed later on, this method's generateRenderData(...) must be reinvoked passing in the new shadingType of either MESH_SHADING_TYPE::mstFLAT or MESH_SHADING_TYPE::mstSMOOTH. Internally, both the flat shaded data and smooth shaded data may exist concurrently if both shading types have been generated via generateRenderData(...). However, the default behavior only loads the skin's default shading type; this saves memory on both the graphics card and CPU-side. All meshes are rendered via OpenGL using indexed geometry objects; namely, Vertex Buffer Objects (VBOs) or Vertex Arrays (VAs). If the current machine supports VBOs, all non-deformable geometry is placed in a VBO and rendered via a Vertex Array encapsulating the constituient VBO buffers; this sends all data to the graphics card exactly once at load time. Any rendering of these objects is then performed on graphics hardware limited only by the graphics hardware. If the current machine does not support VAs/VBOs, or if the geometry is deformable, then VAs are used to perform the rendering. VAs send the data to the graphics card each render; however optimized drivers cache the VA data on the graphics card and only need to update the deformed geometry. Again, all rendering is done with indexed geometry; that is, no display lists, no immediate mode rendering. */ class ModelMeshDataShared { public: //When using this constructor or the constructor consuming a ModelMeshRenderDataGenerator, //this instance's generateRenderData(...) will be invoked to produce an internally owned //ModelMeshRenderData which will be placed into this instance's this->renderData. this->renderData is //an std::map which maps GL_PRIM_TYPE, MESH_SHADING_TYPE (affects normal/topology generation), and MESH_RENDER_TYPE (VBO or VA) //to a specific ModelMeshRenderData that can be drawn by OpenGL. ModelMeshDataShared(); ///In passed renderDataGenerator's ownership is given to this instance. ModelMeshDataShared( std::unique_ptr renderDataGenerator ); ///This constructor consumes/takes ownership of the in passed ModelMeshRenderData* object. This is used when ///a generator isn't necessarily needed, but the in passed render data ought to be used as is. For example, ///The Asset Importer library (assimp) performs the primitive type conversion, shading operators, and ///normal generation that the ModelMeshRenderDataGenerator usually does. ///If assimp already does this, we don't want to redo that work. ///It's also likely that assimp does this conversion more efficiently and timely than the generator class. ///The user must also pass in the corresponding shading type of the contained render data (smooth or flat). ///The user must also pass in the OpenGL Primitive type of the rendered primitives, ie GL_TRIANGLES, ///GL_TRIANGLE_STRIP, GL_POINTS, etc. /// ///*When this constructor is called, we have to invoke ModelMeshDataShared::addModelMeshRenderData() /// from within ManagerModelMultiplicity::sendModelDataSharedServerSide() call. This is in contrast to /// other parsers which invoke this class' default constructor and then invoke this->generateRenderData(). /// When the default constructor is called, we have to invoke ModelMeshDataShared::generateRenderData() /// from within ManagerModelMultiplicity::sendModelDataSharedServerSide(). /// /// The GenerateRenderData() variant of the method will also generate normals, shading information, etc. /// Using this constructor will not invoke generate and will let the user (parser, like ASSIMP) populate /// the normal array and shading information directly from the parser. So if the 3D model file has normal /// information, this constructor lets you directly use that instead of recomputing smooth or flat shading. /// ModelMeshDataShared( std::unique_ptr renderData, const MESH_SHADING_TYPE& mstType, GLenum glPrimType, bool destroyCPUSideDataAfterSendingToGPU = true ); virtual ~ModelMeshDataShared(); /// Returns true iff a valid ModelMeshRenderData matching the in passed triplet of render keys was found bool has_sent_ModelMeshRenderData_to_ServerSide( MESH_SHADING_TYPE shadingType, GLenum glPrimType = GL_TRIANGLES ) const noexcept; ///Returns true only if a new ModelMeshRenderData was inserted into this instance internal renderDataMap AND successfully registered/sent ///to the Graphics API. /// This method will ensure the data passed into this MMDS is sent to OpenGL and properly instantianted. This includes the 3 constructors /// worth of input data -- either a ModelMeshRenderDataGenerator (default constructor or 2nd constructor) OR a ModelMeshRenderData /// directly built from a parser (such as ASSIMP) (3rd constructor). /// You may use this method instead of the lower level methods: generateRenderData(...) and addModelMeshRenderData(...) bool send_ModelMeshRenderData_to_ServerSide( MESH_SHADING_TYPE shadingType, GLenum glPrimType = GL_TRIANGLES, MESH_RENDER_TYPE renderType = MESH_RENDER_TYPE::mrtVBO ); /** This method is invoked by the ManagerModelMultiplicity and uses the data from the in passed ModelMeshRenderDataGenerator* renderDataGenerator to construct the Indexed Geometry (VBO or VA), generate all necessary OpenGL handles, send the data to graphics memory, and everything else necessary to make this instance's render() method correctly draw the model. */ virtual void generateRenderData( MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType = MESH_RENDER_TYPE::mrtVBO, GLenum glPrimType = GL_TRIANGLES ); /** This method is called manually when the underlying ModelMeshRenderData for a specific shading/render type is already generated, but the underlying generator has changed size, so mapping isn't sufficient. */ virtual void regenerateRenderData(MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType, GLenum glPrimType); virtual bool hasGeneratedRenderData( MESH_SHADING_TYPE shadingType, GLenum primType ); std::string toString() const; virtual void render( ModelMeshSkin& skin ); virtual void renderSelection( const ModelMeshSkin& skin, GLubyte red, GLubyte green, GLubyte blue ); bool isUsingVBO( MESH_SHADING_TYPE shadingType, GLenum glPrimType ); bool isUsingVA( MESH_SHADING_TYPE shadingType, GLenum glPrimType ); ModelMeshRenderDataGenerator* getRenderDataGenerator(); void setRenderDataGenerator( std::unique_ptr g ); ModelMeshRenderData* getModelMeshRenderData( MESH_SHADING_TYPE shadingType, GLenum primType ); /// This method simply inserts the corresponding render data and shading info into this ModelMeshDataShared /// AND also SENDS the data data to OpenGL -- this MUST be called from the main thread which owns the /// OpenGL context. If you are calling from a worker thread, use the 3rd constructor to defer making any /// OpenGL calls (thread safe). /// /// For example, the Asset Importer library (assimp) performs the primitive type conversion and shading /// operators that the ModelMeshRenderDataGenerator usually does. If assimp already does this, we don't /// want to redo that work. /// It's also likely that assimp does this conversion more efficiently and timely than the generator class. /// The user must also pass in the corresponding shading type of the contained render data (smooth or flat). /// The user must also pass in the OpenGL Primitive type of the rendered primitives, ie GL_TRIANGLES, /// GL_TRIANGLE_STRIP, GL_POINTS, etc. /// /// The in passed data becomes *owned* by this instanced. /// /// Nothing will be usable by the Graphics API *until* after calling this method, the user/AftrBurner /// also invokes this->send_ModelDataShared_to_ServerSide(...). This invocation *must* be performed /// on the same thread which created the OpenGL context or undefined behavior of OpenGL will occur. /// Typically, the ManagerModelMultiplicity::sendModelDataSharedServerSide() automatically takes care /// of this. /// /// If an existing renderData is already associated with the corresponding shadingType & glPrimType, the /// bool overwriteExistingMMRD is used to determine if the existing renderData will be destroyed and replaced /// with the in pass renderData. If overwriteExistingMMRD is true, the existing renderData will be replaced, /// otherwise it will not be replaced. /// If destroyCPUSideDataAfterSendingToGPU is true, the CPU's copy of the render data (vertex info, index info, etc) will /// be deleted once a GPU-side copy has been made. Deleting it is free CPU-side memory, but the CPU may lost quick /// access to the total set of vertices which may want to be used by the physics engine at a later time. bool addModelMeshRenderData( ModelMeshRenderData* renderData, const MESH_SHADING_TYPE& shadingType, GLenum glPrimType, bool overwriteExistingMMRD = true, bool destroyCPUSideDataAfterSendingToGPU = true ); //modifies the underlying model mesh's modelMeshRenderData to re-sample the vertices //currently only works for points, re-sampling techniques should be possible for other primitive shapes //but may not make sense for all models (under sampling triangles will leave gaps in the model). //Currently uses the currently selected skin's prim-type, render-type pair, could be extended to modify //a specified pairing. //Factor is the under sampling factor relative to the ORIGINAL data (easy enough for sequential data, may //take more thought for other types, i.e. passing in 2 will give you half as many original points, //followed by a subsequent call of 3 will give you one third as many points, not one sixth. void underSampleMeshWithFactor( int factor, const ModelMeshSkin& skin ); protected: void initMemberData( std::unique_ptr renderDataGenerator ); void sendDataServerSide( ModelMeshRenderData& r ); void sendNormalDataServerSide(ModelMeshRenderData& r);///< For rendering normals in a 3.2+ core compliant manner, but currently not called in dev branch(SLN) /// Saves the in-passed ModelMeshRenderData and corresponding parameters and will send them to OpenGL on the /// next invocation of send_ModelMeshDataShared_to_ServerSide(...). This let's a worker thread create the MMRD but /// the main thread owning the GL context to actually send to GL via send_ModelMeshDataShared_to_ServerSide(...). void prepareToSend_MMRD( ModelMeshRenderData* mmrd, MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType, GLenum glPrimType ); virtual void render( ModelMeshSkin& skin, ModelMeshRenderData& r ); virtual void renderMeshCompatibility( ModelMeshSkin& skin, const ModelMeshRenderData& r ); virtual void renderMeshGL32( ModelMeshSkin& skin, const ModelMeshRenderData& r ); virtual void renderNormals( ModelMeshSkin& skin, ModelMeshRenderData& r ); virtual void renderNormals32(ModelMeshSkin& skin, ModelMeshRenderData& r); virtual void renderSelection( const ModelMeshSkin& skin, const ModelMeshRenderData& r, GLubyte red, GLubyte green, GLubyte blue ); virtual void renderMeshSelectionCompatibility( const ModelMeshSkin& skin, const ModelMeshRenderData& r, GLubyte red, GLubyte green, GLubyte blue ); virtual void renderMeshSelectionGL32( const ModelMeshSkin& skin, const ModelMeshRenderData& r ); ///Map of shading type and GL Primitive Type: (GL_TRIANGLES, GL_POINTS, GL_TRIANGLE_STRIP, etc std::map< std::pair< MESH_SHADING_TYPE , GLenum >, ModelMeshRenderData* > renderDataMap; std::unique_ptr renderDataGenerator = nullptr; ///< Mesh data populated from parser //Used by the 3rd constructor or the addModelMeshRenderData(...) method struct MMRD_to_send { std::unique_ptr mmrd = nullptr; bool destroyCPUSideDataAfterSendingToGPU = false; MESH_SHADING_TYPE shadingType = MESH_SHADING_TYPE::mstAUTO; GLenum glPrimType = GL_TRIANGLES; MESH_RENDER_TYPE renderType = MESH_RENDER_TYPE::mrtVBO; }; std::optional< MMRD_to_send > mmrd_to_send_to_gpu; ///< 3rd constructor will populate this member. Subsequent //call to send_ModelDataShared_to_ServerSide(...) will invoke OpenGL functions //send to GPU memory / register w/ OpenGL. }; }