1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827 |
- /*
- Open Asset Import Library (assimp)
- ----------------------------------------------------------------------
- Copyright (c) 2006-2025, assimp team
- All rights reserved.
- Redistribution and use of this software in source and binary forms,
- with or without modification, are permitted provided that the
- following conditions are met:
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the
- following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the
- following disclaimer in the documentation and/or other
- materials provided with the distribution.
- * Neither the name of the assimp team, nor the names of its
- contributors may be used to endorse or promote products
- derived from this software without specific prior
- written permission of the assimp team.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- ----------------------------------------------------------------------
- */
- #ifndef ASSIMP_BUILD_NO_EXPORT
- #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
- #include "FBXExporter.h"
- #include "FBXExportNode.h"
- #include "FBXExportProperty.h"
- #include "FBXCommon.h"
- #include "FBXUtil.h"
- #include <assimp/version.h> // aiGetVersion
- #include <assimp/IOSystem.hpp>
- #include <assimp/Exporter.hpp>
- #include <assimp/DefaultLogger.hpp>
- #include <assimp/StreamWriter.h> // StreamWriterLE
- #include <assimp/Exceptional.h> // DeadlyExportError
- #include <assimp/material.h> // aiTextureType
- #include <assimp/scene.h>
- #include <assimp/mesh.h>
- // Header files, standard library.
- #include <array>
- #include <ctime> // localtime, tm_*
- #include <map>
- #include <memory> // shared_ptr
- #include <numeric>
- #include <set>
- #include <sstream> // stringstream
- #include <string>
- #include <unordered_set>
- #include <utility>
- #include <vector>
- #include <cmath>
- // RESOURCES:
- // https://code.blender.org/2013/08/fbx-binary-file-format-specification/
- // https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
- using namespace Assimp;
- using namespace Assimp::FBX;
- // some constants that we'll use for writing metadata
- namespace Assimp {
- namespace FBX {
- const std::string EXPORT_VERSION_STR = "7.5.0";
- const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+
- // FBX files have some hashed values that depend on the creation time field,
- // but for now we don't actually know how to generate these.
- // what we can do is set them to a known-working version.
- // this is the data that Blender uses in their FBX export process.
- const std::string GENERIC_CTIME = "1970-01-01 10:00:00:000";
- const std::string GENERIC_FILEID =
- "\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1";
- const std::string GENERIC_FOOTID =
- "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e";
- const std::string FOOT_MAGIC =
- "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b";
- const std::string COMMENT_UNDERLINE =
- ";------------------------------------------------------------------";
- }
- // ---------------------------------------------------------------------
- // Worker function for exporting a scene to binary FBX.
- // Prototyped and registered in Exporter.cpp
- void ExportSceneFBX (
- const char* pFile,
- IOSystem* pIOSystem,
- const aiScene* pScene,
- const ExportProperties* pProperties
- ){
- // initialize the exporter
- FBXExporter exporter(pScene, pProperties);
- // perform binary export
- exporter.ExportBinary(pFile, pIOSystem);
- }
- // ---------------------------------------------------------------------
- // Worker function for exporting a scene to ASCII FBX.
- // Prototyped and registered in Exporter.cpp
- void ExportSceneFBXA (
- const char* pFile,
- IOSystem* pIOSystem,
- const aiScene* pScene,
- const ExportProperties* pProperties
- ){
- // initialize the exporter
- FBXExporter exporter(pScene, pProperties);
- // perform ascii export
- exporter.ExportAscii(pFile, pIOSystem);
- }
- } // end of namespace Assimp
- FBXExporter::FBXExporter ( const aiScene* pScene, const ExportProperties* pProperties )
- : binary(false)
- , mScene(pScene)
- , mProperties(pProperties)
- , outfile()
- , connections()
- , mesh_uids()
- , material_uids()
- , node_uids() {
- // will probably need to determine UIDs, connections, etc here.
- // basically anything that needs to be known
- // before we start writing sections to the stream.
- }
- void FBXExporter::ExportBinary (
- const char* pFile,
- IOSystem* pIOSystem
- ){
- // remember that we're exporting in binary mode
- binary = true;
- // we're not currently using these preferences,
- // but clang will cry about it if we never touch it.
- // TODO: some of these might be relevant to export
- (void)mProperties;
- // open the indicated file for writing (in binary mode)
- outfile.reset(pIOSystem->Open(pFile,"wb"));
- if (!outfile) {
- throw DeadlyExportError(
- "could not open output .fbx file: " + std::string(pFile)
- );
- }
- // first a binary-specific file header
- WriteBinaryHeader();
- // the rest of the file is in node entries.
- // we have to serialize each entry before we write to the output,
- // as the first thing we write is the byte offset of the _next_ entry.
- // Either that or we can skip back to write the offset when we finish.
- WriteAllNodes();
- // finally we have a binary footer to the file
- WriteBinaryFooter();
- // explicitly release file pointer,
- // so we don't have to rely on class destruction.
- outfile.reset();
- }
- void FBXExporter::ExportAscii (
- const char* pFile,
- IOSystem* pIOSystem
- ){
- // remember that we're exporting in ascii mode
- binary = false;
- // open the indicated file for writing in text mode
- outfile.reset(pIOSystem->Open(pFile,"wt"));
- if (!outfile) {
- throw DeadlyExportError(
- "could not open output .fbx file: " + std::string(pFile)
- );
- }
- // write the ascii header
- WriteAsciiHeader();
- // write all the sections
- WriteAllNodes();
- // make sure the file ends with a newline.
- // note: if the file is opened in text mode,
- // this should do the right cross-platform thing.
- outfile->Write("\n", 1, 1);
- // explicitly release file pointer,
- // so we don't have to rely on class destruction.
- outfile.reset();
- }
- void FBXExporter::WriteAsciiHeader()
- {
- // basically just a comment at the top of the file
- std::stringstream head;
- head << "; FBX " << EXPORT_VERSION_STR << " project file\n";
- head << "; Created by the Open Asset Import Library (Assimp)\n";
- head << "; http://assimp.org\n";
- head << "; -------------------------------------------------\n";
- const std::string ascii_header = head.str();
- outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
- }
- void FBXExporter::WriteAsciiSectionHeader(const std::string& title)
- {
- StreamWriterLE outstream(outfile);
- std::stringstream s;
- s << "\n\n; " << title << '\n';
- s << FBX::COMMENT_UNDERLINE << "\n";
- outstream.PutString(s.str());
- }
- void FBXExporter::WriteBinaryHeader()
- {
- // first a specific sequence of 23 bytes, always the same
- const char binary_header[24] = "Kaydara FBX Binary\x20\x20\x00\x1a\x00";
- outfile->Write(binary_header, 1, 23);
- // then FBX version number, "multiplied" by 1000, as little-endian uint32.
- // so 7.3 becomes 7300 == 0x841C0000, 7.4 becomes 7400 == 0xE81C0000, etc
- {
- StreamWriterLE outstream(outfile);
- outstream.PutU4(EXPORT_VERSION_INT);
- } // StreamWriter destructor writes the data to the file
- // after this the node data starts immediately
- // (probably with the FBXHEaderExtension node)
- }
- void FBXExporter::WriteBinaryFooter()
- {
- outfile->Write(NULL_RECORD, NumNullRecords, 1);
- outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1);
- // here some padding is added for alignment to 16 bytes.
- // if already aligned, the full 16 bytes is added.
- size_t pos = outfile->Tell();
- size_t pad = 16 - (pos % 16);
- for (size_t i = 0; i < pad; ++i) {
- outfile->Write("\x00", 1, 1);
- }
- // not sure what this is, but it seems to always be 0 in modern files
- for (size_t i = 0; i < 4; ++i) {
- outfile->Write("\x00", 1, 1);
- }
- // now the file version again
- {
- StreamWriterLE outstream(outfile);
- outstream.PutU4(EXPORT_VERSION_INT);
- } // StreamWriter destructor writes the data to the file
- // and finally some binary footer added to all files
- for (size_t i = 0; i < 120; ++i) {
- outfile->Write("\x00", 1, 1);
- }
- outfile->Write(FOOT_MAGIC.c_str(), FOOT_MAGIC.size(), 1);
- }
- void FBXExporter::WriteAllNodes ()
- {
- // header
- // (and fileid, creation time, creator, if binary)
- WriteHeaderExtension();
- // global settings
- WriteGlobalSettings();
- // documents
- WriteDocuments();
- // references
- WriteReferences();
- // definitions
- WriteDefinitions();
- // objects
- WriteObjects();
- // connections
- WriteConnections();
- // WriteTakes? (deprecated since at least 2015 (fbx 7.4))
- }
- //FBXHeaderExtension top-level node
- void FBXExporter::WriteHeaderExtension ()
- {
- if (!binary) {
- // no title, follows directly from the top comment
- }
- FBX::Node n("FBXHeaderExtension");
- StreamWriterLE outstream(outfile);
- int indent = 0;
- // begin node
- n.Begin(outstream, binary, indent);
- // write properties
- // (none)
- // finish properties
- n.EndProperties(outstream, binary, indent, 0);
- // begin children
- n.BeginChildren(outstream, binary, indent);
- indent = 1;
- // write child nodes
- FBX::Node::WritePropertyNode(
- "FBXHeaderVersion", int32_t(1003), outstream, binary, indent
- );
- FBX::Node::WritePropertyNode(
- "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent
- );
- if (binary) {
- FBX::Node::WritePropertyNode(
- "EncryptionType", int32_t(0), outstream, binary, indent
- );
- }
- FBX::Node CreationTimeStamp("CreationTimeStamp");
- time_t rawtime;
- time(&rawtime);
- struct tm * now = localtime(&rawtime);
- CreationTimeStamp.AddChild("Version", int32_t(1000));
- CreationTimeStamp.AddChild("Year", int32_t(now->tm_year + 1900));
- CreationTimeStamp.AddChild("Month", int32_t(now->tm_mon + 1));
- CreationTimeStamp.AddChild("Day", int32_t(now->tm_mday));
- CreationTimeStamp.AddChild("Hour", int32_t(now->tm_hour));
- CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min));
- CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec));
- CreationTimeStamp.AddChild("Millisecond", int32_t(0));
- CreationTimeStamp.Dump(outstream, binary, indent);
- std::stringstream creator;
- creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor()
- << "." << aiGetVersionMinor() << "." << aiGetVersionRevision();
- FBX::Node::WritePropertyNode(
- "Creator", creator.str(), outstream, binary, indent
- );
- indent = 0;
- // finish node
- n.End(outstream, binary, indent, true);
- // that's it for FBXHeaderExtension...
- if (!binary) { return; }
- // but binary files also need top-level FileID, CreationTime, Creator:
- std::vector<uint8_t> raw(GENERIC_FILEID.size());
- for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) {
- raw[i] = uint8_t(GENERIC_FILEID[i]);
- }
- FBX::Node::WritePropertyNode(
- "FileId", std::move(raw), outstream, binary, indent
- );
- FBX::Node::WritePropertyNode(
- "CreationTime", GENERIC_CTIME, outstream, binary, indent
- );
- FBX::Node::WritePropertyNode(
- "Creator", creator.str(), outstream, binary, indent
- );
- }
- // WriteGlobalSettings helpers
- void WritePropInt(const aiScene* scene, FBX::Node& p, const std::string& key, int defaultValue)
- {
- int value;
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) {
- p.AddP70int(key, value);
- } else {
- p.AddP70int(key, defaultValue);
- }
- }
- void WritePropDouble(const aiScene* scene, FBX::Node& p, const std::string& key, double defaultValue)
- {
- double value;
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) {
- p.AddP70double(key, value);
- } else {
- // fallback lookup float instead
- float floatValue;
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, floatValue)) {
- p.AddP70double(key, (double)floatValue);
- } else {
- p.AddP70double(key, defaultValue);
- }
- }
- }
- void WritePropEnum(const aiScene* scene, FBX::Node& p, const std::string& key, int defaultValue)
- {
- int value;
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) {
- p.AddP70enum(key, value);
- } else {
- p.AddP70enum(key, defaultValue);
- }
- }
- void WritePropColor(const aiScene* scene, FBX::Node& p, const std::string& key, const aiVector3D& defaultValue)
- {
- aiVector3D value;
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) {
- // ai_real can be float or double, cast to avoid warnings
- p.AddP70color(key, (double)value.x, (double)value.y, (double)value.z);
- } else {
- p.AddP70color(key, (double)defaultValue.x, (double)defaultValue.y, (double)defaultValue.z);
- }
- }
- void WritePropString(const aiScene* scene, FBX::Node& p, const std::string& key, const std::string& defaultValue)
- {
- aiString value; // MetaData doesn't hold std::string
- if (scene->mMetaData != nullptr && scene->mMetaData->Get(key, value)) {
- p.AddP70string(key, value.C_Str());
- } else {
- p.AddP70string(key, defaultValue);
- }
- }
- void FBXExporter::WriteGlobalSettings () {
- FBX::Node gs("GlobalSettings");
- gs.AddChild("Version", int32_t(1000));
- FBX::Node p("Properties70");
- WritePropInt(mScene, p, "UpAxis", 1);
- WritePropInt(mScene, p, "UpAxisSign", 1);
- WritePropInt(mScene, p, "FrontAxis", 2);
- WritePropInt(mScene, p, "FrontAxisSign", 1);
- WritePropInt(mScene, p, "CoordAxis", 0);
- WritePropInt(mScene, p, "CoordAxisSign", 1);
- WritePropInt(mScene, p, "OriginalUpAxis", 1);
- WritePropInt(mScene, p, "OriginalUpAxisSign", 1);
- WritePropDouble(mScene, p, "UnitScaleFactor", 1.0);
- WritePropDouble(mScene, p, "OriginalUnitScaleFactor", 1.0);
- WritePropColor(mScene, p, "AmbientColor", aiVector3D((ai_real)0.0, (ai_real)0.0, (ai_real)0.0));
- WritePropString(mScene, p,"DefaultCamera", "Producer Perspective");
- WritePropEnum(mScene, p, "TimeMode", 11);
- WritePropEnum(mScene, p, "TimeProtocol", 2);
- WritePropEnum(mScene, p, "SnapOnFrameMode", 0);
- p.AddP70time("TimeSpanStart", 0); // TODO: animation support
- p.AddP70time("TimeSpanStop", FBX::SECOND); // TODO: animation support
- WritePropDouble(mScene, p, "CustomFrameRate", -1.0);
- p.AddP70("TimeMarker", "Compound", "", ""); // not sure what this is
- WritePropInt(mScene, p, "CurrentTimeMarker", -1);
- gs.AddChild(p);
- gs.Dump(outfile, binary, 0);
- }
- void FBXExporter::WriteDocuments() {
- if (!binary) {
- WriteAsciiSectionHeader("Documents Description");
- }
- // not sure what the use of multiple documents would be,
- // or whether any end-application supports it
- FBX::Node docs("Documents");
- docs.AddChild("Count", int32_t(1));
- FBX::Node doc("Document");
- // generate uid
- int64_t uid = generate_uid();
- doc.AddProperties(uid, "", "Scene");
- FBX::Node p("Properties70");
- p.AddP70("SourceObject", "object", "", ""); // what is this even for?
- p.AddP70string("ActiveAnimStackName", ""); // should do this properly?
- doc.AddChild(p);
- // UID for root node in scene hierarchy.
- // always set to 0 in the case of a single document.
- // not sure what happens if more than one document exists,
- // but that won't matter to us as we're exporting a single scene.
- doc.AddChild("RootNode", int64_t(0));
- docs.AddChild(doc);
- docs.Dump(outfile, binary, 0);
- }
- void FBXExporter::WriteReferences() {
- if (!binary) {
- WriteAsciiSectionHeader("Document References");
- }
- // always empty for now.
- // not really sure what this is for.
- FBX::Node n("References");
- n.force_has_children = true;
- n.Dump(outfile, binary, 0);
- }
- // ---------------------------------------------------------------
- // some internal helper functions used for writing the definitions
- // (before any actual data is written)
- // ---------------------------------------------------------------
- size_t count_nodes(const aiNode* n, const aiNode* root) {
- size_t count;
- if (n == root) {
- count = n->mNumMeshes; // (not counting root node)
- } else if (n->mNumMeshes > 1) {
- count = n->mNumMeshes + 1;
- } else {
- count = 1;
- }
- for (size_t i = 0; i < n->mNumChildren; ++i) {
- count += count_nodes(n->mChildren[i], root);
- }
- return count;
- }
- static bool has_phong_mat(const aiScene* scene) {
- // just search for any material with a shininess exponent
- for (size_t i = 0; i < scene->mNumMaterials; ++i) {
- aiMaterial* mat = scene->mMaterials[i];
- float shininess = 0;
- mat->Get(AI_MATKEY_SHININESS, shininess);
- if (shininess > 0) {
- return true;
- }
- }
- return false;
- }
- static size_t count_images(const aiScene* scene) {
- std::unordered_set<std::string> images;
- aiString texpath;
- for (size_t i = 0; i < scene->mNumMaterials; ++i) {
- aiMaterial *mat = scene->mMaterials[i];
- for (size_t tt = aiTextureType_DIFFUSE; tt < aiTextureType_UNKNOWN; ++tt) {
- const aiTextureType textype = static_cast<aiTextureType>(tt);
- const size_t texcount = mat->GetTextureCount(textype);
- for (unsigned int j = 0; j < texcount; ++j) {
- mat->GetTexture(textype, j, &texpath);
- images.insert(std::string(texpath.C_Str()));
- }
- }
- }
- return images.size();
- }
- static size_t count_textures(const aiScene* scene) {
- size_t count = 0;
- for (size_t i = 0; i < scene->mNumMaterials; ++i) {
- aiMaterial* mat = scene->mMaterials[i];
- for (
- size_t tt = aiTextureType_DIFFUSE;
- tt < aiTextureType_UNKNOWN;
- ++tt
- ){
- // TODO: handle layered textures
- if (mat->GetTextureCount(static_cast<aiTextureType>(tt)) > 0) {
- count += 1;
- }
- }
- }
- return count;
- }
- static size_t count_deformers(const aiScene* scene) {
- size_t count = 0;
- for (size_t i = 0; i < scene->mNumMeshes; ++i) {
- const size_t n = scene->mMeshes[i]->mNumBones;
- if (n) {
- // 1 main deformer, 1 subdeformer per bone
- count += n + 1;
- }
- }
- return count;
- }
- void FBXExporter::WriteDefinitions () {
- // basically this is just bookkeeping:
- // determining how many of each type of object there are
- // and specifying the base properties to use when otherwise unspecified.
- // ascii section header
- if (!binary) {
- WriteAsciiSectionHeader("Object definitions");
- }
- // we need to count the objects
- int32_t count;
- int32_t total_count = 0;
- // and store them
- std::vector<FBX::Node> object_nodes;
- FBX::Node n, pt, p;
- // GlobalSettings
- // this seems to always be here in Maya exports
- n = FBX::Node("ObjectType", "GlobalSettings");
- count = 1;
- n.AddChild("Count", count);
- object_nodes.push_back(n);
- total_count += count;
- // AnimationStack / FbxAnimStack
- // this seems to always be here in Maya exports,
- // but no harm seems to come of leaving it out.
- count = mScene->mNumAnimations;
- if (count) {
- n = FBX::Node("ObjectType", "AnimationStack");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxAnimStack");
- p = FBX::Node("Properties70");
- p.AddP70string("Description", "");
- p.AddP70time("LocalStart", 0);
- p.AddP70time("LocalStop", 0);
- p.AddP70time("ReferenceStart", 0);
- p.AddP70time("ReferenceStop", 0);
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // AnimationLayer / FbxAnimLayer
- // this seems to always be here in Maya exports,
- // but no harm seems to come of leaving it out.
- // Assimp doesn't support animation layers,
- // so there will be one per aiAnimation
- count = mScene->mNumAnimations;
- if (count) {
- n = FBX::Node("ObjectType", "AnimationLayer");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FBXAnimLayer");
- p = FBX::Node("Properties70");
- p.AddP70("Weight", "Number", "", "A", double(100));
- p.AddP70bool("Mute", false);
- p.AddP70bool("Solo", false);
- p.AddP70bool("Lock", false);
- p.AddP70color("Color", 0.8, 0.8, 0.8);
- p.AddP70("BlendMode", "enum", "", "", int32_t(0));
- p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0));
- p.AddP70("ScaleAccumulationMode", "enum", "", "", int32_t(0));
- p.AddP70("BlendModeBypass", "ULongLong", "", "", int64_t(0));
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // NodeAttribute
- // this is completely absurd.
- // there can only be one "NodeAttribute" template,
- // but FbxSkeleton, FbxCamera, FbxLight all are "NodeAttributes".
- // so if only one exists we should set the template for that,
- // otherwise... we just pick one :/.
- // the others have to set all their properties every instance,
- // because there's no template.
- count = 1; // TODO: select properly
- if (count) {
- // FbxSkeleton
- n = FBX::Node("ObjectType", "NodeAttribute");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxSkeleton");
- p = FBX::Node("Properties70");
- p.AddP70color("Color", 0.8, 0.8, 0.8);
- p.AddP70double("Size", 33.333333333333);
- p.AddP70("LimbLength", "double", "Number", "H", double(1));
- // note: not sure what the "H" flag is for - hidden?
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Model / FbxNode
- // <~~ node hierarchy
- count = int32_t(count_nodes(mScene->mRootNode, mScene->mRootNode));
- if (count) {
- n = FBX::Node("ObjectType", "Model");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxNode");
- p = FBX::Node("Properties70");
- p.AddP70enum("QuaternionInterpolate", 0);
- p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0);
- p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0);
- p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0);
- p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0);
- p.AddP70bool("TranslationActive", false);
- p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0);
- p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0);
- p.AddP70bool("TranslationMinX", false);
- p.AddP70bool("TranslationMinY", false);
- p.AddP70bool("TranslationMinZ", false);
- p.AddP70bool("TranslationMaxX", false);
- p.AddP70bool("TranslationMaxY", false);
- p.AddP70bool("TranslationMaxZ", false);
- p.AddP70enum("RotationOrder", 0);
- p.AddP70bool("RotationSpaceForLimitOnly", false);
- p.AddP70double("RotationStiffnessX", 0.0);
- p.AddP70double("RotationStiffnessY", 0.0);
- p.AddP70double("RotationStiffnessZ", 0.0);
- p.AddP70double("AxisLen", 10.0);
- p.AddP70vector("PreRotation", 0.0, 0.0, 0.0);
- p.AddP70vector("PostRotation", 0.0, 0.0, 0.0);
- p.AddP70bool("RotationActive", false);
- p.AddP70vector("RotationMin", 0.0, 0.0, 0.0);
- p.AddP70vector("RotationMax", 0.0, 0.0, 0.0);
- p.AddP70bool("RotationMinX", false);
- p.AddP70bool("RotationMinY", false);
- p.AddP70bool("RotationMinZ", false);
- p.AddP70bool("RotationMaxX", false);
- p.AddP70bool("RotationMaxY", false);
- p.AddP70bool("RotationMaxZ", false);
- p.AddP70enum("InheritType", 0);
- p.AddP70bool("ScalingActive", false);
- p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0);
- p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0);
- p.AddP70bool("ScalingMinX", false);
- p.AddP70bool("ScalingMinY", false);
- p.AddP70bool("ScalingMinZ", false);
- p.AddP70bool("ScalingMaxX", false);
- p.AddP70bool("ScalingMaxY", false);
- p.AddP70bool("ScalingMaxZ", false);
- p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0);
- p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0);
- p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0);
- p.AddP70double("MinDampRangeX", 0.0);
- p.AddP70double("MinDampRangeY", 0.0);
- p.AddP70double("MinDampRangeZ", 0.0);
- p.AddP70double("MaxDampRangeX", 0.0);
- p.AddP70double("MaxDampRangeY", 0.0);
- p.AddP70double("MaxDampRangeZ", 0.0);
- p.AddP70double("MinDampStrengthX", 0.0);
- p.AddP70double("MinDampStrengthY", 0.0);
- p.AddP70double("MinDampStrengthZ", 0.0);
- p.AddP70double("MaxDampStrengthX", 0.0);
- p.AddP70double("MaxDampStrengthY", 0.0);
- p.AddP70double("MaxDampStrengthZ", 0.0);
- p.AddP70double("PreferedAngleX", 0.0);
- p.AddP70double("PreferedAngleY", 0.0);
- p.AddP70double("PreferedAngleZ", 0.0);
- p.AddP70("LookAtProperty", "object", "", "");
- p.AddP70("UpVectorProperty", "object", "", "");
- p.AddP70bool("Show", true);
- p.AddP70bool("NegativePercentShapeSupport", true);
- p.AddP70int("DefaultAttributeIndex", -1);
- p.AddP70bool("Freeze", false);
- p.AddP70bool("LODBox", false);
- p.AddP70(
- "Lcl Translation", "Lcl Translation", "", "A",
- double(0), double(0), double(0)
- );
- p.AddP70(
- "Lcl Rotation", "Lcl Rotation", "", "A",
- double(0), double(0), double(0)
- );
- p.AddP70(
- "Lcl Scaling", "Lcl Scaling", "", "A",
- double(1), double(1), double(1)
- );
- p.AddP70("Visibility", "Visibility", "", "A", double(1));
- p.AddP70(
- "Visibility Inheritance", "Visibility Inheritance", "", "",
- int32_t(1)
- );
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Geometry / FbxMesh
- // <~~ aiMesh
- count = mScene->mNumMeshes;
- // Blendshapes are considered Geometry
- int32_t bsDeformerCount=0;
- for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
- aiMesh* m = mScene->mMeshes[mi];
- if (m->mNumAnimMeshes > 0) {
- count+=m->mNumAnimMeshes;
- bsDeformerCount+=m->mNumAnimMeshes; // One deformer per blendshape
- bsDeformerCount++; // Plus one master blendshape deformer
- }
- }
- if (count) {
- n = FBX::Node("ObjectType", "Geometry");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxMesh");
- p = FBX::Node("Properties70");
- p.AddP70color("Color", 0, 0, 0);
- p.AddP70vector("BBoxMin", 0, 0, 0);
- p.AddP70vector("BBoxMax", 0, 0, 0);
- p.AddP70bool("Primary Visibility", true);
- p.AddP70bool("Casts Shadows", true);
- p.AddP70bool("Receive Shadows", true);
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Material / FbxSurfacePhong, FbxSurfaceLambert, FbxSurfaceMaterial
- // <~~ aiMaterial
- // basically if there's any phong material this is defined as phong,
- // and otherwise lambert.
- // More complex materials cause a bare-bones FbxSurfaceMaterial definition
- // and are treated specially, as they're not really supported by FBX.
- // TODO: support Maya's Stingray PBS material
- count = mScene->mNumMaterials;
- if (count) {
- bool has_phong = has_phong_mat(mScene);
- n = FBX::Node("ObjectType", "Material");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate");
- if (has_phong) {
- pt.AddProperty("FbxSurfacePhong");
- } else {
- pt.AddProperty("FbxSurfaceLambert");
- }
- p = FBX::Node("Properties70");
- if (has_phong) {
- p.AddP70string("ShadingModel", "Phong");
- } else {
- p.AddP70string("ShadingModel", "Lambert");
- }
- p.AddP70bool("MultiLayer", false);
- p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0);
- p.AddP70numberA("EmissiveFactor", 1.0);
- p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2);
- p.AddP70numberA("AmbientFactor", 1.0);
- p.AddP70colorA("DiffuseColor", 0.8, 0.8, 0.8);
- p.AddP70numberA("DiffuseFactor", 1.0);
- p.AddP70vector("Bump", 0.0, 0.0, 0.0);
- p.AddP70vector("NormalMap", 0.0, 0.0, 0.0);
- p.AddP70double("BumpFactor", 1.0);
- p.AddP70colorA("TransparentColor", 0.0, 0.0, 0.0);
- p.AddP70numberA("TransparencyFactor", 0.0);
- p.AddP70color("DisplacementColor", 0.0, 0.0, 0.0);
- p.AddP70double("DisplacementFactor", 1.0);
- p.AddP70color("VectorDisplacementColor", 0.0, 0.0, 0.0);
- p.AddP70double("VectorDisplacementFactor", 1.0);
- if (has_phong) {
- p.AddP70colorA("SpecularColor", 0.2, 0.2, 0.2);
- p.AddP70numberA("SpecularFactor", 1.0);
- p.AddP70numberA("ShininessExponent", 20.0);
- p.AddP70colorA("ReflectionColor", 0.0, 0.0, 0.0);
- p.AddP70numberA("ReflectionFactor", 1.0);
- }
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Video / FbxVideo
- // one for each image file.
- count = int32_t(count_images(mScene));
- if (count) {
- n = FBX::Node("ObjectType", "Video");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxVideo");
- p = FBX::Node("Properties70");
- p.AddP70bool("ImageSequence", false);
- p.AddP70int("ImageSequenceOffset", 0);
- p.AddP70double("FrameRate", 0.0);
- p.AddP70int("LastFrame", 0);
- p.AddP70int("Width", 0);
- p.AddP70int("Height", 0);
- p.AddP70("Path", "KString", "XRefUrl", "", "");
- p.AddP70int("StartFrame", 0);
- p.AddP70int("StopFrame", 0);
- p.AddP70double("PlaySpeed", 0.0);
- p.AddP70time("Offset", 0);
- p.AddP70enum("InterlaceMode", 0);
- p.AddP70bool("FreeRunning", false);
- p.AddP70bool("Loop", false);
- p.AddP70enum("AccessMode", 0);
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Texture / FbxFileTexture
- // <~~ aiTexture
- count = int32_t(count_textures(mScene));
- if (count) {
- n = FBX::Node("ObjectType", "Texture");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxFileTexture");
- p = FBX::Node("Properties70");
- p.AddP70enum("TextureTypeUse", 0);
- p.AddP70numberA("Texture alpha", 1.0);
- p.AddP70enum("CurrentMappingType", 0);
- p.AddP70enum("WrapModeU", 0);
- p.AddP70enum("WrapModeV", 0);
- p.AddP70bool("UVSwap", false);
- p.AddP70bool("PremultiplyAlpha", true);
- p.AddP70vectorA("Translation", 0.0, 0.0, 0.0);
- p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0);
- p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0);
- p.AddP70vector("TextureRotationPivot", 0.0, 0.0, 0.0);
- p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0);
- p.AddP70enum("CurrentTextureBlendMode", 1);
- p.AddP70string("UVSet", "default");
- p.AddP70bool("UseMaterial", false);
- p.AddP70bool("UseMipMap", false);
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // AnimationCurveNode / FbxAnimCurveNode
- count = mScene->mNumAnimations * 3;
- if (count) {
- n = FBX::Node("ObjectType", "AnimationCurveNode");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode");
- p = FBX::Node("Properties70");
- p.AddP70("d", "Compound", "", "");
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // AnimationCurve / FbxAnimCurve
- count = mScene->mNumAnimations * 9;
- if (count) {
- n = FBX::Node("ObjectType", "AnimationCurve");
- n.AddChild("Count", count);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Pose
- count = 0;
- for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
- aiMesh* mesh = mScene->mMeshes[i];
- if (mesh->HasBones()) { ++count; }
- }
- if (count) {
- n = FBX::Node("ObjectType", "Pose");
- n.AddChild("Count", count);
- object_nodes.push_back(n);
- total_count += count;
- }
- // Deformer
- count = int32_t(count_deformers(mScene))+bsDeformerCount;
- if (count) {
- n = FBX::Node("ObjectType", "Deformer");
- n.AddChild("Count", count);
- object_nodes.push_back(n);
- total_count += count;
- }
- // (template)
- count = 0;
- if (count) {
- n = FBX::Node("ObjectType", "");
- n.AddChild("Count", count);
- pt = FBX::Node("PropertyTemplate", "");
- p = FBX::Node("Properties70");
- pt.AddChild(p);
- n.AddChild(pt);
- object_nodes.push_back(n);
- total_count += count;
- }
- // now write it all
- FBX::Node defs("Definitions");
- defs.AddChild("Version", int32_t(100));
- defs.AddChild("Count", int32_t(total_count));
- for (auto &on : object_nodes) {
- defs.AddChild(on);
- }
- defs.Dump(outfile, binary, 0);
- }
- // -------------------------------------------------------------------
- // some internal helper functions used for writing the objects section
- // (which holds the actual data)
- // -------------------------------------------------------------------
- static aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node) {
- for (size_t i = 0; i < node->mNumMeshes; ++i) {
- if (node->mMeshes[i] == meshIndex) {
- return node;
- }
- }
- for (size_t i = 0; i < node->mNumChildren; ++i) {
- aiNode* ret = get_node_for_mesh(meshIndex, node->mChildren[i]);
- if (ret) { return ret; }
- }
- return nullptr;
- }
- aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) {
- std::vector<const aiNode*> node_chain;
- while (node != scene->mRootNode && node != nullptr) {
- node_chain.push_back(node);
- node = node->mParent;
- }
- aiMatrix4x4 transform;
- for (auto n = node_chain.rbegin(); n != node_chain.rend(); ++n) {
- transform *= (*n)->mTransformation;
- }
- return transform;
- }
- inline int64_t to_ktime(double ticks, const aiAnimation* anim) {
- if (FP_ZERO == std::fpclassify(anim->mTicksPerSecond)) {
- return static_cast<int64_t>(ticks) * FBX::SECOND;
- }
- return (static_cast<int64_t>(ticks / anim->mTicksPerSecond)) * FBX::SECOND;
- }
- inline int64_t to_ktime(double time) {
- return (static_cast<int64_t>(time * FBX::SECOND));
- }
- void FBXExporter::WriteObjects () {
- if (!binary) {
- WriteAsciiSectionHeader("Object properties");
- }
- // numbers should match those given in definitions! make sure to check
- StreamWriterLE outstream(outfile);
- FBX::Node object_node("Objects");
- int indent = 0;
- object_node.Begin(outstream, binary, indent);
- object_node.EndProperties(outstream, binary, indent);
- object_node.BeginChildren(outstream, binary, indent);
- bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
- // save vertex_indices as it is needed later
- std::vector<std::vector<int32_t>> vVertexIndice(mScene->mNumMeshes);
- std::vector<uint32_t> uniq_v_before_mi;
- const auto bTransparencyFactorReferencedToOpacity = mProperties->GetPropertyBool(AI_CONFIG_EXPORT_FBX_TRANSPARENCY_FACTOR_REFER_TO_OPACITY, false);
- // geometry (aiMesh)
- mesh_uids.clear();
- indent = 1;
- std::function<void(const aiNode*)> visit_node_geo = [&](const aiNode *node) {
- if (node->mNumMeshes == 0) {
- for (uint32_t ni = 0; ni < node->mNumChildren; ni++) {
- visit_node_geo(node->mChildren[ni]);
- }
- return;
- }
- // start the node record
- FBX::Node n("Geometry");
- int64_t uid = generate_uid();
- mesh_uids[node] = uid;
- n.AddProperty(uid);
- n.AddProperty(FBX::SEPARATOR + "Geometry");
- n.AddProperty("Mesh");
- n.Begin(outstream, binary, indent);
- n.DumpProperties(outstream, binary, indent);
- n.EndProperties(outstream, binary, indent);
- n.BeginChildren(outstream, binary, indent);
- // output vertex data - each vertex should be unique (probably)
- std::vector<double> flattened_vertices;
- // index of original vertex in vertex data vector
- std::vector<int32_t> vertex_indices;
- std::vector<double> normal_data;
- std::vector<double> color_data;
- std::vector<int32_t> polygon_data;
- std::vector<std::vector<double>> uv_data;
- std::vector<std::vector<int32_t>> uv_indices;
- std::map<aiVector3D, int32_t> index_by_uv;
- std::vector<int32_t> offsets = { 0 };
- indent = 2;
- for (uint32_t n_mi = 0; n_mi < node->mNumMeshes; n_mi++) {
- const auto mi = node->mMeshes[n_mi];
- const aiMesh *m = mScene->mMeshes[mi];
- size_t v_offset = vertex_indices.size();
- size_t uniq_v_before = flattened_vertices.size() / 3;
- // map of vertex value to its index in the data vector
- std::map<aiVector3D,size_t> index_by_vertex_value;
- if(bJoinIdenticalVertices){
- int32_t index = 0;
- for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
- aiVector3D vtx = m->mVertices[vi];
- auto elem = index_by_vertex_value.find(vtx);
- if (elem == index_by_vertex_value.end()) {
- vertex_indices.push_back(index);
- index_by_vertex_value[vtx] = index;
- flattened_vertices.insert(flattened_vertices.end(), { vtx.x, vtx.y, vtx.z });
- ++index;
- } else {
- vertex_indices.push_back(int32_t(elem->second));
- }
- }
- } else { // do not join vertex, respect the export flag
- vertex_indices.resize(v_offset + m->mNumVertices);
- std::iota(vertex_indices.begin() + v_offset, vertex_indices.end(), (int)v_offset);
- for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
- aiVector3D vtx = m->mVertices[v];
- flattened_vertices.insert(flattened_vertices.end(), {vtx.x, vtx.y, vtx.z});
- }
- }
- vVertexIndice[mi].insert(
- // TODO test whether this can be end or not
- vVertexIndice[mi].begin(),
- vertex_indices.begin(),
- vertex_indices.end()
- );
- // here could be edges but they're insane.
- // it's optional anyway, so let's ignore it.
- // output polygon data as a flattened array of vertex indices.
- // the last vertex index of each polygon is negated and - 1
- for (size_t fi = 0; fi < m->mNumFaces; fi++) {
- const aiFace &f = m->mFaces[fi];
- size_t pvi = 0;
- for (; pvi < f.mNumIndices - 1; pvi++) {
- polygon_data.push_back(
- static_cast<int32_t>(uniq_v_before + vertex_indices[v_offset + f.mIndices[pvi]])
- );
- }
- polygon_data.push_back(
- static_cast<int32_t>(-1 - (uniq_v_before + vertex_indices[v_offset+f.mIndices[pvi]]))
- );
- }
- uniq_v_before_mi.push_back(static_cast<uint32_t>(uniq_v_before));
- if (m->HasNormals()) {
- normal_data.reserve(3 * polygon_data.size());
- for (size_t fi = 0; fi < m->mNumFaces; fi++) {
- const aiFace & f = m->mFaces[fi];
- for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
- const aiVector3D &curN = m->mNormals[f.mIndices[pvi]];
- normal_data.insert(normal_data.end(), { curN.x, curN.y, curN.z });
- }
- }
- }
- const int32_t colorChannelIndex = 0;
- if (m->HasVertexColors(colorChannelIndex)) {
- color_data.reserve(4 * polygon_data.size());
- for (size_t fi = 0; fi < m->mNumFaces; fi++) {
- const aiFace &f = m->mFaces[fi];
- for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
- const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]];
- color_data.insert(color_data.end(), { c.r, c.g, c.b, c.a });
- }
- }
- }
- const auto num_uv = static_cast<size_t>(m->GetNumUVChannels());
- uv_indices.resize(std::max(num_uv, uv_indices.size()));
- uv_data.resize(std::max(num_uv, uv_data.size()));
- // uvs, if any
- for (size_t uvi = 0; uvi < m->GetNumUVChannels(); uvi++) {
- if (m->mNumUVComponents[uvi] > 2) {
- // FBX only supports 2-channel UV maps...
- // or at least i'm not sure how to indicate a different number
- std::stringstream err;
- err << "Only 2-channel UV maps supported by FBX,";
- err << " but mesh " << mi;
- if (m->mName.length) {
- err << " (" << m->mName.C_Str() << ")";
- }
- err << " UV map " << uvi;
- err << " has " << m->mNumUVComponents[uvi];
- err << " components! Data will be preserved,";
- err << " but may be incorrectly interpreted on load.";
- ASSIMP_LOG_WARN(err.str());
- }
- int32_t index = 0;
- for (size_t fi = 0; fi < m->mNumFaces; fi++) {
- const aiFace &f = m->mFaces[fi];
- for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
- const aiVector3D &curUv = m->mTextureCoords[uvi][f.mIndices[pvi]];
- auto elem = index_by_uv.find(curUv);
- if (elem == index_by_uv.end()) {
- index_by_uv[curUv] = index;
- uv_indices[uvi].push_back(index);
- for (uint32_t x = 0; x < m->mNumUVComponents[uvi]; ++x) {
- uv_data[uvi].push_back(curUv[x]);
- }
- ++index;
- } else {
- uv_indices[uvi].push_back(elem->second);
- }
- }
- }
- }
- offsets.push_back((int32_t)polygon_data.size());
- }
- FBX::Node::WritePropertyNode("Vertices", flattened_vertices, outstream, binary, indent);
- FBX::Node::WritePropertyNode("PolygonVertexIndex", polygon_data, outstream, binary, indent);
- FBX::Node::WritePropertyNode("GeometryVersion", int32_t(124), outstream, binary, indent);
- FBX::Node normals("LayerElementNormal", int32_t(0));
- normals.Begin(outstream, binary, indent);
- normals.DumpProperties(outstream, binary, indent);
- normals.EndProperties(outstream, binary, indent);
- normals.BeginChildren(outstream, binary, indent);
- indent = 3;
- FBX::Node::WritePropertyNode("Version", int32_t(101),outstream,binary,indent);
- FBX::Node::WritePropertyNode("Name", "",outstream,binary,indent);
- FBX::Node::WritePropertyNode("MappingInformationType", "ByPolygonVertex",outstream,binary,indent);
- FBX::Node::WritePropertyNode("ReferenceInformationType", "Direct",outstream,binary,indent);
- FBX::Node::WritePropertyNode("Normals", normal_data,outstream,binary,indent);
- // note: version 102 has a NormalsW also... not sure what it is,
- // so stick with version 101 for now.
- indent = 2;
- normals.End(outstream,binary,indent,true);
- const auto colorChannelIndex = 0;
- FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
- vertexcolors.Begin(outstream, binary, indent);
- vertexcolors.DumpProperties(outstream, binary, indent);
- vertexcolors.EndProperties(outstream, binary, indent);
- vertexcolors.BeginChildren(outstream, binary, indent);
- indent = 3;
- FBX::Node::WritePropertyNode("Version", int32_t(101), outstream, binary, indent);
- char layerName[8];
- snprintf(layerName, sizeof(layerName), "COLOR_%d", colorChannelIndex);
- FBX::Node::WritePropertyNode("Name", (const char *)layerName, outstream, binary, indent);
- FBX::Node::WritePropertyNode("MappingInformationType", "ByPolygonVertex", outstream, binary, indent);
- FBX::Node::WritePropertyNode("ReferenceInformationType", "Direct", outstream, binary, indent);
- FBX::Node::WritePropertyNode("Colors", color_data, outstream, binary, indent);
- indent = 2;
- vertexcolors.End(outstream, binary, indent, true);
- for (uint32_t uvi = 0; uvi < uv_data.size(); uvi++) {
- FBX::Node uv("LayerElementUV", int32_t(uvi));
- uv.Begin(outstream, binary, indent);
- uv.DumpProperties(outstream, binary, indent);
- uv.EndProperties(outstream, binary, indent);
- uv.BeginChildren(outstream, binary, indent);
- indent = 3;
- FBX::Node::WritePropertyNode("Version", int32_t(101), outstream, binary, indent);
- FBX::Node::WritePropertyNode("Name", "", outstream, binary, indent);
- FBX::Node::WritePropertyNode("MappingInformationType", "ByPolgonVertex", outstream, binary, indent);
- FBX::Node::WritePropertyNode("ReferenceInformationType", "IndexToDirect", outstream, binary, indent);
- FBX::Node::WritePropertyNode("UV", uv_data[uvi], outstream, binary, indent);
- FBX::Node::WritePropertyNode("UVIndex", uv_indices[uvi], outstream, binary, indent);
- indent = 2;
- uv.End(outstream, binary, indent, true);
- }
- // When merging multiple meshes, we instead use by polygon so the correct material is
- // assigned to each face. Previously, this LayerElementMaterial always had 0 since it
- // assumed there was 1 material for each node for all meshes.
- FBX::Node mat("LayerElementMaterial", int32_t(0));
- mat.AddChild("Version", int32_t(101));
- mat.AddChild("Name", "");
- if (node->mNumMeshes == 1) {
- mat.AddChild("MappingInformationType", "AllSame");
- mat.AddChild("ReferenceInformationType", "IndexToDirect");
- std::vector<int32_t> mat_indices = {0};
- mat.AddChild("Materials", mat_indices);
- } else {
- mat.AddChild("MappingInformationType", "ByPolygon");
- mat.AddChild("ReferenceInformationType", "IndexToDirect");
- std::vector<int32_t> mat_indices(polygon_data.size());
- uint32_t curr_offset = 0;
- for (uint32_t mi = 0; mi < node->mNumMeshes; mi++) {
- uint32_t num_faces = mScene->mMeshes[node->mMeshes[mi]]->mNumFaces;
- for (uint32_t fi = 0; fi < num_faces; fi++) {
- mat_indices[curr_offset + fi] = mi;
- }
- curr_offset += num_faces;
- }
- mat.AddChild("Materials", mat_indices);
- }
- mat.Dump(outstream, binary, indent);
- // finally we have the layer specifications,
- // which select the normals / UV set / etc to use.
- // TODO: handle multiple uv sets correctly?
- FBX::Node layer("Layer", int32_t(0));
- layer.AddChild("Version", int32_t(100));
- FBX::Node le("LayerElement");
- le.AddChild("Type", "LayerElementNormal");
- le.AddChild("TypedIndex", int32_t(0));
- layer.AddChild(le);
- le = FBX::Node("LayerElement");
- le.AddChild("Type", "LayerElementColor");
- le.AddChild("TypedIndex", int32_t(0));
- layer.AddChild(le);
- le = FBX::Node("LayerElement");
- le.AddChild("Type", "LayerElementMaterial");
- le.AddChild("TypedIndex", int32_t(0));
- layer.AddChild(le);
- le = FBX::Node("LayerElement");
- le.AddChild("Type", "LayerElementUV");
- le.AddChild("TypedIndex", int32_t(0));
- layer.AddChild(le);
- layer.Dump(outstream, binary, indent);
- for(unsigned int lr = 1; lr < uv_data.size(); ++ lr) {
- FBX::Node layerExtra("Layer", int32_t(lr));
- layerExtra.AddChild("Version", int32_t(100));
- FBX::Node leExtra("LayerElement");
- leExtra.AddChild("Type", "LayerElementUV");
- leExtra.AddChild("TypedIndex", int32_t(lr));
- layerExtra.AddChild(leExtra);
- layerExtra.Dump(outstream, binary, indent);
- }
- // finish the node record
- indent = 1;
- n.End(outstream, binary, indent, true);
- for (uint32_t ni = 0; ni < node->mNumChildren; ni++) {
- visit_node_geo(node->mChildren[ni]);
- }
- return;
- };
- visit_node_geo(mScene->mRootNode);
- // aiMaterial
- material_uids.clear();
- for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
- // it's all about this material
- aiMaterial* m = mScene->mMaterials[i];
- // these are used to receive material data
- ai_real f; aiColor3D c;
- // start the node record
- FBX::Node n("Material");
- int64_t uid = generate_uid();
- material_uids.push_back(uid);
- n.AddProperty(uid);
- aiString name;
- m->Get(AI_MATKEY_NAME, name);
- n.AddProperty(name.C_Str() + FBX::SEPARATOR + "Material");
- n.AddProperty("");
- n.AddChild("Version", int32_t(102));
- f = 0;
- m->Get(AI_MATKEY_SHININESS, f);
- bool phong = (f > 0);
- if (phong) {
- n.AddChild("ShadingModel", "phong");
- } else {
- n.AddChild("ShadingModel", "lambert");
- }
- n.AddChild("MultiLayer", int32_t(0));
- FBX::Node p("Properties70");
- // materials exported using the FBX SDK have two sets of fields.
- // there are the properties specified in the PropertyTemplate,
- // which are those supported by the modernFBX SDK,
- // and an extra set of properties with simpler names.
- // The extra properties are a legacy material system from pre-2009.
- //
- // In the modern system, each property has "color" and "factor".
- // Generally the interpretation of these seems to be
- // that the colour is multiplied by the factor before use,
- // but this is not always clear-cut.
- //
- // Usually assimp only stores the colour,
- // so we can just leave the factors at the default "1.0".
- // first we can export the "standard" properties
- if (m->Get(AI_MATKEY_COLOR_AMBIENT, c) == aiReturn_SUCCESS) {
- p.AddP70colorA("AmbientColor", c.r, c.g, c.b);
- //p.AddP70numberA("AmbientFactor", 1.0);
- }
- if (m->Get(AI_MATKEY_COLOR_DIFFUSE, c) == aiReturn_SUCCESS) {
- p.AddP70colorA("DiffuseColor", c.r, c.g, c.b);
- //p.AddP70numberA("DiffuseFactor", 1.0);
- }
- if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
- // "TransparentColor" / "TransparencyFactor"...
- // thanks FBX, for your insightful interpretation of consistency
- p.AddP70colorA("TransparentColor", c.r, c.g, c.b);
- if (!bTransparencyFactorReferencedToOpacity) {
- // TransparencyFactor defaults to 0.0, so set it to 1.0.
- // note: Maya always sets this to 1.0,
- // so we can't use it sensibly as "Opacity".
- // In stead we rely on the legacy "Opacity" value, below.
- // Blender also relies on "Opacity" not "TransparencyFactor",
- // probably for a similar reason.
- p.AddP70numberA("TransparencyFactor", 1.0);
- }
- }
- if (bTransparencyFactorReferencedToOpacity) {
- if (m->Get(AI_MATKEY_OPACITY, f) == aiReturn_SUCCESS) {
- p.AddP70numberA("TransparencyFactor", 1.0 - f);
- }
- }
- if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) {
- p.AddP70colorA("ReflectionColor", c.r, c.g, c.b);
- }
- if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
- p.AddP70numberA("ReflectionFactor", f);
- }
- if (phong) {
- if (m->Get(AI_MATKEY_COLOR_SPECULAR, c) == aiReturn_SUCCESS) {
- p.AddP70colorA("SpecularColor", c.r, c.g, c.b);
- }
- if (m->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS) {
- p.AddP70numberA("ShininessFactor", f);
- }
- if (m->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS) {
- p.AddP70numberA("ShininessExponent", f);
- }
- if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
- p.AddP70numberA("ReflectionFactor", f);
- }
- }
- // Now the legacy system.
- // For safety let's include it.
- // thrse values don't exist in the property template,
- // and usually are completely ignored when loading.
- // One notable exception is the "Opacity" property,
- // which Blender uses as (1.0 - alpha).
- c.r = 0.0f; c.g = 0.0f; c.b = 0.0f;
- m->Get(AI_MATKEY_COLOR_EMISSIVE, c);
- p.AddP70vector("Emissive", c.r, c.g, c.b);
- c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
- m->Get(AI_MATKEY_COLOR_AMBIENT, c);
- p.AddP70vector("Ambient", c.r, c.g, c.b);
- c.r = 0.8f; c.g = 0.8f; c.b = 0.8f;
- m->Get(AI_MATKEY_COLOR_DIFFUSE, c);
- p.AddP70vector("Diffuse", c.r, c.g, c.b);
- // The FBX SDK determines "Opacity" from transparency colour (RGB)
- // and factor (F) as: O = (1.0 - F * ((R + G + B) / 3)).
- // However we actually have an opacity value,
- // so we should take it from AI_MATKEY_OPACITY if possible.
- // It might make more sense to use TransparencyFactor,
- // but Blender actually loads "Opacity" correctly, so let's use it.
- f = 1.0f;
- if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
- f = 1.0f - ((c.r + c.g + c.b) / 3.0f);
- }
- m->Get(AI_MATKEY_OPACITY, f);
- p.AddP70double("Opacity", f);
- if (phong) {
- // specular color is multiplied by shininess_strength
- c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
- m->Get(AI_MATKEY_COLOR_SPECULAR, c);
- f = 1.0f;
- m->Get(AI_MATKEY_SHININESS_STRENGTH, f);
- p.AddP70vector("Specular", f*c.r, f*c.g, f*c.b);
- f = 20.0f;
- m->Get(AI_MATKEY_SHININESS, f);
- p.AddP70double("Shininess", f);
- // Legacy "Reflectivity" is F*F*((R+G+B)/3),
- // where F is the proportion of light reflected (AKA reflectivity),
- // and RGB is the reflective colour of the material.
- // No idea why, but we might as well set it the same way.
- f = 0.0f;
- m->Get(AI_MATKEY_REFLECTIVITY, f);
- c.r = 1.0f, c.g = 1.0f, c.b = 1.0f;
- m->Get(AI_MATKEY_COLOR_REFLECTIVE, c);
- p.AddP70double("Reflectivity", f*f*((c.r+c.g+c.b)/3.0));
- }
- n.AddChild(p);
- n.Dump(outstream, binary, indent);
- }
- // we need to look up all the images we're using,
- // so we can generate uids, and eliminate duplicates.
- std::map<std::string, int64_t> uid_by_image;
- for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
- aiString texpath;
- aiMaterial* mat = mScene->mMaterials[i];
- for (
- size_t tt = aiTextureType_DIFFUSE;
- tt < aiTextureType_UNKNOWN;
- ++tt
- ){
- const aiTextureType textype = static_cast<aiTextureType>(tt);
- const size_t texcount = mat->GetTextureCount(textype);
- for (size_t j = 0; j < texcount; ++j) {
- mat->GetTexture(textype, (unsigned int)j, &texpath);
- const std::string texstring = texpath.C_Str();
- auto elem = uid_by_image.find(texstring);
- if (elem == uid_by_image.end()) {
- uid_by_image[texstring] = generate_uid();
- }
- }
- }
- }
- // FbxVideo - stores images used by textures.
- for (const auto &it : uid_by_image) {
- FBX::Node n("Video");
- const int64_t& uid = it.second;
- const std::string name = ""; // TODO: ... name???
- n.AddProperties(uid, name + FBX::SEPARATOR + "Video", "Clip");
- n.AddChild("Type", "Clip");
- FBX::Node p("Properties70");
- // TODO: get full path... relative path... etc... ugh...
- // for now just use the same path for everything,
- // and hopefully one of them will work out.
- std::string path = it.first;
- // try get embedded texture
- const aiTexture* embedded_texture = mScene->GetEmbeddedTexture(it.first.c_str());
- if (embedded_texture != nullptr) {
- // change the path (use original filename, if available. If name is empty, concatenate texture index with file extension)
- std::stringstream newPath;
- if (embedded_texture->mFilename.length > 0) {
- newPath << embedded_texture->mFilename.C_Str();
- } else if (embedded_texture->achFormatHint[0]) {
- int texture_index = std::stoi(path.substr(1, path.size() - 1));
- newPath << texture_index << "." << embedded_texture->achFormatHint;
- }
- path = newPath.str();
- // embed the texture
- size_t texture_size = static_cast<size_t>(embedded_texture->mWidth * std::max(embedded_texture->mHeight, 1u));
- if (binary) {
- // embed texture as binary data
- std::vector<uint8_t> tex_data;
- tex_data.resize(texture_size);
- memcpy(&tex_data[0], (char*)embedded_texture->pcData, texture_size);
- n.AddChild("Content", tex_data);
- } else {
- // embed texture in base64 encoding
- std::string encoded_texture = FBX::Util::EncodeBase64((char*)embedded_texture->pcData, texture_size);
- n.AddChild("Content", encoded_texture);
- }
- }
- p.AddP70("Path", "KString", "XRefUrl", "", path);
- n.AddChild(p);
- n.AddChild("UseMipMap", int32_t(0));
- n.AddChild("Filename", path);
- n.AddChild("RelativeFilename", path);
- n.Dump(outstream, binary, indent);
- }
- // Textures
- // referenced by material_index/texture_type pairs.
- std::map<std::pair<size_t,size_t>,int64_t> texture_uids;
- const std::map<aiTextureType,std::string> prop_name_by_tt = {
- {aiTextureType_DIFFUSE, "DiffuseColor"},
- {aiTextureType_SPECULAR, "SpecularColor"},
- {aiTextureType_AMBIENT, "AmbientColor"},
- {aiTextureType_EMISSIVE, "EmissiveColor"},
- {aiTextureType_HEIGHT, "Bump"},
- {aiTextureType_NORMALS, "NormalMap"},
- {aiTextureType_SHININESS, "ShininessExponent"},
- {aiTextureType_OPACITY, "TransparentColor"},
- {aiTextureType_DISPLACEMENT, "DisplacementColor"},
- //{aiTextureType_LIGHTMAP, "???"},
- {aiTextureType_REFLECTION, "ReflectionColor"}
- //{aiTextureType_UNKNOWN, ""}
- };
- for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
- // textures are attached to materials
- aiMaterial* mat = mScene->mMaterials[i];
- int64_t material_uid = material_uids[i];
- for (
- size_t j = aiTextureType_DIFFUSE;
- j < aiTextureType_UNKNOWN;
- ++j
- ) {
- const aiTextureType tt = static_cast<aiTextureType>(j);
- size_t n = mat->GetTextureCount(tt);
- if (n < 1) { // no texture of this type
- continue;
- }
- if (n > 1) {
- // TODO: multilayer textures
- std::stringstream err;
- err << "Multilayer textures not supported (for now),";
- err << " skipping texture type " << j;
- err << " of material " << i;
- ASSIMP_LOG_WARN(err.str());
- }
- // get image path for this (single-image) texture
- aiString tpath;
- if (mat->GetTexture(tt, 0, &tpath) != aiReturn_SUCCESS) {
- std::stringstream err;
- err << "Failed to get texture 0 for texture of type " << tt;
- err << " on material " << i;
- err << ", however GetTextureCount returned 1.";
- throw DeadlyExportError(err.str());
- }
- const std::string texture_path(tpath.C_Str());
- // get connected image uid
- auto elem = uid_by_image.find(texture_path);
- if (elem == uid_by_image.end()) {
- // this should never happen
- std::stringstream err;
- err << "Failed to find video element for texture with path";
- err << " \"" << texture_path << "\"";
- err << ", type " << j << ", material " << i;
- throw DeadlyExportError(err.str());
- }
- const int64_t image_uid = elem->second;
- // get the name of the material property to connect to
- auto elem2 = prop_name_by_tt.find(tt);
- if (elem2 == prop_name_by_tt.end()) {
- // don't know how to handle this type of texture,
- // so skip it.
- std::stringstream err;
- err << "Not sure how to handle texture of type " << j;
- err << " on material " << i;
- err << ", skipping...";
- ASSIMP_LOG_WARN(err.str());
- continue;
- }
- const std::string& prop_name = elem2->second;
- // generate a uid for this texture
- const int64_t texture_uid = generate_uid();
- // link the texture to the material
- connections.emplace_back(
- "C", "OP", texture_uid, material_uid, prop_name
- );
- // link the image data to the texture
- connections.emplace_back("C", "OO", image_uid, texture_uid);
- aiUVTransform trafo;
- unsigned int max = sizeof(aiUVTransform);
- aiGetMaterialFloatArray(mat, AI_MATKEY_UVTRANSFORM(aiTextureType_DIFFUSE, 0), (ai_real *)&trafo, &max);
- // now write the actual texture node
- FBX::Node tnode("Texture");
- // TODO: some way to determine texture name?
- const std::string texture_name = "" + FBX::SEPARATOR + "Texture";
- tnode.AddProperties(texture_uid, texture_name, "");
- // there really doesn't seem to be a better type than this:
- tnode.AddChild("Type", "TextureVideoClip");
- tnode.AddChild("Version", int32_t(202));
- tnode.AddChild("TextureName", texture_name);
- FBX::Node p("Properties70");
- p.AddP70vectorA("Translation", trafo.mTranslation[0], trafo.mTranslation[1], 0.0);
- p.AddP70vectorA("Rotation", 0, 0, trafo.mRotation);
- p.AddP70vectorA("Scaling", trafo.mScaling[0], trafo.mScaling[1], 0.0);
- p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify
- //p.AddP70string("UVSet", ""); // TODO: how should this work?
- p.AddP70bool("UseMaterial", true);
- tnode.AddChild(p);
- // can't easily determine which texture path will be correct,
- // so just store what we have in every field.
- // these being incorrect is a common problem with FBX anyway.
- tnode.AddChild("FileName", texture_path);
- tnode.AddChild("RelativeFilename", texture_path);
- tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0));
- tnode.AddChild("ModelUVScaling", double(1.0), double(1.0));
- tnode.AddChild("Texture_Alpha_Source", "None");
- tnode.AddChild(
- "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
- );
- tnode.Dump(outstream, binary, indent);
- }
- }
- // Blendshapes, if any
- for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
- const aiMesh* m = mScene->mMeshes[mi];
- if (m->mNumAnimMeshes == 0) {
- continue;
- }
- // make a deformer for this mesh
- int64_t deformer_uid = generate_uid();
- FBX::Node dnode("Deformer");
- dnode.AddProperties(deformer_uid, m->mName.data + FBX::SEPARATOR + "Blendshapes", "BlendShape");
- dnode.AddChild("Version", int32_t(101));
- dnode.Dump(outstream, binary, indent);
- // connect it
- const auto node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
- connections.emplace_back("C", "OO", deformer_uid, mesh_uids[node]);
- std::vector<int32_t> vertex_indices = vVertexIndice[mi];
- for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) {
- aiAnimMesh *pAnimMesh = m->mAnimMeshes[am];
- std::string blendshape_name = pAnimMesh->mName.data;
- // start the node record
- FBX::Node bsnode("Geometry");
- int64_t blendshape_uid = generate_uid();
- blendshape_uids.push_back(blendshape_uid);
- bsnode.AddProperty(blendshape_uid);
- bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Geometry");
- bsnode.AddProperty("Shape");
- bsnode.AddChild("Version", int32_t(100));
- bsnode.Begin(outstream, binary, indent);
- bsnode.DumpProperties(outstream, binary, indent);
- bsnode.EndProperties(outstream, binary, indent);
- bsnode.BeginChildren(outstream, binary, indent);
- indent++;
- if (pAnimMesh->HasPositions()) {
- std::vector<int32_t>shape_indices;
- std::vector<float>pPositionDiff;
- std::vector<float>pNormalDiff;
- for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) {
- aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[vt]]);
- shape_indices.push_back(vertex_indices[vt]);
- pPositionDiff.push_back(pDiff[0]);
- pPositionDiff.push_back(pDiff[1]);
- pPositionDiff.push_back(pDiff[2]);
- if (pAnimMesh->HasNormals()) {
- aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]);
- pNormalDiff.push_back(nDiff[0]);
- pNormalDiff.push_back(nDiff[1]);
- pNormalDiff.push_back(nDiff[2]);
- } else {
- pNormalDiff.push_back(0.0);
- pNormalDiff.push_back(0.0);
- pNormalDiff.push_back(0.0);
- }
- }
- FBX::Node::WritePropertyNode(
- "Indexes", shape_indices, outstream, binary, indent
- );
- FBX::Node::WritePropertyNode(
- "Vertices", pPositionDiff, outstream, binary, indent
- );
- if (pNormalDiff.size()>0) {
- FBX::Node::WritePropertyNode(
- "Normals", pNormalDiff, outstream, binary, indent
- );
- }
- }
- indent--;
- bsnode.End(outstream, binary, indent, true);
- // Add blendshape Channel Deformer
- FBX::Node sdnode("Deformer");
- const int64_t blendchannel_uid = generate_uid();
- sdnode.AddProperties(
- blendchannel_uid, blendshape_name + FBX::SEPARATOR + "SubDeformer", "BlendShapeChannel"
- );
- sdnode.AddChild("Version", int32_t(100));
- sdnode.AddChild("DeformPercent", float(0.0));
- FBX::Node p("Properties70");
- p.AddP70numberA("DeformPercent", 0.0);
- sdnode.AddChild(p);
- // TODO: Normally just one weight per channel, adding stub for later development
- std::vector<double>fFullWeights;
- fFullWeights.push_back(100.);
- sdnode.AddChild("FullWeights", fFullWeights);
- sdnode.Dump(outstream, binary, indent);
- connections.emplace_back("C", "OO", blendchannel_uid, deformer_uid);
- connections.emplace_back("C", "OO", blendshape_uid, blendchannel_uid);
- }
- }
- // bones.
- //
- // output structure:
- // subset of node hierarchy that are "skeleton",
- // i.e. do not have meshes but only bones.
- // but.. i'm not sure how anyone could guarantee that...
- //
- // input...
- // well, for each mesh it has "bones",
- // and the bone names correspond to nodes.
- // of course we also need the parent nodes,
- // as they give some of the transform........
- //
- // well. we can assume a sane input, i suppose.
- //
- // so input is the bone node hierarchy,
- // with an extra thing for the transformation of the MESH in BONE space.
- //
- // output is a set of bone nodes,
- // a "bindpose" which indicates the default local transform of all bones,
- // and a set of "deformers".
- // each deformer is parented to a mesh geometry,
- // and has one or more "subdeformer"s as children.
- // each subdeformer has one bone node as a child,
- // and represents the influence of that bone on the grandparent mesh.
- // the subdeformer has a list of indices, and weights,
- // with indices specifying vertex indices,
- // and weights specifying the corresponding influence of this bone.
- // it also has Transform and TransformLink elements,
- // specifying the transform of the MESH in BONE space,
- // and the transformation of the BONE in WORLD space,
- // likely in the bindpose.
- //
- // the input bone structure is different but similar,
- // storing the number of weights for this bone,
- // and an array of (vertex index, weight) pairs.
- //
- // one sticky point is that the number of vertices may not match,
- // because assimp splits vertices by normal, uv, etc.
- // first we should mark the skeleton for each mesh.
- // the skeleton must include not only the aiBones,
- // but also all their parent nodes.
- // anything that affects the position of any bone node must be included.
- // note that we want to preserve input order as much as possible here.
- // previously, sorting by name lead to consistent output across systems, but was not
- // suitable for downstream consumption by some applications.
- std::vector<std::vector<const aiNode*>> skeleton_by_mesh(mScene->mNumMeshes);
- // at the same time we can build a list of all the skeleton nodes,
- // which will be used later to mark them as type "limbNode".
- std::unordered_set<const aiNode*> limbnodes;
- //actual bone nodes in fbx, without parenting-up
- std::vector<std::string> allBoneNames;
- for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) {
- aiMesh* pMesh = mScene->mMeshes[m];
- for(unsigned int b = 0; b < pMesh->mNumBones; ++ b)
- allBoneNames.push_back(pMesh->mBones[b]->mName.data);
- }
- aiMatrix4x4 mxTransIdentity;
- // and a map of nodes by bone name, as finding them is annoying.
- std::map<std::string,aiNode*> node_by_bone;
- for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
- const aiMesh* m = mScene->mMeshes[mi];
- std::vector<const aiNode*> skeleton;
- for (size_t bi =0; bi < m->mNumBones; ++bi) {
- const aiBone* b = m->mBones[bi];
- const std::string name(b->mName.C_Str());
- auto elem = node_by_bone.find(name);
- aiNode* n;
- if (elem != node_by_bone.end()) {
- n = elem->second;
- } else {
- n = mScene->mRootNode->FindNode(b->mName);
- if (!n) {
- // this should never happen
- std::stringstream err;
- err << "Failed to find node for bone: \"" << name << "\"";
- throw DeadlyExportError(err.str());
- }
- node_by_bone[name] = n;
- limbnodes.insert(n);
- }
- skeleton.push_back(n);
- // mark all parent nodes as skeleton as well,
- // up until we find the root node,
- // or else the node containing the mesh,
- // or else the parent of a node containing the mesh.
- for (
- const aiNode* parent = n->mParent;
- parent && parent != mScene->mRootNode;
- parent = parent->mParent
- ) {
- // if we've already done this node we can skip it all
- if (std::find(skeleton.begin(), skeleton.end(), parent) != skeleton.end()) {
- break;
- }
- // ignore fbx transform nodes as these will be collapsed later
- // TODO: cache this by aiNode*
- const std::string node_name(parent->mName.C_Str());
- if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
- continue;
- }
- //not a bone in scene && no effect in transform
- if (std::find(allBoneNames.begin(), allBoneNames.end(), node_name) == allBoneNames.end()
- && parent->mTransformation == mxTransIdentity) {
- continue;
- }
- // otherwise check if this is the root of the skeleton
- bool end = false;
- // is the mesh part of this node?
- for (size_t i = 0; i < parent->mNumMeshes && !end; ++i) {
- end |= parent->mMeshes[i] == mi;
- }
- // is the mesh in one of the children of this node?
- for (size_t j = 0; j < parent->mNumChildren && !end; ++j) {
- aiNode* child = parent->mChildren[j];
- for (size_t i = 0; i < child->mNumMeshes && !end; ++i) {
- end |= child->mMeshes[i] == mi;
- }
- }
- // if it was the skeleton root we can finish here
- if (end) { break; }
- }
- }
- skeleton_by_mesh[mi] = skeleton;
- }
- // we'll need the uids for the bone nodes, so generate them now
- for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
- auto &s = skeleton_by_mesh[i];
- for (const aiNode* n : s) {
- if (node_uids.find(n) == node_uids.end()) {
- node_uids[n] = generate_uid();
- }
- }
- }
- // now, for each aiMesh, we need to export a deformer,
- // and for each aiBone a subdeformer,
- // which should have all the skinning info.
- // these will need to be connected properly to the mesh,
- // and we can do that all now.
- for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
- const aiMesh* m = mScene->mMeshes[mi];
- if (!m->HasBones()) {
- continue;
- }
- const aiNode *mesh_node = get_node_for_mesh((uint32_t)mi, mScene->mRootNode);
- // make a deformer for this mesh
- int64_t deformer_uid = generate_uid();
- FBX::Node dnode("Deformer");
- dnode.AddProperties(deformer_uid, FBX::SEPARATOR + "Deformer", "Skin");
- dnode.AddChild("Version", int32_t(101));
- // "acuracy"... this is not a typo....
- dnode.AddChild("Link_DeformAcuracy", double(50));
- dnode.AddChild("SkinningType", "Linear"); // TODO: other modes?
- dnode.Dump(outstream, binary, indent);
- // connect it
- connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mesh_node]);
- // TODO, FIXME: this won't work if anything is not in the bind pose.
- // for now if such a situation is detected, we throw an exception.
- std::set<const aiBone*> not_in_bind_pose;
- std::set<const aiNode*> no_offset_matrix;
- // first get this mesh's position in world space,
- // as we'll need it for each subdeformer.
- //
- // ...of course taking the position of the MESH doesn't make sense,
- // as it can be instanced to many nodes.
- // All we can do is assume no instancing,
- // and take the first node we find that contains the mesh.
- aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
- // now make a subdeformer for each bone in the skeleton
- const auto & skeleton= skeleton_by_mesh[mi];
- for (const aiNode* bone_node : skeleton) {
- // if there's a bone for this node, find it
- const aiBone* b = nullptr;
- for (size_t bi = 0; bi < m->mNumBones; ++bi) {
- // TODO: this probably should index by something else
- const std::string name(m->mBones[bi]->mName.C_Str());
- if (node_by_bone[name] == bone_node) {
- b = m->mBones[bi];
- break;
- }
- }
- if (!b) {
- no_offset_matrix.insert(bone_node);
- }
- // start the subdeformer node
- const int64_t subdeformer_uid = generate_uid();
- FBX::Node sdnode("Deformer");
- sdnode.AddProperties(
- subdeformer_uid, FBX::SEPARATOR + "SubDeformer", "Cluster"
- );
- sdnode.AddChild("Version", int32_t(100));
- sdnode.AddChild("UserData", "", "");
- // add indices and weights, if any
- if (b) {
- std::set<int32_t> setWeightedVertex;
- std::vector<int32_t> subdef_indices;
- std::vector<double> subdef_weights;
- int32_t last_index = -1;
- for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
- int32_t vi = vVertexIndice[mi][b->mWeights[wi].mVertexId] \
- + uniq_v_before_mi[mi];
- bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end());
- if (vi == last_index || bIsWeightedAlready) {
- // only for vertices we exported to fbx
- // TODO, FIXME: this assumes identically-located vertices
- // will always deform in the same way.
- // as assimp doesn't store a separate list of "positions",
- // there's not much that can be done about this
- // other than assuming that identical position means
- // identical vertex.
- continue;
- }
- setWeightedVertex.insert(vi);
- subdef_indices.push_back(vi);
- subdef_weights.push_back(b->mWeights[wi].mWeight);
- last_index = vi;
- }
- // yes, "indexes"
- sdnode.AddChild("Indexes", subdef_indices);
- sdnode.AddChild("Weights", subdef_weights);
- }
- // transform is the transform of the mesh, but in bone space.
- // if the skeleton is in the bind pose,
- // we can take the inverse of the world-space bone transform
- // and multiply by the world-space transform of the mesh.
- aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene);
- aiMatrix4x4 inverse_bone_xform = bone_xform;
- inverse_bone_xform.Inverse();
- aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
- sdnode.AddChild("Transform", tr);
- sdnode.AddChild("TransformLink", bone_xform);
- // note: this means we ALWAYS rely on the mesh node transform
- // being unchanged from the time the skeleton was bound.
- // there's not really any way around this at the moment.
- // done
- sdnode.Dump(outstream, binary, indent);
- // lastly, connect to the parent deformer
- connections.emplace_back(
- "C", "OO", subdeformer_uid, deformer_uid
- );
- // we also need to connect the limb node to the subdeformer.
- connections.emplace_back(
- "C", "OO", node_uids[bone_node], subdeformer_uid
- );
- }
- // if we cannot create a valid FBX file, simply die.
- // this will both prevent unnecessary bug reports,
- // and tell the user what they can do to fix the situation
- // (i.e. export their model in the bind pose).
- if (no_offset_matrix.size() && not_in_bind_pose.size()) {
- std::stringstream err;
- err << "Not enough information to construct bind pose";
- err << " for mesh " << mi << "!";
- err << " Transform matrix for bone \"";
- err << (*not_in_bind_pose.begin())->mName.C_Str() << "\"";
- if (not_in_bind_pose.size() > 1) {
- err << " (and " << not_in_bind_pose.size() - 1 << " more)";
- }
- err << " does not match mOffsetMatrix,";
- err << " and node \"";
- err << (*no_offset_matrix.begin())->mName.C_Str() << "\"";
- if (no_offset_matrix.size() > 1) {
- err << " (and " << no_offset_matrix.size() - 1 << " more)";
- }
- err << " has no offset matrix to rely on.";
- err << " Please ensure bones are in the bind pose to export.";
- throw DeadlyExportError(err.str());
- }
- }
- // BindPose
- //
- // This is a legacy system, which should be unnecessary.
- //
- // Somehow including it slows file loading by the official FBX SDK,
- // and as it can reconstruct it from the deformers anyway,
- // this is not currently included.
- //
- // The code is kept here in case it's useful in the future,
- // but it's pretty much a hack anyway,
- // as assimp doesn't store bindpose information for full skeletons.
- //
- /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
- aiMesh* mesh = mScene->mMeshes[mi];
- if (! mesh->HasBones()) { continue; }
- int64_t bindpose_uid = generate_uid();
- FBX::Node bpnode("Pose");
- bpnode.AddProperty(bindpose_uid);
- // note: this uid is never linked or connected to anything.
- bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name
- bpnode.AddProperty("BindPose");
- bpnode.AddChild("Type", "BindPose");
- bpnode.AddChild("Version", int32_t(100));
- aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode);
- // next get the whole skeleton for this mesh.
- // we need it all to define the bindpose section.
- // the FBX SDK will complain if it's missing,
- // and also if parents of used bones don't have a subdeformer.
- // order shouldn't matter.
- std::set<aiNode*> skeleton;
- for (size_t bi = 0; bi < mesh->mNumBones; ++bi) {
- // bone node should have already been indexed
- const aiBone* b = mesh->mBones[bi];
- const std::string bone_name(b->mName.C_Str());
- aiNode* parent = node_by_bone[bone_name];
- // insert all nodes down to the root or mesh node
- while (
- parent
- && parent != mScene->mRootNode
- && parent != mesh_node
- ) {
- skeleton.insert(parent);
- parent = parent->mParent;
- }
- }
- // number of pose nodes. includes one for the mesh itself.
- bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size()));
- // the first pose node is always the mesh itself
- FBX::Node pose("PoseNode");
- pose.AddChild("Node", mesh_uids[mi]);
- aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene);
- pose.AddChild("Matrix", mesh_node_xform);
- bpnode.AddChild(pose);
- for (aiNode* bonenode : skeleton) {
- // does this node have a uid yet?
- int64_t node_uid;
- auto node_uid_iter = node_uids.find(bonenode);
- if (node_uid_iter != node_uids.end()) {
- node_uid = node_uid_iter->second;
- } else {
- node_uid = generate_uid();
- node_uids[bonenode] = node_uid;
- }
- // make a pose thingy
- pose = FBX::Node("PoseNode");
- pose.AddChild("Node", node_uid);
- aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene);
- pose.AddChild("Matrix", node_xform);
- bpnode.AddChild(pose);
- }
- // now write it
- bpnode.Dump(outstream, binary, indent);
- }*/
- // lights
- indent = 1;
- lights_uids.clear();
- for (size_t li = 0; li < mScene->mNumLights; ++li) {
- aiLight* l = mScene->mLights[li];
- int64_t uid = generate_uid();
- const std::string lightNodeAttributeName = l->mName.C_Str() + FBX::SEPARATOR + "NodeAttribute";
- FBX::Node lna("NodeAttribute");
- lna.AddProperties(uid, lightNodeAttributeName, "Light");
- FBX::Node lnap("Properties70");
- // Light color.
- lnap.AddP70colorA("Color", l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b);
- // TODO Assimp light description is quite concise and do not handle light intensity.
- // Default value to 1000W.
- lnap.AddP70numberA("Intensity", 1000);
- // FBXLight::EType conversion
- switch (l->mType) {
- case aiLightSource_POINT:
- lnap.AddP70enum("LightType", 0);
- break;
- case aiLightSource_DIRECTIONAL:
- lnap.AddP70enum("LightType", 1);
- break;
- case aiLightSource_SPOT:
- lnap.AddP70enum("LightType", 2);
- lnap.AddP70numberA("InnerAngle", AI_RAD_TO_DEG(l->mAngleInnerCone));
- lnap.AddP70numberA("OuterAngle", AI_RAD_TO_DEG(l->mAngleOuterCone));
- break;
- // TODO Assimp do not handle 'area' nor 'volume' lights, but FBX does.
- /*case aiLightSource_AREA:
- lnap.AddP70enum("LightType", 3);
- lnap.AddP70enum("AreaLightShape", 0); // 0=Rectangle, 1=Sphere
- break;
- case aiLightSource_VOLUME:
- lnap.AddP70enum("LightType", 4);
- break;*/
- default:
- break;
- }
- // Did not understood how to configure the decay so disabling attenuation.
- lnap.AddP70enum("DecayType", 0);
- // Dump to FBX stream
- lna.AddChild(lnap);
- lna.AddChild("TypeFlags", FBX::FBXExportProperty("Light"));
- lna.AddChild("GeometryVersion", FBX::FBXExportProperty(int32_t(124)));
- lna.Dump(outstream, binary, indent);
- // Store name and uid (will be used later when parsing scene nodes)
- lights_uids[l->mName.C_Str()] = uid;
- }
- // TODO: cameras
- // write nodes (i.e. model hierarchy)
- // start at root node
- WriteModelNodes(
- outstream, mScene->mRootNode, 0, limbnodes
- );
- // animations
- //
- // in FBX there are:
- // * AnimationStack - corresponds to an aiAnimation
- // * AnimationLayer - a combinable animation component
- // * AnimationCurveNode - links the property to be animated
- // * AnimationCurve - defines animation data for a single property value
- //
- // the CurveNode also provides the default value for a property,
- // such as the X, Y, Z coordinates for animatable translation.
- //
- // the Curve only specifies values for one component of the property,
- // so there will be a separate AnimationCurve for X, Y, and Z.
- //
- // Assimp has:
- // * aiAnimation - basically corresponds to an AnimationStack
- // * aiNodeAnim - defines all animation for one aiNode
- // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S
- //
- // assimp has no equivalent for AnimationLayer,
- // and these are flattened on FBX import.
- // we can assume there will be one per AnimationStack.
- //
- // the aiNodeAnim contains all animation data for a single aiNode,
- // which will correspond to three AnimationCurveNode's:
- // one each for translation, rotation and scale.
- // The data for each of these will be put in 9 AnimationCurve's,
- // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc.
- // AnimationStack / aiAnimation
- std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations);
- for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
- int64_t animstack_uid = generate_uid();
- animation_stack_uids[ai] = animstack_uid;
- const aiAnimation* anim = mScene->mAnimations[ai];
- FBX::Node asnode("AnimationStack");
- std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack";
- asnode.AddProperties(animstack_uid, name, "");
- FBX::Node p("Properties70");
- p.AddP70time("LocalStart", 0); // assimp doesn't store this
- p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim));
- p.AddP70time("ReferenceStart", 0);
- p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim));
- asnode.AddChild(p);
- // this node absurdly always pretends it has children
- // (in this case it does, but just in case...)
- asnode.force_has_children = true;
- asnode.Dump(outstream, binary, indent);
- // note: animation stacks are not connected to anything
- }
- // AnimationLayer - one per aiAnimation
- std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations);
- for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
- int64_t animlayer_uid = generate_uid();
- animation_layer_uids[ai] = animlayer_uid;
- FBX::Node alnode("AnimationLayer");
- alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
- // this node absurdly always pretends it has children
- alnode.force_has_children = true;
- alnode.Dump(outstream, binary, indent);
- // connect to the relevant animstack
- connections.emplace_back(
- "C", "OO", animlayer_uid, animation_stack_uids[ai]
- );
- }
- // AnimCurveNode - three per aiNodeAnim
- std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids;
- for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
- const aiAnimation* anim = mScene->mAnimations[ai];
- const int64_t layer_uid = animation_layer_uids[ai];
- std::vector<std::array<int64_t,3>> nodeanim_uids;
- for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
- const aiNodeAnim* na = anim->mChannels[nai];
- // get the corresponding aiNode
- const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
- // and its transform
- const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
- aiVector3D T, R, S;
- node_xfm.Decompose(S, R, T);
- // AnimationCurveNode uids
- std::array<int64_t,3> ids;
- ids[0] = generate_uid(); // T
- ids[1] = generate_uid(); // R
- ids[2] = generate_uid(); // S
- // translation
- WriteAnimationCurveNode(outstream,
- ids[0], "T", T, "Lcl Translation",
- layer_uid, node_uids[node]
- );
- // rotation
- WriteAnimationCurveNode(outstream,
- ids[1], "R", R, "Lcl Rotation",
- layer_uid, node_uids[node]
- );
- // scale
- WriteAnimationCurveNode(outstream,
- ids[2], "S", S, "Lcl Scale",
- layer_uid, node_uids[node]
- );
- // store the uids for later use
- nodeanim_uids.push_back(ids);
- }
- curve_node_uids.push_back(nodeanim_uids);
- }
- // AnimCurve - defines actual keyframe data.
- // there's a separate curve for every component of every vector,
- // for example a transform curvenode will have separate X/Y/Z AnimCurve's
- for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
- const aiAnimation* anim = mScene->mAnimations[ai];
- for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
- const aiNodeAnim* na = anim->mChannels[nai];
- // get the corresponding aiNode
- const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
- // and its transform
- const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
- aiVector3D T, R, S;
- node_xfm.Decompose(S, R, T);
- const std::array<int64_t,3>& ids = curve_node_uids[ai][nai];
- std::vector<int64_t> times;
- std::vector<float> xval, yval, zval;
- // position/translation
- for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) {
- const aiVectorKey& k = na->mPositionKeys[ki];
- times.push_back(to_ktime(k.mTime, anim));
- xval.push_back(k.mValue.x);
- yval.push_back(k.mValue.y);
- zval.push_back(k.mValue.z);
- }
- // one curve each for X, Y, Z
- WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X");
- WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y");
- WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z");
- // rotation
- times.clear(); xval.clear(); yval.clear(); zval.clear();
- for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) {
- const aiQuatKey& k = na->mRotationKeys[ki];
- times.push_back(to_ktime(k.mTime, anim));
- // TODO: aiQuaternion method to convert to Euler...
- aiMatrix4x4 m(k.mValue.GetMatrix());
- aiVector3D qs, qr, qt;
- m.Decompose(qs, qr, qt);
- qr = AI_RAD_TO_DEG(qr);
- xval.push_back(qr.x);
- yval.push_back(qr.y);
- zval.push_back(qr.z);
- }
- WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X");
- WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y");
- WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z");
- // scaling/scale
- times.clear(); xval.clear(); yval.clear(); zval.clear();
- for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) {
- const aiVectorKey& k = na->mScalingKeys[ki];
- times.push_back(to_ktime(k.mTime, anim));
- xval.push_back(k.mValue.x);
- yval.push_back(k.mValue.y);
- zval.push_back(k.mValue.z);
- }
- WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X");
- WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y");
- WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z");
- }
- }
- indent = 0;
- object_node.End(outstream, binary, indent, true);
- }
- // convenience map of magic node name strings to FBX properties,
- // including the expected type of transform.
- const std::map<std::string,std::pair<std::string,char>> transform_types = {
- {"Translation", {"Lcl Translation", 't'}},
- {"RotationOffset", {"RotationOffset", 't'}},
- {"RotationPivot", {"RotationPivot", 't'}},
- {"PreRotation", {"PreRotation", 'r'}},
- {"Rotation", {"Lcl Rotation", 'r'}},
- {"PostRotation", {"PostRotation", 'r'}},
- {"RotationPivotInverse", {"RotationPivotInverse", 'i'}},
- {"ScalingOffset", {"ScalingOffset", 't'}},
- {"ScalingPivot", {"ScalingPivot", 't'}},
- {"Scaling", {"Lcl Scaling", 's'}},
- {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}},
- {"GeometricScaling", {"GeometricScaling", 's'}},
- {"GeometricRotation", {"GeometricRotation", 'r'}},
- {"GeometricTranslation", {"GeometricTranslation", 't'}},
- {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}},
- {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}},
- {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
- };
- //add metadata to fbx property
- void add_meta(FBX::Node& fbx_node, const aiNode* node){
- if(node->mMetaData == nullptr) return;
- aiMetadata* meta = node->mMetaData;
- for (unsigned int i = 0; i < meta->mNumProperties; ++i) {
- aiString key = meta->mKeys[i];
- aiMetadataEntry* entry = &meta->mValues[i];
- switch (entry->mType) {
- case AI_BOOL:{
- bool val = *static_cast<bool *>(entry->mData);
- fbx_node.AddP70bool(key.C_Str(), val);
- break;
- }
- case AI_INT32:{
- int32_t val = *static_cast<int32_t *>(entry->mData);
- fbx_node.AddP70int(key.C_Str(), val);
- break;
- }
- case AI_UINT64:{
- //use string to add uint64
- uint64_t val = *static_cast<uint64_t *>(entry->mData);
- fbx_node.AddP70string(key.C_Str(), std::to_string(val).c_str());
- break;
- }
- case AI_FLOAT:{
- float val = *static_cast<float *>(entry->mData);
- fbx_node.AddP70double(key.C_Str(), val);
- break;
- }
- case AI_DOUBLE:{
- double val = *static_cast<double *>(entry->mData);
- fbx_node.AddP70double(key.C_Str(), val);
- break;
- }
- case AI_AISTRING:{
- aiString val = *static_cast<aiString *>(entry->mData);
- fbx_node.AddP70string(key.C_Str(), val.C_Str());
- break;
- }
- case AI_AIMETADATA: {
- //ignore
- break;
- }
- default:
- break;
- }
-
- }
-
- }
- // write a single model node to the stream
- void FBXExporter::WriteModelNode(
- StreamWriterLE& outstream,
- bool,
- const aiNode* node,
- int64_t node_uid,
- const std::string& type,
- const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
- TransformInheritance inherit_type
- ){
- const aiVector3D zero = {0, 0, 0};
- const aiVector3D one = {1, 1, 1};
- FBX::Node m("Model");
- std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model";
- m.AddProperties(node_uid, std::move(name), type);
- m.AddChild("Version", int32_t(232));
- FBX::Node p("Properties70");
- p.AddP70bool("RotationActive", true);
- p.AddP70int("DefaultAttributeIndex", 0);
- p.AddP70enum("InheritType", inherit_type);
- if (transform_chain.empty()) {
- // decompose 4x4 transform matrix into TRS
- aiVector3D t, r, s;
- node->mTransformation.Decompose(s, r, t);
- if (t != zero) {
- p.AddP70(
- "Lcl Translation", "Lcl Translation", "", "A",
- double(t.x), double(t.y), double(t.z)
- );
- }
- if (r != zero) {
- r = AI_RAD_TO_DEG(r);
- p.AddP70(
- "Lcl Rotation", "Lcl Rotation", "", "A",
- double(r.x), double(r.y), double(r.z)
- );
- }
- if (s != one) {
- p.AddP70(
- "Lcl Scaling", "Lcl Scaling", "", "A",
- double(s.x), double(s.y), double(s.z)
- );
- }
- } else {
- // apply the transformation chain.
- // these transformation elements are created when importing FBX,
- // which has a complex transformation hierarchy for each node.
- // as such we can bake the hierarchy back into the node on export.
- for (auto &item : transform_chain) {
- auto elem = transform_types.find(item.first);
- if (elem == transform_types.end()) {
- // then this is a bug
- std::stringstream err;
- err << "unrecognized FBX transformation type: ";
- err << item.first;
- throw DeadlyExportError(err.str());
- }
- const std::string &cur_name = elem->second.first;
- const aiVector3D &v = item.second;
- if (cur_name.compare(0, 4, "Lcl ") == 0) {
- // special handling for animatable properties
- p.AddP70( cur_name, cur_name, "", "A", double(v.x), double(v.y), double(v.z) );
- } else {
- p.AddP70vector(cur_name, v.x, v.y, v.z);
- }
- }
- }
- add_meta(p, node);
- m.AddChild(p);
- // not sure what these are for,
- // but they seem to be omnipresent
- m.AddChild("Shading", FBXExportProperty(true));
- m.AddChild("Culling", FBXExportProperty("CullingOff"));
- m.Dump(outstream, binary, 1);
- }
- // wrapper for WriteModelNodes to create and pass a blank transform chain
- void FBXExporter::WriteModelNodes(
- StreamWriterLE& s,
- const aiNode* node,
- int64_t parent_uid,
- const std::unordered_set<const aiNode*>& limbnodes
- ) {
- std::vector<std::pair<std::string,aiVector3D>> chain;
- WriteModelNodes(s, node, parent_uid, limbnodes, chain);
- }
- void FBXExporter::WriteModelNodes(
- StreamWriterLE& outstream,
- const aiNode* node,
- int64_t parent_uid,
- const std::unordered_set<const aiNode*>& limbnodes,
- std::vector<std::pair<std::string,aiVector3D>>& transform_chain
- ) {
- // first collapse any expanded transformation chains created by FBX import.
- std::string node_name(node->mName.C_Str());
- if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
- auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
- std::string type_name = node_name.substr(pos);
- auto elem = transform_types.find(type_name);
- if (elem == transform_types.end()) {
- // then this is a bug and should be fixed
- std::stringstream err;
- err << "unrecognized FBX transformation node";
- err << " of type " << type_name << " in node " << node_name;
- throw DeadlyExportError(err.str());
- }
- aiVector3D t, r, s;
- node->mTransformation.Decompose(s, r, t);
- switch (elem->second.second) {
- case 'i': // inverse
- // we don't need to worry about the inverse matrices
- break;
- case 't': // translation
- transform_chain.emplace_back(elem->first, t);
- break;
- case 'r': // rotation
- transform_chain.emplace_back(elem->first, AI_RAD_TO_DEG(r));
- break;
- case 's': // scale
- transform_chain.emplace_back(elem->first, s);
- break;
- default:
- // this should never happen
- std::stringstream err;
- err << "unrecognized FBX transformation type code: ";
- err << elem->second.second;
- throw DeadlyExportError(err.str());
- }
- // now continue on to any child nodes
- for (unsigned i = 0; i < node->mNumChildren; ++i) {
- WriteModelNodes(
- outstream,
- node->mChildren[i],
- parent_uid,
- limbnodes,
- transform_chain
- );
- }
- return;
- }
- int64_t node_uid = 0;
- // generate uid and connect to parent, if not the root node,
- if (node != mScene->mRootNode) {
- auto elem = node_uids.find(node);
- if (elem != node_uids.end()) {
- node_uid = elem->second;
- } else {
- node_uid = generate_uid();
- node_uids[node] = node_uid;
- }
- connections.emplace_back("C", "OO", node_uid, parent_uid);
- }
- // what type of node is this?
- if (node == mScene->mRootNode) {
- // handled later
- } else if (node->mNumMeshes == 1) {
- // connect to child mesh, which should have been written previously
- // TODO double check this line
- connections.emplace_back("C", "OO", mesh_uids[node], node_uid);
- // also connect to the material for the child mesh
- connections.emplace_back(
- "C", "OO",
- material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
- node_uid
- );
- // write model node
- WriteModelNode(
- outstream, binary, node, node_uid, "Mesh", transform_chain
- );
- } else if (limbnodes.count(node)) {
- WriteModelNode(
- outstream, binary, node, node_uid, "LimbNode", transform_chain
- );
- // we also need to write a nodeattribute to mark it as a skeleton
- int64_t node_attribute_uid = generate_uid();
- FBX::Node na("NodeAttribute");
- na.AddProperties(
- node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
- );
- na.AddChild("TypeFlags", FBXExportProperty("Skeleton"));
- na.Dump(outstream, binary, 1);
- // and connect them
- connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
- } else if (node->mNumMeshes >= 1) {
- connections.emplace_back("C", "OO", mesh_uids[node], node_uid);
- for (size_t i = 0; i < node->mNumMeshes; i++) {
- connections.emplace_back(
- "C", "OO",
- material_uids[mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex],
- node_uid
- );
- }
- WriteModelNode(outstream, binary, node, node_uid, "Mesh", transform_chain);
- } else {
- const auto& lightIt = lights_uids.find(node->mName.C_Str());
- if(lightIt != lights_uids.end()) {
- // Node has a light connected to it.
- WriteModelNode(
- outstream, binary, node, node_uid, "Light", transform_chain
- );
- connections.emplace_back("C", "OO", lightIt->second, node_uid);
- } else {
- // generate a null node so we can add children to it
- WriteModelNode(
- outstream, binary, node, node_uid, "Null", transform_chain
- );
- }
- }
- if (node == mScene->mRootNode && node->mNumMeshes > 0) {
- int64_t new_node_uid = generate_uid();
- connections.emplace_back("C", "OO", new_node_uid, node_uid);
- connections.emplace_back("C", "OO", mesh_uids[node], new_node_uid);
- for (size_t i = 0; i < node->mNumMeshes; ++i) {
- connections.emplace_back(
- "C", "OO",
- material_uids[mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex],
- new_node_uid
- );
- }
- aiNode new_node;
- new_node.mName = mScene->mMeshes[0]->mName;
- WriteModelNode(outstream, binary, &new_node, new_node_uid, "Mesh", {});
- }
- // now recurse into children
- for (size_t i = 0; i < node->mNumChildren; ++i) {
- WriteModelNodes(
- outstream, node->mChildren[i], node_uid, limbnodes
- );
- }
- }
- void FBXExporter::WriteAnimationCurveNode(
- StreamWriterLE &outstream,
- int64_t uid,
- const std::string &name, // "T", "R", or "S"
- aiVector3D default_value,
- const std::string &property_name, // "Lcl Translation" etc
- int64_t layer_uid,
- int64_t node_uid) {
- FBX::Node n("AnimationCurveNode");
- n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", "");
- FBX::Node p("Properties70");
- p.AddP70numberA("d|X", default_value.x);
- p.AddP70numberA("d|Y", default_value.y);
- p.AddP70numberA("d|Z", default_value.z);
- n.AddChild(p);
- n.Dump(outstream, binary, 1);
- // connect to layer
- this->connections.emplace_back("C", "OO", uid, layer_uid);
- // connect to bone
- this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
- }
- void FBXExporter::WriteAnimationCurve(
- StreamWriterLE& outstream,
- double default_value,
- const std::vector<int64_t>& times,
- const std::vector<float>& values,
- int64_t curvenode_uid,
- const std::string& property_link // "d|X", "d|Y", etc
- ) {
- FBX::Node n("AnimationCurve");
- int64_t curve_uid = generate_uid();
- n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", "");
- n.AddChild("Default", default_value);
- n.AddChild("KeyVer", int32_t(4009));
- n.AddChild("KeyTime", times);
- n.AddChild("KeyValueFloat", values);
- // TODO: keyattr flags and data (STUB for now)
- n.AddChild("KeyAttrFlags", std::vector<int32_t>{0});
- n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
- n.AddChild(
- "KeyAttrRefCount",
- std::vector<int32_t>{static_cast<int32_t>(times.size())}
- );
- n.Dump(outstream, binary, 1);
- this->connections.emplace_back(
- "C", "OP", curve_uid, curvenode_uid, property_link
- );
- }
- void FBXExporter::WriteConnections ()
- {
- // we should have completed the connection graph already,
- // so basically just dump it here
- if (!binary) {
- WriteAsciiSectionHeader("Object connections");
- }
- // TODO: comments with names in the ascii version
- FBX::Node conn("Connections");
- StreamWriterLE outstream(outfile);
- conn.Begin(outstream, binary, 0);
- conn.BeginChildren(outstream, binary, 0);
- for (auto &n : connections) {
- n.Dump(outstream, binary, 1);
- }
- conn.End(outstream, binary, 0, !connections.empty());
- connections.clear();
- }
- #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
- #endif // ASSIMP_BUILD_NO_EXPORT
|