diff --git a/Makefile b/Makefile index 038a1b1c..81398ca3 100644 --- a/Makefile +++ b/Makefile @@ -390,7 +390,6 @@ endif .PHONY: binaries binaries: \ - binaries-assimp \ binaries-tools \ binaries-radiant \ @@ -400,10 +399,6 @@ binaries-radiant: \ binaries-radiant-plugins \ binaries-radiant-core \ -.PHONY: binaries-assimp -binaries-assimp: \ - $(INSTALLDIR)/assimp_.$(DLL) \ - .PHONY: binaries-radiant-modules binaries-radiant-modules: \ $(INSTALLDIR)/modules/archivepak.$(DLL) \ @@ -416,8 +411,7 @@ binaries-radiant-modules: \ $(INSTALLDIR)/modules/imageq2.$(DLL) \ $(INSTALLDIR)/modules/mapq3.$(DLL) \ $(INSTALLDIR)/modules/mapxml.$(DLL) \ - $(INSTALLDIR)/modules/md3model.$(DLL) \ - $(INSTALLDIR)/modules/model.$(DLL) \ + $(INSTALLDIR)/modules/assmodel.$(DLL) \ $(INSTALLDIR)/modules/shaders.$(DLL) \ $(INSTALLDIR)/modules/vfspk3.$(DLL) \ @@ -528,8 +522,11 @@ else $(INSTALLDIR)/q3map2.$(EXE): LDFLAGS_EXTRA := -Wl,--stack,4194304 endif endif -$(INSTALLDIR)/q3map2.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_PNG) $(LIBS_JPEG) $(LIBS_ZLIB) -$(INSTALLDIR)/q3map2.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_PNG) $(CPPFLAGS_JPEG) -Itools/quake3/common -Ilibs -Iinclude +ifneq ($(OS),Win32) +$(INSTALLDIR)/q3map2.$(EXE): LDFLAGS_EXTRA += -Wl,-rpath '-Wl,$$ORIGIN' +endif +$(INSTALLDIR)/q3map2.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_PNG) $(LIBS_JPEG) $(LIBS_ZLIB) -lassimp_ -L$(INSTALLDIR) +$(INSTALLDIR)/q3map2.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_PNG) $(CPPFLAGS_JPEG) -Itools/quake3/common -Ilibs -Iinclude -Ilibs/assimp/include $(INSTALLDIR)/q3map2.$(EXE): \ tools/quake3/common/cmdlib.o \ tools/quake3/common/imagelib.o \ @@ -588,7 +585,7 @@ $(INSTALLDIR)/q3map2.$(EXE): \ libddslib.$(A) \ libfilematch.$(A) \ libl_net.$(A) \ - libpicomodel.$(A) \ + $(INSTALLDIR)/libassimp_.$(DLL) \ $(if $(findstring Win32,$(OS)),icons/q3map2.o,) \ libmathlib.$(A): CPPFLAGS_EXTRA := -Ilibs @@ -630,9 +627,9 @@ libpicomodel.$(A): \ libs/picomodel/pm_obj.o \ libs/picomodel/pm_terrain.o \ -$(INSTALLDIR)/assimp_.$(DLL): LIBS_EXTRA := $(LIBS_ZLIB) -$(INSTALLDIR)/assimp_.$(DLL): CPPFLAGS_EXTRA := $(CPPFLAGS_ZLIB) -Ilibs/assimp/include -Ilibs/assimp/code -Ilibs/assimp/contrib/pugixml/src -Ilibs/assimp/contrib/unzip -Ilibs/assimp -Ilibs/assimp/contrib/openddlparser/include -Ilibs/assimp/contrib/rapidjson/include -Ilibs/assimp/contrib -DASSIMP_BUILD_DLL_EXPORT -DASSIMP_BUILD_NO_C4D_IMPORTER -DASSIMP_BUILD_NO_EXPORT -DASSIMP_BUILD_NO_IFC_IMPORTER -DASSIMP_BUILD_NO_OWN_ZLIB -DASSIMP_IMPORTER_GLTF_USE_OPEN3DGC=1 -DMINIZ_USE_UNALIGNED_LOADS_AND_STORES=0 -DOPENDDLPARSER_BUILD -DRAPIDJSON_HAS_STDSTRING=1 -DRAPIDJSON_NOMEMBERITERATORCLASS -DWIN32_LEAN_AND_MEAN -Dassimp_EXPORTS -fvisibility=hidden -Wno-long-long -Wa,-mbig-obj -fexceptions -frtti -$(INSTALLDIR)/assimp_.$(DLL): \ +$(INSTALLDIR)/libassimp_.$(DLL): LIBS_EXTRA := $(LIBS_ZLIB) +$(INSTALLDIR)/libassimp_.$(DLL): CPPFLAGS_EXTRA := $(CPPFLAGS_ZLIB) -Ilibs/assimp/include -Ilibs/assimp/code -Ilibs/assimp/contrib/pugixml/src -Ilibs/assimp/contrib/unzip -Ilibs/assimp -Ilibs/assimp/contrib/openddlparser/include -Ilibs/assimp/contrib/rapidjson/include -Ilibs/assimp/contrib -DASSIMP_BUILD_DLL_EXPORT -DASSIMP_BUILD_NO_C4D_IMPORTER -DASSIMP_BUILD_NO_EXPORT -DASSIMP_BUILD_NO_IFC_IMPORTER -DASSIMP_BUILD_NO_OWN_ZLIB -DASSIMP_IMPORTER_GLTF_USE_OPEN3DGC=1 -DMINIZ_USE_UNALIGNED_LOADS_AND_STORES=0 -DOPENDDLPARSER_BUILD -DRAPIDJSON_HAS_STDSTRING=1 -DRAPIDJSON_NOMEMBERITERATORCLASS -DWIN32_LEAN_AND_MEAN -Dassimp_EXPORTS -fvisibility=hidden -Wno-long-long -fexceptions -frtti +$(INSTALLDIR)/libassimp_.$(DLL): \ libs/assimp/code/Common/Assimp.o \ libs/assimp/code/CApi/CInterfaceIOWrapper.o \ libs/assimp/code/Common/BaseImporter.o \ @@ -1077,21 +1074,16 @@ $(INSTALLDIR)/modules/mapxml.$(DLL): \ plugins/mapxml/xmlparse.o \ plugins/mapxml/xmlwrite.o \ -$(INSTALLDIR)/modules/md3model.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude -$(INSTALLDIR)/modules/md3model.$(DLL): \ - plugins/md3model/md2.o \ - plugins/md3model/md3.o \ - plugins/md3model/md5.o \ - plugins/md3model/mdc.o \ - plugins/md3model/mdlimage.o \ - plugins/md3model/mdl.o \ - plugins/md3model/plugin.o \ - -$(INSTALLDIR)/modules/model.$(DLL): LIBS_EXTRA := -lassimp_ -L$(INSTALLDIR) -$(INSTALLDIR)/modules/model.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude -Ilibs/assimp/include -$(INSTALLDIR)/modules/model.$(DLL): \ - plugins/model/model.o \ - plugins/model/plugin.o \ +ifneq ($(OS),Win32) +$(INSTALLDIR)/modules/assmodel.$(DLL): LDFLAGS_EXTRA := -Wl,-rpath '-Wl,$$ORIGIN/..' +endif +$(INSTALLDIR)/modules/assmodel.$(DLL): LIBS_EXTRA := -lassimp_ -L$(INSTALLDIR) +$(INSTALLDIR)/modules/assmodel.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude -Ilibs/assimp/include +$(INSTALLDIR)/modules/assmodel.$(DLL): \ + plugins/assmodel/mdlimage.o \ + plugins/assmodel/model.o \ + plugins/assmodel/plugin.o \ + $(INSTALLDIR)/libassimp_.$(DLL) \ $(INSTALLDIR)/modules/shaders.$(DLL): LIBS_EXTRA := $(LIBS_GLIB) $(INSTALLDIR)/modules/shaders.$(DLL): CPPFLAGS_EXTRA := $(CPPFLAGS_GLIB) -Ilibs -Iinclude diff --git a/libs/scenelib.h b/libs/scenelib.h index 1ad4d676..f204244b 100644 --- a/libs/scenelib.h +++ b/libs/scenelib.h @@ -107,7 +107,7 @@ public: typedef MemberCaller, &NodeType::initialise> InitialiseCaller; TypeId getTypeId(){ #if defined( _DEBUG ) - ASSERT_MESSAGE( m_typeId != NODETYPEID_NONE, "node-type " << makeQuoted( Name ) << " used before being initialised" ); + ASSERT_MESSAGE( m_typeId != NODETYPEID_NONE, "node-type " << makeQuoted( Type::Name ) << " used before being initialised" ); #endif return m_typeId; } diff --git a/plugins/assmodel/mdlimage.cpp b/plugins/assmodel/mdlimage.cpp new file mode 100644 index 00000000..d0e11154 --- /dev/null +++ b/plugins/assmodel/mdlimage.cpp @@ -0,0 +1,49 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "mdlimage.h" + +#include "imagelib.h" + +#include "assimp/Importer.hpp" +#include "assimp/scene.h" + + +Image* LoadMDLImage( Assimp::Importer& importer, ArchiveFile& file ){ + const aiScene *scene = importer.GetScene(); + if( scene != nullptr && scene->HasTextures() ){ + const aiTexture *tex = scene->mTextures[0]; + if( tex->mWidth != 0 ){ // not compressed + RGBAImage* image = new RGBAImage( tex->mWidth, tex->mHeight ); + unsigned char* pRGBA = image->getRGBAPixels(); + const aiTexel *texel = tex->pcData; + for( size_t i = 0; i < tex->mWidth * tex->mHeight; ++i, ++texel ){ + *pRGBA++ = texel->r; + *pRGBA++ = texel->g; + *pRGBA++ = texel->b; + *pRGBA++ = texel->a; + } + return image; + } + } + return nullptr; +} + diff --git a/plugins/assmodel/mdlimage.h b/plugins/assmodel/mdlimage.h new file mode 100644 index 00000000..82de20cc --- /dev/null +++ b/plugins/assmodel/mdlimage.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_MDLIMAGE_H ) +#define INCLUDED_MDLIMAGE_H + +class Image; +class ArchiveFile; +namespace Assimp{ +class Importer; +} + +Image* LoadMDLImage( Assimp::Importer& importer, ArchiveFile& file ); + +#endif diff --git a/plugins/assmodel/model.cpp b/plugins/assmodel/model.cpp new file mode 100644 index 00000000..1d70dff9 --- /dev/null +++ b/plugins/assmodel/model.cpp @@ -0,0 +1,704 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "model.h" + +#include "iarchive.h" +#include "idatastream.h" +#include "imodel.h" +#include "modelskin.h" + +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" + +#include "math/frustum.h" +#include "string/string.h" +#include "generic/static.h" +#include "shaderlib.h" +#include "scenelib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "render.h" + +#include "assimp/Importer.hpp" +#include "assimp/postprocess.h" +#include "assimp/scene.h" +#include "assimp/mesh.h" +#include "os/path.h" +#include "stream/stringstream.h" + +class VectorLightList : public LightList +{ + typedef std::vector Lights; + Lights m_lights; +public: + void addLight( const RendererLight& light ){ + m_lights.push_back( &light ); + } + void clear(){ + m_lights.clear(); + } + void evaluateLights() const { + } + void lightsChanged() const { + } + void forEachLight( const RendererLightCallback& callback ) const { + for ( Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i ) + { + callback( *( *i ) ); + } + } +}; + +struct AssScene +{ + const aiScene* m_scene; + const char* m_rootPath; + const char* m_matName; // forced global mat name, if not null +}; + +class PicoSurface final : + public OpenGLRenderable +{ + AABB m_aabb_local; + CopiedString m_shader; + Shader* m_state; + + Array m_vertices; + Array m_indices; + +public: + + PicoSurface(){ + constructNull(); + CaptureShader(); + } + PicoSurface( const AssScene scene, const aiMesh* mesh ){ + CopyPicoSurface( scene, mesh ); + CaptureShader(); + } + ~PicoSurface(){ + ReleaseShader(); + } + + void render( RenderStateFlags state ) const { + if ( ( state & RENDER_BUMP ) != 0 ) { + if ( GlobalShaderCache().useShaderLanguage() ) { + glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->normal ); + glVertexAttribPointerARB( c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->texcoord ); + glVertexAttribPointerARB( c_attr_Tangent, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->tangent ); + glVertexAttribPointerARB( c_attr_Binormal, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->bitangent ); + } + else + { + glVertexAttribPointerARB( 11, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->normal ); + glVertexAttribPointerARB( 8, 2, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->texcoord ); + glVertexAttribPointerARB( 9, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->tangent ); + glVertexAttribPointerARB( 10, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->bitangent ); + } + } + else + { + glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->normal ); + glTexCoordPointer( 2, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->texcoord ); + } + glVertexPointer( 3, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_vertices.data()->vertex ); + glDrawElements( GL_TRIANGLES, GLsizei( m_indices.size() ), RenderIndexTypeID, m_indices.data() ); + +#if defined( _DEBUG ) && !defined( _DEBUG_QUICKER ) + GLfloat modelview[16]; + glGetFloatv( GL_MODELVIEW_MATRIX, modelview ); // I know this is slow as hell, but hey - we're in _DEBUG + Matrix4 modelview_inv( + modelview[0], modelview[1], modelview[2], modelview[3], + modelview[4], modelview[5], modelview[6], modelview[7], + modelview[8], modelview[9], modelview[10], modelview[11], + modelview[12], modelview[13], modelview[14], modelview[15] ); + matrix4_full_invert( modelview_inv ); + Matrix4 modelview_inv_transposed = matrix4_transposed( modelview_inv ); + + glBegin( GL_LINES ); + + for ( Array::const_iterator i = m_vertices.begin(); i != m_vertices.end(); ++i ) + { + Vector3 normal = normal3f_to_vector3( ( *i ).normal ); + normal = matrix4_transformed_direction( modelview_inv, vector3_normalised( matrix4_transformed_direction( modelview_inv_transposed, normal ) ) ); // do some magic + Vector3 normalTransformed = vector3_added( vertex3f_to_vector3( ( *i ).vertex ), vector3_scaled( normal, 8 ) ); + glVertex3fv( vertex3f_to_array( ( *i ).vertex ) ); + glVertex3fv( vector3_to_array( normalTransformed ) ); + } + glEnd(); +#endif + } + + VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const { + return test.TestAABB( m_aabb_local, localToWorld ); + } + + const AABB& localAABB() const { + return m_aabb_local; + } + + void render( Renderer& renderer, const Matrix4& localToWorld, Shader* state ) const { + renderer.SetState( state, Renderer::eFullMaterials ); + renderer.addRenderable( *this, localToWorld ); + } + + void render( Renderer& renderer, const Matrix4& localToWorld ) const { + render( renderer, localToWorld, m_state ); + } + + void testSelect( Selector& selector, SelectionTest& test, const Matrix4& localToWorld ){ + test.BeginMesh( localToWorld, true ); + + SelectionIntersection best; + testSelect( test, best ); + if ( best.valid() ) { + selector.addIntersection( best ); + } + } + + const char* getShader() const { + return m_shader.c_str(); + } + Shader* getState() const { + return m_state; + } + +private: + + void CaptureShader(){ + m_state = GlobalShaderCache().capture( m_shader.c_str() ); + } + void ReleaseShader(){ + GlobalShaderCache().release( m_shader.c_str() ); + } + + void UpdateAABB(){ + m_aabb_local = AABB(); + for ( std::size_t i = 0; i < m_vertices.size(); ++i ) + aabb_extend_by_point_safe( m_aabb_local, reinterpret_cast( m_vertices[i].vertex ) ); + + + for ( Array::iterator i = m_indices.begin(); i != m_indices.end(); i += 3 ) + { + ArbitraryMeshVertex& a = m_vertices[*( i + 0 )]; + ArbitraryMeshVertex& b = m_vertices[*( i + 1 )]; + ArbitraryMeshVertex& c = m_vertices[*( i + 2 )]; + + ArbitraryMeshTriangle_sumTangents( a, b, c ); + } + + for ( Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i ) + { + vector3_normalise( reinterpret_cast( ( *i ).tangent ) ); + vector3_normalise( reinterpret_cast( ( *i ).bitangent ) ); + } + } + + void testSelect( SelectionTest& test, SelectionIntersection& best ){ + test.TestTriangles( + VertexPointer( VertexPointer::pointer( &m_vertices.data()->vertex ), sizeof( ArbitraryMeshVertex ) ), + IndexPointer( m_indices.data(), IndexPointer::index_type( m_indices.size() ) ), + best + ); + } + + void CopyPicoSurface( const AssScene scene, const aiMesh* mesh ){ + if( scene.m_matName != nullptr ){ + m_shader = scene.m_matName; + } + else{ + aiMaterial *material = scene.m_scene->mMaterials[mesh->mMaterialIndex]; + + aiString matname = material->GetName(); +#ifdef _DEBUG + globalOutputStream() << "matname: " << matname.C_Str() << "\n"; +#endif + aiString texname; + if( aiReturn_SUCCESS == material->Get( AI_MATKEY_TEXTURE_DIFFUSE(0), texname ) + && texname.length != 0 ){ +#ifdef _DEBUG + globalOutputStream() << "texname: " << texname.C_Str() << "\n"; +#endif + m_shader = StringOutputStream()( PathCleaned( PathExtensionless( texname.C_Str() ) ) ); + + } + else{ + m_shader = StringOutputStream()( PathCleaned( PathExtensionless( matname.C_Str() ) ) ); + } + + const CopiedString oldShader( m_shader ); + if( strchr( m_shader.c_str(), '/' ) == nullptr ){ /* texture is likely in the folder, where model is */ + m_shader = StringOutputStream()( scene.m_rootPath, m_shader.c_str() ); + } + else{ + const char *name = m_shader.c_str(); + if( name[0] == '/' || ( name[0] != '\0' && name[1] == ':' ) || strstr( name, ".." ) ){ /* absolute path or with .. */ + const char* p; + if( ( p = string_in_string_nocase( name, "/models/" ) ) + || ( p = string_in_string_nocase( name, "/textures/" ) ) ){ + m_shader = p + 1; + } + else{ + m_shader = StringOutputStream()( scene.m_rootPath, path_get_filename_start( name ) ); + } + } + } + + if( oldShader != m_shader ) + globalOutputStream() << "substituting: " << oldShader.c_str() << " -> " << m_shader.c_str() << "\n"; + } + + m_vertices.resize( mesh->mNumVertices ); + m_indices.resize( mesh->mNumFaces * 3 ); + + for ( std::size_t i = 0; i < m_vertices.size(); ++i ) + { + m_vertices[i].vertex = { mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z }; + + if( mesh->HasNormals() ) + m_vertices[i].normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z }; + + if( mesh->HasTextureCoords( 0 ) ) + m_vertices[i].texcoord = { mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y }; +#if 0 + if( mesh->HasTangentsAndBitangents() ){ + m_vertices[i].tangent = { mesh->mTangents[i].x, mesh->mTangents[i].y, mesh->mTangents[i].z }; + m_vertices[i].bitangent = { mesh->mBitangents[i].x, mesh->mBitangents[i].y, mesh->mBitangents[i].z }; + } +#endif +#if 0 + picoVec_t* color = PicoGetSurfaceColor( surface, 0, int(i) ); + m_vertices[i].colour = Colour4b( color[0], color[1], color[2], color[3] ); +#endif + } + + size_t idCopied = 0; + for ( size_t t = 0; t < mesh->mNumFaces; ++t ){ + const aiFace& face = mesh->mFaces[t]; + // if( face.mNumIndices == 3 ) + for ( size_t i = 0; i < 3; i++ ){ + m_indices[idCopied++] = face.mIndices[i]; + } + } + + UpdateAABB(); + } + + void constructQuad( std::size_t index, const Vector3& a, const Vector3& b, const Vector3& c, const Vector3& d, const Vector3& normal ){ + m_vertices[index * 4 + 0] = ArbitraryMeshVertex( + vertex3f_for_vector3( a ), + normal3f_for_vector3( normal ), + texcoord2f_from_array( aabb_texcoord_topleft ) + ); + m_vertices[index * 4 + 1] = ArbitraryMeshVertex( + vertex3f_for_vector3( b ), + normal3f_for_vector3( normal ), + texcoord2f_from_array( aabb_texcoord_topright ) + ); + m_vertices[index * 4 + 2] = ArbitraryMeshVertex( + vertex3f_for_vector3( c ), + normal3f_for_vector3( normal ), + texcoord2f_from_array( aabb_texcoord_botright ) + ); + m_vertices[index * 4 + 3] = ArbitraryMeshVertex( + vertex3f_for_vector3( d ), + normal3f_for_vector3( normal ), + texcoord2f_from_array( aabb_texcoord_botleft ) + ); + } + + void constructNull(){ + AABB aabb( Vector3( 0, 0, 0 ), Vector3( 8, 8, 8 ) ); + + Vector3 points[8]; + aabb_corners( aabb, points ); + + m_vertices.resize( 24 ); + + constructQuad( 0, points[2], points[1], points[5], points[6], aabb_normals[0] ); + constructQuad( 1, points[1], points[0], points[4], points[5], aabb_normals[1] ); + constructQuad( 2, points[0], points[1], points[2], points[3], aabb_normals[2] ); + constructQuad( 3, points[0], points[3], points[7], points[4], aabb_normals[3] ); + constructQuad( 4, points[3], points[2], points[6], points[7], aabb_normals[4] ); + constructQuad( 5, points[7], points[6], points[5], points[4], aabb_normals[5] ); + + m_indices.resize( 36 ); + + RenderIndex indices[36] = { + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + 12, 13, 14, 12, 14, 15, + 16, 17, 18, 16, 18, 19, + 20, 21, 22, 20, 22, 23, + }; + + + Array::iterator j = m_indices.begin(); + for ( RenderIndex* i = indices; i != indices + ( sizeof( indices ) / sizeof( RenderIndex ) ); ++i ) + { + *j++ = *i; + } + + m_shader = ""; + + UpdateAABB(); + } +}; + + + +class PicoModel : + public Cullable, + public Bounded +{ + typedef std::vector surfaces_t; + surfaces_t m_surfaces; + + AABB m_aabb_local; +public: + Callback m_lightsChanged; + + PicoModel(){ + constructNull(); + } + PicoModel( const AssScene scene ){ + m_aabb_local = AABB(); + CopyPicoModel( scene, scene.m_scene->mRootNode ); + } + ~PicoModel(){ + for ( surfaces_t::iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i ) + delete *i; + } + + typedef surfaces_t::const_iterator const_iterator; + + const_iterator begin() const { + return m_surfaces.begin(); + } + const_iterator end() const { + return m_surfaces.end(); + } + std::size_t size() const { + return m_surfaces.size(); + } + + VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const { + return test.TestAABB( m_aabb_local, localToWorld ); + } + + virtual const AABB& localAABB() const { + return m_aabb_local; + } + + void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, std::vector states ) const { + for ( surfaces_t::const_iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i ) + { + if ( ( *i )->intersectVolume( volume, localToWorld ) != c_volumeOutside ) { + ( *i )->render( renderer, localToWorld, states[i - m_surfaces.begin()] ); + } + } + } + + void testSelect( Selector& selector, SelectionTest& test, const Matrix4& localToWorld ){ + for ( surfaces_t::iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i ) + { + if ( ( *i )->intersectVolume( test.getVolume(), localToWorld ) != c_volumeOutside ) { + ( *i )->testSelect( selector, test, localToWorld ); + } + } + } + +private: + void CopyPicoModel( const AssScene scene, const aiNode* node ){ + for( size_t n = 0; n < node->mNumMeshes; ++n ){ + const aiMesh *mesh = scene.m_scene->mMeshes[node->mMeshes[n]]; + if( mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ){ + PicoSurface* picosurface = new PicoSurface( scene, mesh ); + aabb_extend_by_aabb_safe( m_aabb_local, picosurface->localAABB() ); + m_surfaces.push_back( picosurface ); + } + } + + // traverse all children + for ( size_t n = 0; n < node->mNumChildren; ++n ){ + CopyPicoModel( scene, node->mChildren[n] ); + } + } + void constructNull(){ + PicoSurface* picosurface = new PicoSurface(); + m_aabb_local = picosurface->localAABB(); + m_surfaces.push_back( picosurface ); + } +}; + +inline void Surface_addLight( PicoSurface& surface, VectorLightList& lights, const Matrix4& localToWorld, const RendererLight& light ){ + if ( light.testAABB( aabb_for_oriented_aabb( surface.localAABB(), localToWorld ) ) ) { + lights.addLight( light ); + } +} + +class PicoModelInstance : + public scene::Instance, + public Renderable, + public SelectionTestable, + public LightCullable, + public SkinnedModel +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts(){ + InstanceContainedCast::install( m_casts ); + InstanceContainedCast::install( m_casts ); + InstanceStaticCast::install( m_casts ); + InstanceStaticCast::install( m_casts ); + InstanceStaticCast::install( m_casts ); + } + InstanceTypeCastTable& get(){ + return m_casts; + } + }; + + PicoModel& m_picomodel; + + const LightList* m_lightList; + typedef Array SurfaceLightLists; + SurfaceLightLists m_surfaceLightLists; + + class Remap + { + public: + CopiedString first; + Shader* second; + Remap() : second( 0 ){ + } + }; + typedef Array SurfaceRemaps; + SurfaceRemaps m_skins; +public: + typedef LazyStatic StaticTypeCasts; + + void* m_test; + + Bounded& get( NullType){ + return m_picomodel; + } + Cullable& get( NullType){ + return m_picomodel; + } + + void lightsChanged(){ + m_lightList->lightsChanged(); + } + typedef MemberCaller LightsChangedCaller; + + void constructRemaps(){ + ASSERT_MESSAGE( m_skins.size() == m_picomodel.size(), "ERROR" ); + ModelSkin* skin = NodeTypeCast::cast( path().parent() ); + if ( skin != 0 && skin->realised() ) { + SurfaceRemaps::iterator j = m_skins.begin(); + for ( PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i, ++j ) + { + const char* remap = skin->getRemap( ( *i )->getShader() ); + if ( !string_empty( remap ) ) { + ( *j ).first = remap; + ( *j ).second = GlobalShaderCache().capture( remap ); + } + else + { + ( *j ).second = 0; + } + } + SceneChangeNotify(); + } + } + void destroyRemaps(){ + ASSERT_MESSAGE( m_skins.size() == m_picomodel.size(), "ERROR" ); + for ( SurfaceRemaps::iterator i = m_skins.begin(); i != m_skins.end(); ++i ) + { + if ( ( *i ).second != 0 ) { + GlobalShaderCache().release( ( *i ).first.c_str() ); + ( *i ).second = 0; + } + } + } + void skinChanged(){ + destroyRemaps(); + constructRemaps(); + } + + PicoModelInstance( const PicoModelInstance& ) = delete; // not copyable + PicoModelInstance operator=( const PicoModelInstance& ) = delete; // not assignable + + PicoModelInstance( const scene::Path& path, scene::Instance* parent, PicoModel& picomodel ) : + Instance( path, parent, this, StaticTypeCasts::instance().get() ), + m_picomodel( picomodel ), + m_surfaceLightLists( m_picomodel.size() ), + m_skins( m_picomodel.size() ){ + m_lightList = &GlobalShaderCache().attach( *this ); + m_picomodel.m_lightsChanged = LightsChangedCaller( *this ); + + Instance::setTransformChangedCallback( LightsChangedCaller( *this ) ); + + constructRemaps(); + } + ~PicoModelInstance(){ + destroyRemaps(); + + Instance::setTransformChangedCallback( Callback() ); + + m_picomodel.m_lightsChanged = Callback(); + GlobalShaderCache().detach( *this ); + } + + void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const { + SurfaceLightLists::const_iterator j = m_surfaceLightLists.begin(); + SurfaceRemaps::const_iterator k = m_skins.begin(); + for ( PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i, ++j, ++k ) + { + if ( ( *i )->intersectVolume( volume, localToWorld ) != c_volumeOutside ) { + renderer.setLights( *j ); + ( *i )->render( renderer, localToWorld, ( *k ).second != 0 ? ( *k ).second : ( *i )->getState() ); + } + } + } + + void renderSolid( Renderer& renderer, const VolumeTest& volume ) const { + m_lightList->evaluateLights(); + + render( renderer, volume, Instance::localToWorld() ); + } + void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const { + renderSolid( renderer, volume ); + } + + void testSelect( Selector& selector, SelectionTest& test ){ + m_picomodel.testSelect( selector, test, Instance::localToWorld() ); + } + + bool testLight( const RendererLight& light ) const { + return light.testAABB( worldAABB() ); + } + void insertLight( const RendererLight& light ){ + const Matrix4& localToWorld = Instance::localToWorld(); + SurfaceLightLists::iterator j = m_surfaceLightLists.begin(); + for ( PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i ) + { + Surface_addLight( *( *i ), *j++, localToWorld, light ); + } + } + void clearLights(){ + for ( SurfaceLightLists::iterator i = m_surfaceLightLists.begin(); i != m_surfaceLightLists.end(); ++i ) + { + ( *i ).clear(); + } + } +}; + +class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts(){ + NodeStaticCast::install( m_casts ); + } + NodeTypeCastTable& get(){ + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + PicoModel m_picomodel; + +public: + typedef LazyStatic StaticTypeCasts; + + PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){ + } + PicoModelNode( const AssScene scene ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_picomodel( scene ){ + } + + void release(){ + delete this; + } + scene::Node& node(){ + return m_node; + } + + scene::Instance* create( const scene::Path& path, scene::Instance* parent ){ + return new PicoModelInstance( path, parent, m_picomodel ); + } + void forEachInstance( const scene::Instantiable::Visitor& visitor ){ + m_instances.forEachInstance( visitor ); + } + void insert( scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance ){ + m_instances.insert( observer, path, instance ); + } + scene::Instance* erase( scene::Instantiable::Observer* observer, const scene::Path& path ){ + return m_instances.erase( observer, path ); + } +}; + + + +scene::Node& loadPicoModel( Assimp::Importer& importer, ArchiveFile& file ){ + unsigned flags = //aiProcessPreset_TargetRealtime_Fast + // | aiProcess_FixInfacingNormals + aiProcess_GenNormals + | aiProcess_JoinIdenticalVertices + | aiProcess_Triangulate + | aiProcess_GenUVCoords + | aiProcess_SortByPType + | aiProcess_FindDegenerates + | aiProcess_FindInvalidData + | aiProcess_ValidateDataStructure + | aiProcess_FlipUVs + | aiProcess_FlipWindingOrder + | aiProcess_PreTransformVertices; + // rotate the whole scene 90 degrees around the x axis to convert assimp's Y = UP to Quakes's Z = UP + importer.SetPropertyMatrix( AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4( 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 ) ); // aiMatrix4x4::RotationX( c_half_pi ) + + const aiScene *scene = importer.ReadFile( file.getName(), flags ); + + if( scene != nullptr ){ + if( scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE ) + globalWarningStream() << "AI_SCENE_FLAGS_INCOMPLETE\n"; + const char *ext = path_get_extension( file.getName() ); + const auto rootPath = StringOutputStream()( PathFilenameless( file.getName() ) ); + const auto matName = StringOutputStream()( PathExtensionless( file.getName() ) ); + return ( new PicoModelNode( AssScene{ scene, rootPath, string_equal_nocase( ext, "mdl" )? matName.c_str() : nullptr } ) )->node(); + } + else{ + return ( new PicoModelNode() )->node(); + } +} diff --git a/plugins/assmodel/model.h b/plugins/assmodel/model.h new file mode 100644 index 00000000..19d6523a --- /dev/null +++ b/plugins/assmodel/model.h @@ -0,0 +1,34 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_MODEL_H ) +#define INCLUDED_MODEL_H + +namespace scene { +class Node; +} +class ArchiveFile; +namespace Assimp{ +class Importer; +} +scene::Node& loadPicoModel( Assimp::Importer& importer, ArchiveFile& file ); + +#endif diff --git a/plugins/assmodel/plugin.cpp b/plugins/assmodel/plugin.cpp new file mode 100644 index 00000000..6623d48c --- /dev/null +++ b/plugins/assmodel/plugin.cpp @@ -0,0 +1,374 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "plugin.h" + +#include +typedef unsigned char byte; +#include +#include +#include + +#include "iscenegraph.h" +#include "irender.h" +#include "iselection.h" +#include "iimage.h" +#include "imodel.h" +#include "igl.h" +#include "ifilesystem.h" +#include "iundo.h" +#include "ifiletypes.h" + +#include "modulesystem/singletonmodule.h" +#include "stream/textstream.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "typesystem.h" + +#include "model.h" + +#include "assimp/Importer.hpp" +#include "assimp/importerdesc.h" +#include "assimp/Logger.hpp" +#include "assimp/DefaultLogger.hpp" +#include "assimp/IOSystem.hpp" +#include "assimp/MemoryIOWrapper.h" +#include "assimp/mesh.h" +#include "iarchive.h" +#include "idatastream.h" +#include "mdlimage.h" + + +class AssLogger : public Assimp::Logger +{ +public: + void OnDebug( const char* message ) override { +#ifdef _DEBUG + globalOutputStream() << message << "\n"; +#endif + } + void OnVerboseDebug( const char *message ) override { +#ifdef _DEBUG + globalOutputStream() << message << "\n"; +#endif + } + void OnInfo( const char* message ) override { +#ifdef _DEBUG + globalOutputStream() << message << "\n"; +#endif + } + void OnWarn( const char* message ) override { + globalWarningStream() << message << "\n"; + } + void OnError( const char* message ) override { + globalErrorStream() << message << "\n"; + } + + bool attachStream( Assimp::LogStream *pStream, unsigned int severity ) override { + return false; + } + bool detachStream( Assimp::LogStream *pStream, unsigned int severity ) override { + return false; + } +}; + + +class AssIOSystem : public Assimp::IOSystem +{ +public: + // ------------------------------------------------------------------- + /** @brief Tests for the existence of a file at the given path. + * + * @param pFile Path to the file + * @return true if there is a file with this path, else false. + */ + bool Exists( const char* pFile ) const override { + if( strchr( pFile, '\\' ) != nullptr ){ + globalWarningStream() << "AssIOSystem::Exists " << pFile << "\n"; + return false; + } + + ArchiveFile *file = GlobalFileSystem().openFile( pFile ); + if ( file != nullptr ) { + file->release(); + return true; + } + return false; + } + + // ------------------------------------------------------------------- + /** @brief Returns the system specific directory separator + * @return System specific directory separator + */ + char getOsSeparator() const override { + return '/'; + } + + // ------------------------------------------------------------------- + /** @brief Open a new file with a given path. + * + * When the access to the file is finished, call Close() to release + * all associated resources (or the virtual dtor of the IOStream). + * + * @param pFile Path to the file + * @param pMode Desired file I/O mode. Required are: "wb", "w", "wt", + * "rb", "r", "rt". + * + * @return New IOStream interface allowing the lib to access + * the underlying file. + * @note When implementing this class to provide custom IO handling, + * you probably have to supply an own implementation of IOStream as well. + */ + Assimp::IOStream* Open( const char* pFile, const char* pMode = "rb" ) override { + if( strchr( pFile, '\\' ) != nullptr ){ + globalWarningStream() << "AssIOSystem::Open " << pFile << "\n"; + return nullptr; + } + + ArchiveFile *file = GlobalFileSystem().openFile( pFile ); + if ( file != nullptr ) { + const size_t size = file->size(); + byte *buffer = new byte[ size ]; + file->getInputStream().read( buffer, size ); + file->release(); + return new Assimp::MemoryIOStream( buffer, size, true ); + } + return nullptr; + } + + // ------------------------------------------------------------------- + /** @brief Closes the given file and releases all resources + * associated with it. + * @param pFile The file instance previously created by Open(). + */ + void Close( Assimp::IOStream* pFile ) override { + delete pFile; + } + + // ------------------------------------------------------------------- + /** @brief CReates an new directory at the given path. + * @param path [in] The path to create. + * @return True, when a directory was created. False if the directory + * cannot be created. + */ + bool CreateDirectory( const std::string &path ) override { + ASSERT_MESSAGE( false, "AssIOSystem::CreateDirectory" ); + return false; + } + + // ------------------------------------------------------------------- + /** @brief Will change the current directory to the given path. + * @param path [in] The path to change to. + * @return True, when the directory has changed successfully. + */ + bool ChangeDirectory( const std::string &path ) override { + ASSERT_MESSAGE( false, "AssIOSystem::ChangeDirectory" ); + return false; + } + + bool DeleteFile( const std::string &file ) override { + ASSERT_MESSAGE( false, "AssIOSystem::DeleteFile" ); + return false; + } + +private: +}; + +static Assimp::Importer *s_assImporter = nullptr; + +void pico_initialise(){ + s_assImporter = new Assimp::Importer(); + + s_assImporter->SetPropertyBool( AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, true ); + s_assImporter->SetPropertyInteger( AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE ); + s_assImporter->SetPropertyString( AI_CONFIG_IMPORT_MDL_COLORMAP, "gfx/palette.lmp" ); // Q1 palette, default is fine too + s_assImporter->SetPropertyBool( AI_CONFIG_IMPORT_MD3_LOAD_SHADERS, false ); + s_assImporter->SetPropertyString( AI_CONFIG_IMPORT_MD3_SHADER_SRC, "scripts/" ); + s_assImporter->SetPropertyBool( AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, false ); + + Assimp::DefaultLogger::set( new AssLogger ); + + s_assImporter->SetIOHandler( new AssIOSystem ); +} + + +class PicoModelLoader : public ModelLoader +{ +public: + PicoModelLoader(){ + } + scene::Node& loadModel( ArchiveFile& file ){ + return loadPicoModel( *s_assImporter, file ); + } +}; + +class ModelPicoDependencies : + public GlobalFileSystemModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalFiletypesModuleRef +{ +}; + +class ModelPicoAPI : public TypeSystemRef +{ + PicoModelLoader m_modelLoader; +public: + typedef ModelLoader Type; + + ModelPicoAPI( const char* extension ){ + GlobalFiletypesModule::getTable().addType( Type::Name, extension, filetype_t( StringOutputStream()( extension, " model" ), StringOutputStream()( "*.", extension ) ) ); + } + ModelLoader* getTable(){ + return &m_modelLoader; + } +}; + +class PicoModelAPIConstructor +{ + CopiedString m_extension; +public: + PicoModelAPIConstructor( const char* extension ) : + m_extension( extension ) { + } + const char* getName(){ + return m_extension.c_str(); + } + ModelPicoAPI* constructAPI( ModelPicoDependencies& dependencies ){ + return new ModelPicoAPI( m_extension.c_str() ); + } + void destroyAPI( ModelPicoAPI* api ){ + delete api; + } +}; + + +typedef SingletonModule PicoModelModule; +typedef std::list PicoModelModules; +PicoModelModules g_PicoModelModules; + + + +class ImageMDLAPI +{ + _QERPlugImageTable m_imagemdl; +public: + typedef _QERPlugImageTable Type; + STRING_CONSTANT( Name, "mdl" ); + + ImageMDLAPI(){ + m_imagemdl.loadImage = &LoadMDLImage_; + } + _QERPlugImageTable* getTable(){ + return &m_imagemdl; + } + static Image* LoadMDLImage_( ArchiveFile& file ){ + return LoadMDLImage( *s_assImporter, file ); //!!! NOTE this lazily relies on model being loaded right before its images + } +}; + +typedef SingletonModule ImageMDLModule; + +ImageMDLModule g_ImageMDLModule; + + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ + initialiseModule( server ); + + pico_initialise(); + + for( size_t i = 0; i < s_assImporter->GetImporterCount(); ++i ){ + globalOutputStream() << s_assImporter->GetImporterInfo( i )->mName << " (" << s_assImporter->GetImporterInfo( i )->mFileExtensions << ")\n"; + } + + aiString extensions; + s_assImporter->GetExtensionList( extensions ); // "*.3ds;*.obj;*.dae" + const char *c = extensions.C_Str(); + while( !string_empty( c ) ){ + StringOutputStream ext; + do{ + if( *c == '*' && *( c + 1 ) == '.' ){ + c += 2; + continue; + } + else if( *c == ';' ){ + ++c; + break; + } + else{ + ext << *c; + ++c; + } + } while( !string_empty( c ) ); + + g_PicoModelModules.push_back( PicoModelModule( PicoModelAPIConstructor( ext ) ) ); + g_PicoModelModules.back().selfRegister(); + +// globalOutputStream() << ext << "\n"; + } + + g_ImageMDLModule.selfRegister(); +} + +/* TODO + write tangents + .obj generated weird normals direction ArnoldSchwarzeneggerBust.OBJ t_objFlip-plug.obj + .ase 0 hub1.ase assimp test utf le be dfwc2019tv // non closed *MATERIAL_LIST brace + ?aiProcess_RemoveRedundantMaterials e.g. in md2 //no, since mat name is ignored and diffuse may be empty +AI_CONFIG_IMPORT_GLOBAL_KEYFRAME // vertex anim frame to load +;;;wzmap: btw my fix to most of this stuff is modelconverterX https://www.scenerydesign.org/modelconverterx/ + don't substitute q1 mdl mat paths in q1 mode + q1 light_flame_small_yellow etc error +fbx orientation fix https://github.com/assimp/assimp/issues/849 +? aiProcess_GenSmoothNormals //if it does not join disconnected verts + "ase ask" asc? // 'c' check in the code +crashes: irr irrmesh !lwo/lwo2/UglyVertexColors.lwo !cube_normals.m3d !regr01.obj !openGEX triangle_with_empty_solid.stl ter x3d invalid +crashes: irr !pk3 x3d + //x3d: File extension not known, trying signature-based detection //not implemented + et mdc 0 + weapons2/thompson crash + hl mdl flipped faces + texture +hl mdl multiple texs, number of fails in models/ +hl spr 'models'? + mdl# flipped normals +q4 lwo, *.md5mesh: mat name should be preferred +*.3d;*.3ds;*.3mf;*.ac;*.ac3d;*.acc;*.amf;*.ase;*.ask;*.assbin;*.b3d;*.blend;*.bvh;*.cob;*.csm;*.dae;*.dxf;*.enff;*.fbx;*.glb;*.gltf;*.hmp;*.ifc;*.ifczip;*.irr;*.irrmesh;*.lwo;*.lws;*.lxo;*.m3d;*.md2;*.md3;*.md5anim;*.md5camera;*.md5mesh;*.mdc;*.mdl;*.mesh;*.mesh.xml;*.mot;*.ms3d;*.ndo;*.nff;*.obj;*.off;*.ogex;*.pk3;*.ply;*.pmx;*.prj;*.q3o;*.q3s;*.raw;*.scn;*.sib;*.smd;*.stl;*.stp;*.ter;*.uc;*.vta;*.x;*.x3d;*.x3db;*.xgl;*.xml;*.zae;*.zgl +Enabled importer formats: AMF 3DS AC ASE ASSBIN B3D BVH COLLADA DXF CSM HMP IRRMESH IRR LWO LWS M3D MD2 MD3 MD5 MDC MDL NFF NDO OFF OBJ OGRE OPENGEX PLY MS3D COB BLEND IFC XGL FBX Q3D Q3BSP RAW SIB SMD STL TERRAGEN 3D X X3D GLTF 3MF MMD +_minus: pk3 md5anim md5camera ogex +old list: md2 md3 ase lwo obj 3ds picoterrain mdl md5mesh ms3d fm + -DNDEBUG +disable crashy loaders +aiProcess_JoinIdenticalVertices uses fixed const static float epsilon = 1e-5f; while spatial sort considers mesh aabb + +___q3map2 + non black default colours +?support non vertex anim frames + test failed model loading +?is PicoFixSurfaceNormals needed? +shaderlab_terrain.ase great error spam + split to smaller meshes +aiProcess_SplitLargeMeshes is inefficient + handle nodes transformations; or aiProcess_PreTransformVertices -> applies them (but also removes animations) + +*/ \ No newline at end of file diff --git a/plugins/assmodel/plugin.h b/plugins/assmodel/plugin.h new file mode 100644 index 00000000..443a80e6 --- /dev/null +++ b/plugins/assmodel/plugin.h @@ -0,0 +1,25 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_PLUGIN_H ) +#define INCLUDED_PLUGIN_H + +#endif diff --git a/tools/quake3/q3map2/light_trace.cpp b/tools/quake3/q3map2/light_trace.cpp index 1c839c64..461de673 100644 --- a/tools/quake3/q3map2/light_trace.cpp +++ b/tools/quake3/q3map2/light_trace.cpp @@ -989,50 +989,28 @@ static void PopulateWithBSPModel( bspModel_t *model, const Matrix4& transform ){ } } - +#include "model.h" /* PopulateWithPicoModel() - ydnar filters a picomodel's surfaces into the raytracing tree */ -static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Matrix4& transform ){ - int i, j, k, numSurfaces, numIndexes; - picoSurface_t *surface; - picoShader_t *shader; - picoIndex_t *indexes; +static void PopulateWithPicoModel( int castShadows, const std::vector& model, const Matrix4& transform ){ traceInfo_t ti; traceWinding_t tw; /* dummy check */ - if ( model == NULL ) { + if ( model.empty() ) { return; } - /* get info */ - numSurfaces = PicoGetModelNumSurfaces( model ); - /* walk the list of surfaces in this model and fill out the info structs */ - for ( i = 0; i < numSurfaces; i++ ) + for ( const auto mesh : model ) { - /* get surface */ - surface = PicoGetModelSurface( model, i ); - if ( surface == NULL ) { - continue; - } - - /* only handle triangle surfaces initially (fixme: support patches) */ - if ( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) { - continue; - } - /* get shader (fixme: support shader remapping) */ - shader = PicoGetSurfaceShader( surface ); - if ( shader == NULL ) { - continue; - } - ti.si = ShaderInfoForShaderNull( PicoGetShaderName( shader ) ); + ti.si = ShaderInfoForShaderNull( mesh->getShaderName() ); if ( ti.si == NULL ) { continue; } @@ -1057,21 +1035,14 @@ static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Ma tw.infoNum = AddTraceInfo( &ti ); tw.numVerts = 3; - /* get info */ - numIndexes = PicoGetSurfaceNumIndexes( surface ); - indexes = PicoGetSurfaceIndexes( surface, 0 ); - /* walk the triangle list */ - for ( j = 0; j < numIndexes; j += 3, indexes += 3 ) - { - for ( k = 0; k < 3; k++ ) - { - tw.v[ k ].xyz = vector3_from_array( PicoGetSurfaceXYZ( surface, indexes[ k ] ) ); - tw.v[ k ].st = vector2_from_array( PicoGetSurfaceST( surface, 0, indexes[ k ] ) ); - matrix4_transform_point( transform, tw.v[ k ].xyz ); + mesh->forEachFace( [&tw, &transform]( const Vector3 ( &xyz )[3], const Vector2 ( &st )[3] ){ + for( size_t i = 0; i < 3; ++i ){ + tw.v[ i ].xyz = matrix4_transformed_point( transform, xyz[ i ] ); + tw.v[ i ].st = st[ i ]; } FilterTraceWindingIntoNodes_r( &tw, headNodeNum ); - } + } ); } } @@ -1085,7 +1056,6 @@ static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Ma static void PopulateTraceNodes( void ){ int m; const char *value; - picoModel_t *model; /* add worldspawn triangles */ @@ -1152,10 +1122,7 @@ static void PopulateTraceNodes( void ){ /* external model */ default: - model = LoadModel( value, e->intForKey( "_frame", "frame" ) ); - if ( model != NULL ) { - PopulateWithPicoModel( castShadows, model, transform ); - } + PopulateWithPicoModel( castShadows, LoadModelWalker( value, e->intForKey( "_frame", "frame" ) ), transform ); continue; } @@ -1180,11 +1147,7 @@ static void PopulateTraceNodes( void ){ /* external model */ default: - model = LoadModel( value, e->intForKey( "_frame2" ) ); - if ( model == NULL ) { - continue; - } - PopulateWithPicoModel( castShadows, model, transform ); + PopulateWithPicoModel( castShadows, LoadModelWalker( value, e->intForKey( "_frame2" ) ), transform ); continue; } } diff --git a/tools/quake3/q3map2/main.cpp b/tools/quake3/q3map2/main.cpp index fcffaa6b..6de0a540 100644 --- a/tools/quake3/q3map2/main.cpp +++ b/tools/quake3/q3map2/main.cpp @@ -145,12 +145,7 @@ int main( int argc, char **argv ){ } /* init model library */ - PicoInit(); - PicoSetMallocFunc( malloc ); - PicoSetFreeFunc( free ); - PicoSetPrintFunc( PicoPrintFunc ); - PicoSetLoadFileFunc( PicoLoadFileFunc ); - PicoSetFreeFileFunc( free ); + assimp_init(); /* set number of threads */ ThreadSetDefault(); diff --git a/tools/quake3/q3map2/model.cpp b/tools/quake3/q3map2/model.cpp index 74aa643e..ed01f630 100644 --- a/tools/quake3/q3map2/model.cpp +++ b/tools/quake3/q3map2/model.cpp @@ -31,87 +31,262 @@ /* dependencies */ #include "q3map2.h" +#include "model.h" + +#include "assimp/Importer.hpp" +#include "assimp/importerdesc.h" +#include "assimp/Logger.hpp" +#include "assimp/DefaultLogger.hpp" +#include "assimp/IOSystem.hpp" +#include "assimp/MemoryIOWrapper.h" +#include "assimp/postprocess.h" +#include "assimp/scene.h" +#include "assimp/mesh.h" + +#include -/* - PicoPrintFunc() - callback for picomodel.lib - */ - -void PicoPrintFunc( int level, const char *str ){ - if ( str == NULL ) { - return; +class AssLogger : public Assimp::Logger +{ +public: + void OnDebug( const char* message ) override { +#ifdef _DEBUG + Sys_Printf( "%s\n", message ); +#endif } - switch ( level ) - { - case PICO_NORMAL: - Sys_Printf( "%s\n", str ); - break; - - case PICO_VERBOSE: - Sys_FPrintf( SYS_VRB, "%s\n", str ); - break; - - case PICO_WARNING: - Sys_Warning( "%s\n", str ); - break; - - case PICO_ERROR: - Sys_FPrintf( SYS_WRN, "ERROR: %s\n", str ); /* let it be a warning, since radiant stops monitoring on error message flag */ - break; - - case PICO_FATAL: - Error( "ERROR: %s\n", str ); - break; + void OnVerboseDebug( const char *message ) override { +#ifdef _DEBUG + Sys_FPrintf( SYS_VRB, "%s\n", message ); +#endif } -} - - - -/* - PicoLoadFileFunc() - callback for picomodel.lib - */ - -void PicoLoadFileFunc( const char *name, byte **buffer, int *bufSize ){ - *bufSize = vfsLoadFile( name, (void**) buffer, 0 ); -} - - - -/* - FindModel() - ydnar - finds an existing picoModel and returns a pointer to the picoModel_t struct or NULL if not found - */ - -picoModel_t *FindModel( const char *name, int frame ){ - int i; - - - /* init */ - if ( numPicoModels <= 0 ) { - memset( picoModels, 0, sizeof( picoModels ) ); + void OnInfo( const char* message ) override { +#ifdef _DEBUG + Sys_Printf( "%s\n", message ); +#endif + } + void OnWarn( const char* message ) override { + Sys_Warning( "%s\n", message ); + } + void OnError( const char* message ) override { + Sys_FPrintf( SYS_WRN, "ERROR: %s\n", message ); /* let it be a warning, since radiant stops monitoring on error message flag */ } - /* dummy check */ - if ( strEmptyOrNull( name ) ) { - return NULL; + bool attachStream( Assimp::LogStream *pStream, unsigned int severity ) override { + return false; + } + bool detachStream( Assimp::LogStream *pStream, unsigned int severity ) override { + return false; + } +}; + + +class AssIOSystem : public Assimp::IOSystem +{ +public: + // ------------------------------------------------------------------- + /** @brief Tests for the existence of a file at the given path. + * + * @param pFile Path to the file + * @return true if there is a file with this path, else false. + */ + bool Exists( const char* pFile ) const override { + return vfsGetFileCount( pFile ) != 0; } - /* search list */ - for ( i = 0; i < MAX_MODELS; i++ ) - { - if ( picoModels[ i ] != NULL && - strEqual( PicoGetModelName( picoModels[ i ] ), name ) && - PicoGetModelFrameNum( picoModels[ i ] ) == frame ) { - return picoModels[ i ]; + // ------------------------------------------------------------------- + /** @brief Returns the system specific directory separator + * @return System specific directory separator + */ + char getOsSeparator() const override { + return '/'; + } + + // ------------------------------------------------------------------- + /** @brief Open a new file with a given path. + * + * When the access to the file is finished, call Close() to release + * all associated resources (or the virtual dtor of the IOStream). + * + * @param pFile Path to the file + * @param pMode Desired file I/O mode. Required are: "wb", "w", "wt", + * "rb", "r", "rt". + * + * @return New IOStream interface allowing the lib to access + * the underlying file. + * @note When implementing this class to provide custom IO handling, + * you probably have to supply an own implementation of IOStream as well. + */ + Assimp::IOStream* Open( const char* pFile, const char* pMode = "rb" ) override { + const uint8_t *boo; + const int size = vfsLoadFile( pFile, (void**) &boo, 0 ); + if ( size >= 0 ) { + return new Assimp::MemoryIOStream( boo, size, true ); } + return nullptr; } - /* no matching picoModel found */ - return NULL; + // ------------------------------------------------------------------- + /** @brief Closes the given file and releases all resources + * associated with it. + * @param pFile The file instance previously created by Open(). + */ + void Close( Assimp::IOStream* pFile ) override { + delete pFile; + } + + // ------------------------------------------------------------------- + /** @brief CReates an new directory at the given path. + * @param path [in] The path to create. + * @return True, when a directory was created. False if the directory + * cannot be created. + */ + bool CreateDirectory( const std::string &path ) override { + Error( "AssIOSystem::CreateDirectory" ); + return false; + } + + // ------------------------------------------------------------------- + /** @brief Will change the current directory to the given path. + * @param path [in] The path to change to. + * @return True, when the directory has changed successfully. + */ + bool ChangeDirectory( const std::string &path ) override { + Error( "AssIOSystem::ChangeDirectory" ); + return false; + } + + bool DeleteFile( const std::string &file ) override { + Error( "AssIOSystem::DeleteFile" ); + return false; + } + +private: +}; + +static Assimp::Importer *s_assImporter = nullptr; + +void assimp_init(){ + s_assImporter = new Assimp::Importer(); + + s_assImporter->SetPropertyBool( AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, true ); + s_assImporter->SetPropertyInteger( AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE ); + s_assImporter->SetPropertyString( AI_CONFIG_IMPORT_MDL_COLORMAP, "gfx/palette.lmp" ); // Q1 palette, default is fine too + s_assImporter->SetPropertyBool( AI_CONFIG_IMPORT_MD3_LOAD_SHADERS, false ); + s_assImporter->SetPropertyString( AI_CONFIG_IMPORT_MD3_SHADER_SRC, "scripts/" ); + s_assImporter->SetPropertyBool( AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, false ); + s_assImporter->SetPropertyInteger( AI_CONFIG_PP_RVC_FLAGS, aiComponent_TANGENTS_AND_BITANGENTS ); // varying tangents prevent aiProcess_JoinIdenticalVertices + + Assimp::DefaultLogger::set( new AssLogger ); + + s_assImporter->SetIOHandler( new AssIOSystem ); } +struct ModelNameFrame +{ + CopiedString m_name; + int m_frame; + bool operator<( const ModelNameFrame& other ) const { + const int cmp = string_compare_nocase( m_name.c_str(), other.m_name.c_str() ); + return cmp != 0? cmp < 0 : m_frame < other.m_frame; + } +}; +struct AssModel +{ + struct AssModelMesh : public AssMeshWalker + { + const aiMesh *m_mesh; + CopiedString m_shader; + + AssModelMesh( const aiScene *scene, const aiMesh *mesh, const char *rootPath ) : m_mesh( mesh ){ + aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex]; + + aiString matname = material->GetName(); +#ifdef _DEBUG + Sys_Printf( "matname: %s\n", matname.C_Str() ); +#endif + aiString texname; + if( aiReturn_SUCCESS == material->Get( AI_MATKEY_TEXTURE_DIFFUSE(0), texname ) + && texname.length != 0 ){ +#ifdef _DEBUG + Sys_Printf( "texname: %s\n", texname.C_Str() ); +#endif + m_shader = StringOutputStream()( PathCleaned( PathExtensionless( texname.C_Str() ) ) ); + + } + else{ + m_shader = StringOutputStream()( PathCleaned( PathExtensionless( matname.C_Str() ) ) ); + } + + const CopiedString oldShader( m_shader ); + if( strchr( m_shader.c_str(), '/' ) == nullptr ){ /* texture is likely in the folder, where model is */ + m_shader = StringOutputStream()( rootPath, m_shader.c_str() ); + } + else{ + const char *name = m_shader.c_str(); + if( name[0] == '/' || ( name[0] != '\0' && name[1] == ':' ) || strstr( name, ".." ) ){ /* absolute path or with .. */ + const char* p; + if( ( p = string_in_string_nocase( name, "/models/" ) ) + || ( p = string_in_string_nocase( name, "/textures/" ) ) ){ + m_shader = p + 1; + } + else{ + m_shader = StringOutputStream()( rootPath, path_get_filename_start( name ) ); + } + } + } + + if( oldShader != m_shader ) + Sys_FPrintf( SYS_VRB, "substituting: %s -> %s\n", oldShader.c_str(), m_shader.c_str() ); + } + + void forEachFace( std::function visitor ) const override { + for ( size_t t = 0; t < m_mesh->mNumFaces; ++t ){ + const aiFace& face = m_mesh->mFaces[t]; + // if( face.mNumIndices == 3 ) + Vector3 xyz[3]; + Vector2 st[3]; + for( size_t n = 0; n < 3; ++n ){ + const auto i = face.mIndices[n]; + xyz[n] = { m_mesh->mVertices[i].x, m_mesh->mVertices[i].y, m_mesh->mVertices[i].z }; + if( m_mesh->HasTextureCoords( 0 ) ) + st[n] = { m_mesh->mTextureCoords[0][i].x, m_mesh->mTextureCoords[0][i].y }; + else + st[n] = { 0, 0 }; + } + visitor( xyz, st ); + } + } + const char *getShaderName() const override { + return m_shader.c_str(); + } + }; + + aiScene *m_scene; + std::vector m_meshes; + + AssModel( aiScene *scene, const char *modelname ) : m_scene( scene ){ + m_meshes.reserve( scene->mNumMeshes ); + const auto rootPath = StringOutputStream()( PathCleaned( PathFilenameless( modelname ) ) ); + const auto traverse = [&]( const auto& self, const aiNode* node ) -> void { + for( size_t n = 0; n < node->mNumMeshes; ++n ){ + const aiMesh *mesh = scene->mMeshes[node->mMeshes[n]]; + if( mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ){ + m_meshes.emplace_back( scene, mesh, rootPath ); + } + } + + // traverse all children + for ( size_t n = 0; n < node->mNumChildren; ++n ){ + self( self, node->mChildren[n] ); + } + }; + + traverse( traverse, scene->mRootNode ); + } +}; + +static std::map s_assModels; + /* @@ -119,83 +294,62 @@ picoModel_t *FindModel( const char *name, int frame ){ loads a picoModel and returns a pointer to the picoModel_t struct or NULL if not found */ -picoModel_t *LoadModel( const char *name, int frame ){ - int i; - picoModel_t *model, **pm; - - - /* init */ - if ( numPicoModels <= 0 ) { - memset( picoModels, 0, sizeof( picoModels ) ); - } - +AssModel *LoadModel( const char *name, int frame ){ /* dummy check */ if ( strEmptyOrNull( name ) ) { - return NULL; + return nullptr; } /* try to find existing picoModel */ - model = FindModel( name, frame ); - if ( model != NULL ) { - return model; + auto it = s_assModels.find( ModelNameFrame{ name, frame } ); + if( it != s_assModels.end() ){ + return &it->second; } - /* none found, so find first non-null picoModel */ - pm = NULL; - for ( i = 0; i < MAX_MODELS; i++ ) - { - if ( picoModels[ i ] == NULL ) { - pm = &picoModels[ i ]; - break; - } + unsigned flags = //aiProcessPreset_TargetRealtime_Fast + // | aiProcess_FixInfacingNormals + aiProcess_GenNormals + | aiProcess_JoinIdenticalVertices + | aiProcess_Triangulate + | aiProcess_GenUVCoords + | aiProcess_SortByPType + | aiProcess_FindDegenerates + | aiProcess_FindInvalidData + | aiProcess_ValidateDataStructure + | aiProcess_FlipUVs + | aiProcess_FlipWindingOrder + | aiProcess_PreTransformVertices + | aiProcess_RemoveComponent + | aiProcess_SplitLargeMeshes; + // rotate the whole scene 90 degrees around the x axis to convert assimp's Y = UP to Quakes's Z = UP + s_assImporter->SetPropertyMatrix( AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4( 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 ) ); // aiMatrix4x4::RotationX( c_half_pi ) + + s_assImporter->SetPropertyInteger( AI_CONFIG_PP_SLM_VERTEX_LIMIT, maxSurfaceVerts ); // TODO this optimal and with respect to lightmapped/not + s_assImporter->SetPropertyInteger( AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, frame ); + + const aiScene *scene = s_assImporter->ReadFile( name, flags ); + + if( scene != nullptr ){ + if( scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE ) + Sys_Warning( "AI_SCENE_FLAGS_INCOMPLETE\n" ); + return &s_assModels.emplace( ModelNameFrame{ name, frame }, AssModel( s_assImporter->GetOrphanedScene(), name ) ).first->second; } - - /* too many picoModels? */ - if ( pm == NULL ) { - Error( "MAX_MODELS (%d) exceeded, there are too many model files referenced by the map.", MAX_MODELS ); + else{ + return nullptr; // TODO /* if loading failed, make a bogus model to silence the rest of the warnings */ } +} - /* attempt to parse model */ - *pm = PicoLoadModel( name, frame ); - - /* if loading failed, make a bogus model to silence the rest of the warnings */ - if ( *pm == NULL ) { - /* allocate a new model */ - *pm = PicoNewModel(); - if ( *pm == NULL ) { - return NULL; - } - - /* set data */ - PicoSetModelName( *pm, name ); - PicoSetModelFrameNum( *pm, frame ); - } - - /* debug code */ - #if 0 - { - int numSurfaces, numVertexes; - picoSurface_t *ps; - - - Sys_Printf( "Model %s\n", name ); - numSurfaces = PicoGetModelNumSurfaces( *pm ); - for ( i = 0; i < numSurfaces; i++ ) - { - ps = PicoGetModelSurface( *pm, i ); - numVertexes = PicoGetSurfaceNumVertexes( ps ); - Sys_Printf( "Surface %d has %d vertexes\n", i, numVertexes ); - } - } - #endif - - /* set count */ - if ( *pm != NULL ) { - numPicoModels++; - } - - /* return the picoModel */ - return *pm; +std::vector LoadModelWalker( const char *name, int frame ){ + AssModel *model = LoadModel( name, frame ); + std::vector vector; + if( model != nullptr ) + std::for_each( model->m_meshes.begin(), model->m_meshes.end(), [&vector]( const auto& val ){ + vector.push_back( &val ); + } ); + return vector; } @@ -206,15 +360,12 @@ picoModel_t *LoadModel( const char *name, int frame ){ */ void InsertModel( const char *name, int skin, int frame, const Matrix4& transform, const std::list *remaps, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth ){ - int i, j, s, k, numSurfaces; + int i, j, k; const Matrix4 nTransform( matrix4_for_normal_transform( transform ) ); - picoModel_t *model; - picoSurface_t *surface; + AssModel *model; shaderInfo_t *si; mapDrawSurface_t *ds; const char *picoShaderName; - byte *color; - picoIndex_t *indexes; char *skinfilecontent; int skinfilesize; char *skinfileptr, *skinfilenextptr; @@ -305,39 +456,28 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor } /* each surface on the model will become a new map drawsurface */ - numSurfaces = PicoGetModelNumSurfaces( model ); //% Sys_FPrintf( SYS_VRB, "Model %s has %d surfaces\n", name, numSurfaces ); - for ( s = 0; s < numSurfaces; s++ ) + for ( const auto& surface : model->m_meshes ) { - /* get surface */ - surface = PicoGetModelSurface( model, s ); - if ( surface == NULL ) { - continue; - } - + const aiMesh *mesh = surface.m_mesh; /* only handle triangle surfaces initially (fixme: support patches) */ - if ( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) { - continue; - } /* get shader name */ - if ( !( picoShaderName = PicoGetShaderName( PicoGetSurfaceShader( surface ) ) ) ) { - picoShaderName = ""; - } + picoShaderName = surface.m_shader.c_str(); /* handle .skin file */ if ( !skins.empty() ) { picoShaderName = NULL; for( const auto& skin : skins ) { - if ( striEqual( surface->name, skin.from ) ) { - Sys_FPrintf( SYS_VRB, "Skin file: mapping %s to %s\n", surface->name, skin.to ); + if ( striEqual( surface.m_shader.c_str(), skin.from ) ) { + Sys_FPrintf( SYS_VRB, "Skin file: mapping %s to %s\n", surface.m_shader.c_str(), skin.to ); picoShaderName = skin.to; break; } } if ( picoShaderName == NULL ) { - Sys_FPrintf( SYS_VRB, "Skin file: not mapping %s\n", surface->name ); + Sys_FPrintf( SYS_VRB, "Skin file: not mapping %s\n", surface.m_shader.c_str() ); continue; } } @@ -393,7 +533,7 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor /* fix the surface's normals (jal: conditioned by shader info) */ if ( !( spawnFlags & 64 ) && ( shadeAngle == 0.0f || ds->type != ESurfaceType::ForcedMeta ) ) { - PicoFixSurfaceNormals( surface ); + // PicoFixSurfaceNormals( surface ); } /* set sample size */ @@ -412,12 +552,12 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor } /* set particulars */ - ds->numVerts = PicoGetSurfaceNumVertexes( surface ); + ds->numVerts = mesh->mNumVertices; ds->verts = safe_calloc( ds->numVerts * sizeof( ds->verts[ 0 ] ) ); - ds->numIndexes = PicoGetSurfaceNumIndexes( surface ); + ds->numIndexes = mesh->mNumFaces * 3; ds->indexes = safe_calloc( ds->numIndexes * sizeof( ds->indexes[ 0 ] ) ); - +// Sys_Printf( "verts %i idx %i\n", ds->numVerts, ds->numIndexes ); /* copy vertexes */ for ( i = 0; i < ds->numVerts; i++ ) { @@ -425,12 +565,14 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor bspDrawVert_t& dv = ds->verts[ i ]; /* xyz and normal */ - dv.xyz = vector3_from_array( PicoGetSurfaceXYZ( surface, i ) ); + dv.xyz = { mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z }; matrix4_transform_point( transform, dv.xyz ); - dv.normal = vector3_from_array( PicoGetSurfaceNormal( surface, i ) ); - matrix4_transform_direction( nTransform, dv.normal ); - VectorNormalize( dv.normal ); + if( mesh->HasNormals() ){ + dv.normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z }; + matrix4_transform_direction( nTransform, dv.normal ); + VectorNormalize( dv.normal ); + } /* ydnar: tek-fu celshading support for flat shaded shit */ if ( flat ) { @@ -447,28 +589,41 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor /* normal texture coordinates */ else { - dv.st = vector2_from_array( PicoGetSurfaceST( surface, 0, i ) ); + if( mesh->HasTextureCoords( 0 ) ) + dv.st = { mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y }; } /* set lightmap/color bits */ - color = PicoGetSurfaceColor( surface, 0, i ); - for ( j = 0; j < MAX_LIGHTMAPS; j++ ) { - dv.lightmap[ j ] = { 0, 0 }; - if ( spawnFlags & 32 ) { // spawnflag 32: model color -> alpha hack - dv.color[ j ] = { 255, 255, 255, color_to_byte( RGBTOGRAY( color ) ) }; - } - else + const aiColor4D color = mesh->HasVertexColors( 0 )? mesh->mColors[0][i] : aiColor4D( 1 ); + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) { - dv.color[ j ] = { color[0], color[1], color[2], color[3] }; + dv.lightmap[ j ] = { 0, 0 }; + if ( spawnFlags & 32 ) { // spawnflag 32: model color -> alpha hack + dv.color[ j ] = { 255, 255, 255, color_to_byte( RGBTOGRAY( color ) * 255 ) }; + } + else + { + dv.color[ j ] = { color_to_byte( color[0] * 255 ), + color_to_byte( color[1] * 255 ), + color_to_byte( color[2] * 255 ), + color_to_byte( color[3] * 255 ) }; + } } } } /* copy indexes */ - indexes = PicoGetSurfaceIndexes( surface, 0 ); - for ( i = 0; i < ds->numIndexes; i++ ) - ds->indexes[ i ] = indexes[ i ]; + { + size_t idCopied = 0; + for ( size_t t = 0; t < mesh->mNumFaces; ++t ){ + const aiFace& face = mesh->mFaces[t]; + // if( face.mNumIndices == 3 ) + for ( size_t i = 0; i < 3; i++ ){ + ds->indexes[idCopied++] = face.mIndices[i]; + } + } + } /* set cel shader */ ds->celShader = celShader; diff --git a/tools/quake3/q3map2/model.h b/tools/quake3/q3map2/model.h new file mode 100644 index 00000000..3cd3ebab --- /dev/null +++ b/tools/quake3/q3map2/model.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_MODEL_H ) +#define INCLUDED_MODEL_H + +#include +#include +#include "generic/vector.h" + +class AssMeshWalker +{ +public: + virtual void forEachFace( std::function visitor ) const = 0; + virtual const char *getShaderName() const = 0; +}; + +std::vector LoadModelWalker( const char *name, int frame ); + +#endif diff --git a/tools/quake3/q3map2/q3map2.h b/tools/quake3/q3map2/q3map2.h index f1229870..bbcb67b8 100644 --- a/tools/quake3/q3map2/q3map2.h +++ b/tools/quake3/q3map2/q3map2.h @@ -72,8 +72,6 @@ #include "md5lib.h" #include "ddslib.h" -#include "picomodel.h" - #include "scriplib.h" #include "polylib.h" #include "imagelib.h" @@ -115,8 +113,6 @@ #define MAX_IMAGES 2048 #define DEFAULT_IMAGE "*default" -#define MAX_MODELS 2048 - #define DEF_BACKSPLASH_FRACTION 0.05f /* 5% backsplash by default */ #define DEF_BACKSPLASH_DISTANCE 23 @@ -1689,10 +1685,7 @@ tree_t *FaceBSP( face_t *list ); /* model.c */ -void PicoPrintFunc( int level, const char *str ); -void PicoLoadFileFunc( const char *name, byte **buffer, int *bufSize ); -picoModel_t *FindModel( const char *name, int frame ); -picoModel_t *LoadModel( const char *name, int frame ); +void assimp_init(); void InsertModel( const char *name, int skin, int frame, const Matrix4& transform, const std::list *remaps, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth ); void AddTriangleModels( entity_t *e ); @@ -2001,9 +1994,6 @@ Q_EXTERN game_t *game Q_ASSIGN( &games[ 0 ] ); Q_EXTERN int numImages Q_ASSIGN( 0 ); Q_EXTERN image_t images[ MAX_IMAGES ]; -Q_EXTERN int numPicoModels Q_ASSIGN( 0 ); -Q_EXTERN picoModel_t *picoModels[ MAX_MODELS ]; - Q_EXTERN shaderInfo_t *shaderInfo Q_ASSIGN( NULL ); Q_EXTERN int numShaderInfo Q_ASSIGN( 0 );