* replace model loaders by assimp library (40+ formats)

* auto split big model meshes to <= maxSurfaceVerts chunks (avoids excess meta process for vertexlit ones)
This commit is contained in:
Garux 2021-05-07 14:47:39 +03:00
parent 89110afef6
commit 75c7c09903
13 changed files with 1624 additions and 272 deletions

View File

@ -390,7 +390,6 @@ endif
.PHONY: binaries .PHONY: binaries
binaries: \ binaries: \
binaries-assimp \
binaries-tools \ binaries-tools \
binaries-radiant \ binaries-radiant \
@ -400,10 +399,6 @@ binaries-radiant: \
binaries-radiant-plugins \ binaries-radiant-plugins \
binaries-radiant-core \ binaries-radiant-core \
.PHONY: binaries-assimp
binaries-assimp: \
$(INSTALLDIR)/assimp_.$(DLL) \
.PHONY: binaries-radiant-modules .PHONY: binaries-radiant-modules
binaries-radiant-modules: \ binaries-radiant-modules: \
$(INSTALLDIR)/modules/archivepak.$(DLL) \ $(INSTALLDIR)/modules/archivepak.$(DLL) \
@ -416,8 +411,7 @@ binaries-radiant-modules: \
$(INSTALLDIR)/modules/imageq2.$(DLL) \ $(INSTALLDIR)/modules/imageq2.$(DLL) \
$(INSTALLDIR)/modules/mapq3.$(DLL) \ $(INSTALLDIR)/modules/mapq3.$(DLL) \
$(INSTALLDIR)/modules/mapxml.$(DLL) \ $(INSTALLDIR)/modules/mapxml.$(DLL) \
$(INSTALLDIR)/modules/md3model.$(DLL) \ $(INSTALLDIR)/modules/assmodel.$(DLL) \
$(INSTALLDIR)/modules/model.$(DLL) \
$(INSTALLDIR)/modules/shaders.$(DLL) \ $(INSTALLDIR)/modules/shaders.$(DLL) \
$(INSTALLDIR)/modules/vfspk3.$(DLL) \ $(INSTALLDIR)/modules/vfspk3.$(DLL) \
@ -528,8 +522,11 @@ else
$(INSTALLDIR)/q3map2.$(EXE): LDFLAGS_EXTRA := -Wl,--stack,4194304 $(INSTALLDIR)/q3map2.$(EXE): LDFLAGS_EXTRA := -Wl,--stack,4194304
endif endif
endif endif
$(INSTALLDIR)/q3map2.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_PNG) $(LIBS_JPEG) $(LIBS_ZLIB) ifneq ($(OS),Win32)
$(INSTALLDIR)/q3map2.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_PNG) $(CPPFLAGS_JPEG) -Itools/quake3/common -Ilibs -Iinclude $(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): \ $(INSTALLDIR)/q3map2.$(EXE): \
tools/quake3/common/cmdlib.o \ tools/quake3/common/cmdlib.o \
tools/quake3/common/imagelib.o \ tools/quake3/common/imagelib.o \
@ -588,7 +585,7 @@ $(INSTALLDIR)/q3map2.$(EXE): \
libddslib.$(A) \ libddslib.$(A) \
libfilematch.$(A) \ libfilematch.$(A) \
libl_net.$(A) \ libl_net.$(A) \
libpicomodel.$(A) \ $(INSTALLDIR)/libassimp_.$(DLL) \
$(if $(findstring Win32,$(OS)),icons/q3map2.o,) \ $(if $(findstring Win32,$(OS)),icons/q3map2.o,) \
libmathlib.$(A): CPPFLAGS_EXTRA := -Ilibs libmathlib.$(A): CPPFLAGS_EXTRA := -Ilibs
@ -630,9 +627,9 @@ libpicomodel.$(A): \
libs/picomodel/pm_obj.o \ libs/picomodel/pm_obj.o \
libs/picomodel/pm_terrain.o \ libs/picomodel/pm_terrain.o \
$(INSTALLDIR)/assimp_.$(DLL): LIBS_EXTRA := $(LIBS_ZLIB) $(INSTALLDIR)/libassimp_.$(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)/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)/assimp_.$(DLL): \ $(INSTALLDIR)/libassimp_.$(DLL): \
libs/assimp/code/Common/Assimp.o \ libs/assimp/code/Common/Assimp.o \
libs/assimp/code/CApi/CInterfaceIOWrapper.o \ libs/assimp/code/CApi/CInterfaceIOWrapper.o \
libs/assimp/code/Common/BaseImporter.o \ libs/assimp/code/Common/BaseImporter.o \
@ -1077,21 +1074,16 @@ $(INSTALLDIR)/modules/mapxml.$(DLL): \
plugins/mapxml/xmlparse.o \ plugins/mapxml/xmlparse.o \
plugins/mapxml/xmlwrite.o \ plugins/mapxml/xmlwrite.o \
$(INSTALLDIR)/modules/md3model.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude ifneq ($(OS),Win32)
$(INSTALLDIR)/modules/md3model.$(DLL): \ $(INSTALLDIR)/modules/assmodel.$(DLL): LDFLAGS_EXTRA := -Wl,-rpath '-Wl,$$ORIGIN/..'
plugins/md3model/md2.o \ endif
plugins/md3model/md3.o \ $(INSTALLDIR)/modules/assmodel.$(DLL): LIBS_EXTRA := -lassimp_ -L$(INSTALLDIR)
plugins/md3model/md5.o \ $(INSTALLDIR)/modules/assmodel.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude -Ilibs/assimp/include
plugins/md3model/mdc.o \ $(INSTALLDIR)/modules/assmodel.$(DLL): \
plugins/md3model/mdlimage.o \ plugins/assmodel/mdlimage.o \
plugins/md3model/mdl.o \ plugins/assmodel/model.o \
plugins/md3model/plugin.o \ plugins/assmodel/plugin.o \
$(INSTALLDIR)/libassimp_.$(DLL) \
$(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 \
$(INSTALLDIR)/modules/shaders.$(DLL): LIBS_EXTRA := $(LIBS_GLIB) $(INSTALLDIR)/modules/shaders.$(DLL): LIBS_EXTRA := $(LIBS_GLIB)
$(INSTALLDIR)/modules/shaders.$(DLL): CPPFLAGS_EXTRA := $(CPPFLAGS_GLIB) -Ilibs -Iinclude $(INSTALLDIR)/modules/shaders.$(DLL): CPPFLAGS_EXTRA := $(CPPFLAGS_GLIB) -Ilibs -Iinclude

View File

@ -107,7 +107,7 @@ public:
typedef MemberCaller<NodeType<Type>, &NodeType<Type>::initialise> InitialiseCaller; typedef MemberCaller<NodeType<Type>, &NodeType<Type>::initialise> InitialiseCaller;
TypeId getTypeId(){ TypeId getTypeId(){
#if defined( _DEBUG ) #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 #endif
return m_typeId; return m_typeId;
} }

View File

@ -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;
}

View File

@ -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

704
plugins/assmodel/model.cpp Normal file
View File

@ -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<const RendererLight*> 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<ArbitraryMeshVertex> m_vertices;
Array<RenderIndex> 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<ArbitraryMeshVertex>::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<const Vector3&>( m_vertices[i].vertex ) );
for ( Array<RenderIndex>::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<ArbitraryMeshVertex>::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i )
{
vector3_normalise( reinterpret_cast<Vector3&>( ( *i ).tangent ) );
vector3_normalise( reinterpret_cast<Vector3&>( ( *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<RenderIndex>::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<PicoSurface*> 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<Shader*> 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<PicoModelInstance, Bounded>::install( m_casts );
InstanceContainedCast<PicoModelInstance, Cullable>::install( m_casts );
InstanceStaticCast<PicoModelInstance, Renderable>::install( m_casts );
InstanceStaticCast<PicoModelInstance, SelectionTestable>::install( m_casts );
InstanceStaticCast<PicoModelInstance, SkinnedModel>::install( m_casts );
}
InstanceTypeCastTable& get(){
return m_casts;
}
};
PicoModel& m_picomodel;
const LightList* m_lightList;
typedef Array<VectorLightList> SurfaceLightLists;
SurfaceLightLists m_surfaceLightLists;
class Remap
{
public:
CopiedString first;
Shader* second;
Remap() : second( 0 ){
}
};
typedef Array<Remap> SurfaceRemaps;
SurfaceRemaps m_skins;
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
void* m_test;
Bounded& get( NullType<Bounded>){
return m_picomodel;
}
Cullable& get( NullType<Cullable>){
return m_picomodel;
}
void lightsChanged(){
m_lightList->lightsChanged();
}
typedef MemberCaller<PicoModelInstance, &PicoModelInstance::lightsChanged> LightsChangedCaller;
void constructRemaps(){
ASSERT_MESSAGE( m_skins.size() == m_picomodel.size(), "ERROR" );
ModelSkin* skin = NodeTypeCast<ModelSkin>::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<PicoModelNode, scene::Instantiable>::install( m_casts );
}
NodeTypeCastTable& get(){
return m_casts;
}
};
scene::Node m_node;
InstanceSet m_instances;
PicoModel m_picomodel;
public:
typedef LazyStatic<TypeCasts> 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();
}
}

34
plugins/assmodel/model.h Normal file
View File

@ -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

374
plugins/assmodel/plugin.cpp Normal file
View File

@ -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 <stdio.h>
typedef unsigned char byte;
#include <stdlib.h>
#include <algorithm>
#include <list>
#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<ModelPicoAPI, ModelPicoDependencies, PicoModelAPIConstructor> PicoModelModule;
typedef std::list<PicoModelModule> 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<ImageMDLAPI, GlobalFileSystemModuleRef> 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)
*/

25
plugins/assmodel/plugin.h Normal file
View File

@ -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

View File

@ -989,50 +989,28 @@ static void PopulateWithBSPModel( bspModel_t *model, const Matrix4& transform ){
} }
} }
#include "model.h"
/* /*
PopulateWithPicoModel() - ydnar PopulateWithPicoModel() - ydnar
filters a picomodel's surfaces into the raytracing tree filters a picomodel's surfaces into the raytracing tree
*/ */
static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Matrix4& transform ){ static void PopulateWithPicoModel( int castShadows, const std::vector<const AssMeshWalker*>& model, const Matrix4& transform ){
int i, j, k, numSurfaces, numIndexes;
picoSurface_t *surface;
picoShader_t *shader;
picoIndex_t *indexes;
traceInfo_t ti; traceInfo_t ti;
traceWinding_t tw; traceWinding_t tw;
/* dummy check */ /* dummy check */
if ( model == NULL ) { if ( model.empty() ) {
return; return;
} }
/* get info */
numSurfaces = PicoGetModelNumSurfaces( model );
/* walk the list of surfaces in this model and fill out the info structs */ /* 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) */ /* get shader (fixme: support shader remapping) */
shader = PicoGetSurfaceShader( surface ); ti.si = ShaderInfoForShaderNull( mesh->getShaderName() );
if ( shader == NULL ) {
continue;
}
ti.si = ShaderInfoForShaderNull( PicoGetShaderName( shader ) );
if ( ti.si == NULL ) { if ( ti.si == NULL ) {
continue; continue;
} }
@ -1057,21 +1035,14 @@ static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Ma
tw.infoNum = AddTraceInfo( &ti ); tw.infoNum = AddTraceInfo( &ti );
tw.numVerts = 3; tw.numVerts = 3;
/* get info */
numIndexes = PicoGetSurfaceNumIndexes( surface );
indexes = PicoGetSurfaceIndexes( surface, 0 );
/* walk the triangle list */ /* walk the triangle list */
for ( j = 0; j < numIndexes; j += 3, indexes += 3 ) mesh->forEachFace( [&tw, &transform]( const Vector3 ( &xyz )[3], const Vector2 ( &st )[3] ){
{ for( size_t i = 0; i < 3; ++i ){
for ( k = 0; k < 3; k++ ) tw.v[ i ].xyz = matrix4_transformed_point( transform, xyz[ i ] );
{ tw.v[ i ].st = st[ i ];
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 );
} }
FilterTraceWindingIntoNodes_r( &tw, headNodeNum ); FilterTraceWindingIntoNodes_r( &tw, headNodeNum );
} } );
} }
} }
@ -1085,7 +1056,6 @@ static void PopulateWithPicoModel( int castShadows, picoModel_t *model, const Ma
static void PopulateTraceNodes( void ){ static void PopulateTraceNodes( void ){
int m; int m;
const char *value; const char *value;
picoModel_t *model;
/* add worldspawn triangles */ /* add worldspawn triangles */
@ -1152,10 +1122,7 @@ static void PopulateTraceNodes( void ){
/* external model */ /* external model */
default: default:
model = LoadModel( value, e->intForKey( "_frame", "frame" ) ); PopulateWithPicoModel( castShadows, LoadModelWalker( value, e->intForKey( "_frame", "frame" ) ), transform );
if ( model != NULL ) {
PopulateWithPicoModel( castShadows, model, transform );
}
continue; continue;
} }
@ -1180,11 +1147,7 @@ static void PopulateTraceNodes( void ){
/* external model */ /* external model */
default: default:
model = LoadModel( value, e->intForKey( "_frame2" ) ); PopulateWithPicoModel( castShadows, LoadModelWalker( value, e->intForKey( "_frame2" ) ), transform );
if ( model == NULL ) {
continue;
}
PopulateWithPicoModel( castShadows, model, transform );
continue; continue;
} }
} }

View File

@ -145,12 +145,7 @@ int main( int argc, char **argv ){
} }
/* init model library */ /* init model library */
PicoInit(); assimp_init();
PicoSetMallocFunc( malloc );
PicoSetFreeFunc( free );
PicoSetPrintFunc( PicoPrintFunc );
PicoSetLoadFileFunc( PicoLoadFileFunc );
PicoSetFreeFileFunc( free );
/* set number of threads */ /* set number of threads */
ThreadSetDefault(); ThreadSetDefault();

View File

@ -31,87 +31,262 @@
/* dependencies */ /* dependencies */
#include "q3map2.h" #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 <map>
/* class AssLogger : public Assimp::Logger
PicoPrintFunc() {
callback for picomodel.lib public:
*/ void OnDebug( const char* message ) override {
#ifdef _DEBUG
void PicoPrintFunc( int level, const char *str ){ Sys_Printf( "%s\n", message );
if ( str == NULL ) { #endif
return;
} }
switch ( level ) void OnVerboseDebug( const char *message ) override {
{ #ifdef _DEBUG
case PICO_NORMAL: Sys_FPrintf( SYS_VRB, "%s\n", message );
Sys_Printf( "%s\n", str ); #endif
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 OnInfo( const char* message ) override {
#ifdef _DEBUG
Sys_Printf( "%s\n", message );
#endif
/* }
PicoLoadFileFunc() void OnWarn( const char* message ) override {
callback for picomodel.lib Sys_Warning( "%s\n", message );
*/ }
void OnError( const char* message ) override {
void PicoLoadFileFunc( const char *name, byte **buffer, int *bufSize ){ Sys_FPrintf( SYS_WRN, "ERROR: %s\n", message ); /* let it be a warning, since radiant stops monitoring on error message flag */
*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 ) );
} }
/* dummy check */ bool attachStream( Assimp::LogStream *pStream, unsigned int severity ) override {
if ( strEmptyOrNull( name ) ) { return false;
return NULL; }
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++ ) /** @brief Returns the system specific directory separator
{ * @return System specific directory separator
if ( picoModels[ i ] != NULL && */
strEqual( PicoGetModelName( picoModels[ i ] ), name ) && char getOsSeparator() const override {
PicoGetModelFrameNum( picoModels[ i ] ) == frame ) { return '/';
return picoModels[ i ]; }
// -------------------------------------------------------------------
/** @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<void( const Vector3 ( &xyz )[3], const Vector2 ( &st )[3])> 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<AssModelMesh> 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<ModelNameFrame, AssModel> 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 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 ){ AssModel *LoadModel( const char *name, int frame ){
int i;
picoModel_t *model, **pm;
/* init */
if ( numPicoModels <= 0 ) {
memset( picoModels, 0, sizeof( picoModels ) );
}
/* dummy check */ /* dummy check */
if ( strEmptyOrNull( name ) ) { if ( strEmptyOrNull( name ) ) {
return NULL; return nullptr;
} }
/* try to find existing picoModel */ /* try to find existing picoModel */
model = FindModel( name, frame ); auto it = s_assModels.find( ModelNameFrame{ name, frame } );
if ( model != NULL ) { if( it != s_assModels.end() ){
return model; return &it->second;
} }
/* none found, so find first non-null picoModel */ unsigned flags = //aiProcessPreset_TargetRealtime_Fast
pm = NULL; // | aiProcess_FixInfacingNormals
for ( i = 0; i < MAX_MODELS; i++ ) aiProcess_GenNormals
{ | aiProcess_JoinIdenticalVertices
if ( picoModels[ i ] == NULL ) { | aiProcess_Triangulate
pm = &picoModels[ i ]; | aiProcess_GenUVCoords
break; | 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;
} }
else{
/* too many picoModels? */ return nullptr; // TODO /* if loading failed, make a bogus model to silence the rest of the warnings */
if ( pm == NULL ) {
Error( "MAX_MODELS (%d) exceeded, there are too many model files referenced by the map.", MAX_MODELS );
} }
}
/* attempt to parse model */ std::vector<const AssMeshWalker*> LoadModelWalker( const char *name, int frame ){
*pm = PicoLoadModel( name, frame ); AssModel *model = LoadModel( name, frame );
std::vector<const AssMeshWalker*> vector;
/* if loading failed, make a bogus model to silence the rest of the warnings */ if( model != nullptr )
if ( *pm == NULL ) { std::for_each( model->m_meshes.begin(), model->m_meshes.end(), [&vector]( const auto& val ){
/* allocate a new model */ vector.push_back( &val );
*pm = PicoNewModel(); } );
if ( *pm == NULL ) { return vector;
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;
} }
@ -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<remap_t> *remaps, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth ){ void InsertModel( const char *name, int skin, int frame, const Matrix4& transform, const std::list<remap_t> *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 ) ); const Matrix4 nTransform( matrix4_for_normal_transform( transform ) );
picoModel_t *model; AssModel *model;
picoSurface_t *surface;
shaderInfo_t *si; shaderInfo_t *si;
mapDrawSurface_t *ds; mapDrawSurface_t *ds;
const char *picoShaderName; const char *picoShaderName;
byte *color;
picoIndex_t *indexes;
char *skinfilecontent; char *skinfilecontent;
int skinfilesize; int skinfilesize;
char *skinfileptr, *skinfilenextptr; 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 */ /* 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 ); //% 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 */ const aiMesh *mesh = surface.m_mesh;
surface = PicoGetModelSurface( model, s );
if ( surface == NULL ) {
continue;
}
/* only handle triangle surfaces initially (fixme: support patches) */ /* only handle triangle surfaces initially (fixme: support patches) */
if ( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) {
continue;
}
/* get shader name */ /* get shader name */
if ( !( picoShaderName = PicoGetShaderName( PicoGetSurfaceShader( surface ) ) ) ) { picoShaderName = surface.m_shader.c_str();
picoShaderName = "";
}
/* handle .skin file */ /* handle .skin file */
if ( !skins.empty() ) { if ( !skins.empty() ) {
picoShaderName = NULL; picoShaderName = NULL;
for( const auto& skin : skins ) for( const auto& skin : skins )
{ {
if ( striEqual( surface->name, skin.from ) ) { if ( striEqual( surface.m_shader.c_str(), skin.from ) ) {
Sys_FPrintf( SYS_VRB, "Skin file: mapping %s to %s\n", surface->name, skin.to ); Sys_FPrintf( SYS_VRB, "Skin file: mapping %s to %s\n", surface.m_shader.c_str(), skin.to );
picoShaderName = skin.to; picoShaderName = skin.to;
break; break;
} }
} }
if ( picoShaderName == NULL ) { 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; 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) */ /* fix the surface's normals (jal: conditioned by shader info) */
if ( !( spawnFlags & 64 ) && ( shadeAngle == 0.0f || ds->type != ESurfaceType::ForcedMeta ) ) { if ( !( spawnFlags & 64 ) && ( shadeAngle == 0.0f || ds->type != ESurfaceType::ForcedMeta ) ) {
PicoFixSurfaceNormals( surface ); // PicoFixSurfaceNormals( surface );
} }
/* set sample size */ /* set sample size */
@ -412,12 +552,12 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor
} }
/* set particulars */ /* set particulars */
ds->numVerts = PicoGetSurfaceNumVertexes( surface ); ds->numVerts = mesh->mNumVertices;
ds->verts = safe_calloc( ds->numVerts * sizeof( ds->verts[ 0 ] ) ); 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 ] ) ); ds->indexes = safe_calloc( ds->numIndexes * sizeof( ds->indexes[ 0 ] ) );
// Sys_Printf( "verts %i idx %i\n", ds->numVerts, ds->numIndexes );
/* copy vertexes */ /* copy vertexes */
for ( i = 0; i < ds->numVerts; i++ ) 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 ]; bspDrawVert_t& dv = ds->verts[ i ];
/* xyz and normal */ /* 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 ); matrix4_transform_point( transform, dv.xyz );
dv.normal = vector3_from_array( PicoGetSurfaceNormal( surface, i ) ); if( mesh->HasNormals() ){
matrix4_transform_direction( nTransform, dv.normal ); dv.normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z };
VectorNormalize( dv.normal ); matrix4_transform_direction( nTransform, dv.normal );
VectorNormalize( dv.normal );
}
/* ydnar: tek-fu celshading support for flat shaded shit */ /* ydnar: tek-fu celshading support for flat shaded shit */
if ( flat ) { if ( flat ) {
@ -447,28 +589,41 @@ void InsertModel( const char *name, int skin, int frame, const Matrix4& transfor
/* normal texture coordinates */ /* normal texture coordinates */
else 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 */ /* set lightmap/color bits */
color = PicoGetSurfaceColor( surface, 0, i );
for ( j = 0; j < MAX_LIGHTMAPS; j++ )
{ {
dv.lightmap[ j ] = { 0, 0 }; const aiColor4D color = mesh->HasVertexColors( 0 )? mesh->mColors[0][i] : aiColor4D( 1 );
if ( spawnFlags & 32 ) { // spawnflag 32: model color -> alpha hack for ( j = 0; j < MAX_LIGHTMAPS; j++ )
dv.color[ j ] = { 255, 255, 255, color_to_byte( RGBTOGRAY( color ) ) };
}
else
{ {
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 */ /* copy indexes */
indexes = PicoGetSurfaceIndexes( surface, 0 ); {
for ( i = 0; i < ds->numIndexes; i++ ) size_t idCopied = 0;
ds->indexes[ i ] = indexes[ i ]; 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 */ /* set cel shader */
ds->celShader = celShader; ds->celShader = celShader;

View File

@ -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 <vector>
#include <functional>
#include "generic/vector.h"
class AssMeshWalker
{
public:
virtual void forEachFace( std::function<void( const Vector3 ( &xyz )[3], const Vector2 ( &st )[3])> visitor ) const = 0;
virtual const char *getShaderName() const = 0;
};
std::vector<const AssMeshWalker*> LoadModelWalker( const char *name, int frame );
#endif

View File

@ -72,8 +72,6 @@
#include "md5lib.h" #include "md5lib.h"
#include "ddslib.h" #include "ddslib.h"
#include "picomodel.h"
#include "scriplib.h" #include "scriplib.h"
#include "polylib.h" #include "polylib.h"
#include "imagelib.h" #include "imagelib.h"
@ -115,8 +113,6 @@
#define MAX_IMAGES 2048 #define MAX_IMAGES 2048
#define DEFAULT_IMAGE "*default" #define DEFAULT_IMAGE "*default"
#define MAX_MODELS 2048
#define DEF_BACKSPLASH_FRACTION 0.05f /* 5% backsplash by default */ #define DEF_BACKSPLASH_FRACTION 0.05f /* 5% backsplash by default */
#define DEF_BACKSPLASH_DISTANCE 23 #define DEF_BACKSPLASH_DISTANCE 23
@ -1689,10 +1685,7 @@ tree_t *FaceBSP( face_t *list );
/* model.c */ /* model.c */
void PicoPrintFunc( int level, const char *str ); void assimp_init();
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 InsertModel( const char *name, int skin, int frame, const Matrix4& transform, const std::list<remap_t> *remaps, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth ); void InsertModel( const char *name, int skin, int frame, const Matrix4& transform, const std::list<remap_t> *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 ); 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 int numImages Q_ASSIGN( 0 );
Q_EXTERN image_t images[ MAX_IMAGES ]; 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 shaderInfo_t *shaderInfo Q_ASSIGN( NULL );
Q_EXTERN int numShaderInfo Q_ASSIGN( 0 ); Q_EXTERN int numShaderInfo Q_ASSIGN( 0 );