/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "core/geometry.h" #include "core/embedded_resource_handler_interface.h" #include "timing/benchmark.h" #include "metafile/nml.h" #include "math/matrix4.h" #include "ascii/parser.h" #include "memory/endian.h" #include "log/log.h" using namespace GS::Core; using namespace GS::AsciiParser; //------------------------------------------------------------------------------ void Geometry::ParseVertex(const Tag *pt) { NMLTagForeach(vt, *pt) { if (vt->name == "Count") vtx.Allocate(vt->GetInteger()); else if (vt->name == "Data") { float *fbff = (float *)vt->GetValue().GetBinaryBuffer(); if (vtx) for (uint n = 0; n < vtx.GetCount(); ++n) { for (int m = 0; m < 3; ++m) Endian::ToHost(&fbff[n * 3 + m], 4, Endian::Intel); vtx[n].Set(fbff[n * 3 + 0], fbff[n * 3 + 1], fbff[n * 3 + 2]); } else __LOG_E__ << "Incomplete vertex chunk loading '" << name << "', expected vertex count integer tag.\n"; } } } void Geometry::ParseAsciiVertex(const Tag *pt) { NMLTagForeach(vt, *pt) { if (vt->name == "Count") vtx.Allocate(vt->GetInteger()); else if (vt->name == "Data") { if (vtx) { uint cvtx = 0; NMLTagForeach(vx, *vt) { if (cvtx == vtx.GetCount()) { __LOG_E__ << "Corrupter tag, more vertices than expected.\n"; break; } vtx[cvtx++].FromMetaTag(*vx); } } else __LOG_E__ << "Incomplete vertex chunk loading '" << name << "', expected vertex count integer tag.\n"; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParseSkin(const Tag *pt) { // Retrieve bone id list. Tag *bonetag = pt->GetTag("Bones"), *bindtag = pt->GetTag("Binds"); skin.Free(); bone_name.Free(); bone_bind_matrix.Free(); if (bonetag) { AllocateBone(bonetag->GetChildCount()); if (!bone_name) __LOG_E__ << "Failed to allocate bone id list.\n"; else { int n = 0; NMLTagForeach(b, *bonetag) bone_name[n++] = b->GetString(); } if (bindtag && (bindtag->GetChildCount() == bone_name.GetCount())) { int n = 0; NMLTagForeach(m, *bindtag) bone_bind_matrix[n++].FromMetaTag(*m); } } // Retrieve skin weights. Tag *weighttag = pt->GetTag("Weights"); if (bone_name.GetCount() && weighttag) { if (weighttag->GetChildCount() != vtx.GetCount()) __LOG_W__ << "Incoherent weight count (" << weighttag->GetChildCount() <<" for " << vtx.GetCount() << " vertice (is the skin being declared before the vertice?)).\n"; if (!skin.Allocate(weighttag->GetChildCount())) __LOG_E__ << "Failed to allocate skin weights array.\n"; else { int n = 0; NMLTagForeach(b, *weighttag) { const char *p = b->GetString(), *e = p + String::strlen(p); skin[n].bone_index[0] = (ushort)String::atoi(p); p = NextEntry(p, e) + 1; skin[n].bone_index[1] = (ushort)String::atoi(p); p = NextEntry(p, e) + 1; skin[n].bone_index[2] = (ushort)String::atoi(p); p = NextEntry(p, e) + 1; skin[n].bone_index[3] = (ushort)String::atoi(p); p = NextEntry(p, e) + 1; skin[n].w[0] = ((float)String::atoi(p)) / 255.f; p = NextEntry(p, e) + 1; skin[n].w[1] = ((float)String::atoi(p)) / 255.f; p = NextEntry(p, e) + 1; skin[n].w[2] = ((float)String::atoi(p)) / 255.f; p = NextEntry(p, e) + 1; skin[n].w[3] = ((float)String::atoi(p)) / 255.f; // Re-normalize weights. float tw = 0.f; for (int w = 0; w < 4; ++w) tw += skin[n].w[w]; float k = 1.f / tw; for (int w = 0; w < 4; ++w) skin[n].w[w] *= k; ++n; } } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParsePolygon(const Tag *pt) { NMLTagForeach(vt, *pt) { if (vt->name == "Count") pol.Allocate(vt->GetInteger()); else if (vt->name == "BindingCount") binding.Allocate(vt->GetInteger()); else if (vt->name == "Data") { if (pol && binding) { uint *pbuffer = (uint *)vt->GetValue().GetBinaryBuffer(); uint *pbinding = binding; for (uint m = 0; m < pol.GetCount(); m++) { Endian::ToHost(pbuffer, 4, Endian::Intel); pol[m].vtx_count = (ushort)*pbuffer++; pol[m].binding = pbinding; for (uint j = 0; j < pol[m].vtx_count; j++) { Endian::ToHost(pbuffer, 4, Endian::Intel); *pbinding++ = *pbuffer++; } Endian::ToHost(pbuffer, 4, Endian::Intel); pol[m].material = (ushort)*pbuffer++; } } else __LOG_E__ << "Could not allocate polygon/binding memory.\n"; } } } void Geometry::ParseAsciiPolygon(const Tag *pt) { NMLTagForeach(vt, *pt) { if (vt->name == "Count") pol.Allocate(vt->GetInteger()); else if (vt->name == "BindingCount") binding.Allocate(vt->GetInteger()); else if (vt->name == "Data") { if (binding && pol) { static String _PolyIndex("Index"); uint *pbinding = binding; uint cpoly = 0; NMLTagForeach(poly, *vt) { if (cpoly == pol.GetCount()) { __LOG_E__ << "Corrupted tag, too many polygons.\n"; break; } Tag *count_tag = poly->GetTag("Count;"), *material_tag = poly->GetTag("Material;"); pol[cpoly].vtx_count = 0; pol[cpoly].binding = pbinding; pol[cpoly].material = 0; if (count_tag && material_tag) { pol[cpoly].vtx_count = (ushort)count_tag->GetInteger(); pol[cpoly].material = (ushort)material_tag->GetInteger(); NMLTagForeach(tag, *poly) if (tag->name == _PolyIndex) *pbinding++ = tag->GetInteger(); } else __LOG_E__ << "Corrupt tag in .\n"; ++cpoly; } } else __LOG_E__ << "Could not allocate polygon/binding memory.\n"; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParsePolygonNormal(const Tag *pt) { NMLTagForeach(vt, *pt) { if (vt->name == "Count") pol_normal.Allocate(vt->GetInteger()); else if (vt->name == "Data") { if (pol_normal) { char *pbuffer = (char *)vt->GetValue().GetBinaryBuffer(); for (uint m = 0; m < pol.GetCount(); m++) { pol_normal[m].x = (float)(pbuffer[0]) / 127.f; pol_normal[m].y = (float)(pbuffer[1]) / 127.f; pol_normal[m].z = (float)(pbuffer[2]) / 127.f; pol_normal[m].Normalize(); pbuffer += 3; } } else __LOG_E__ << "Incomplete polygon normal chunk loading '" << name << "'.\n"; } } } void Geometry::ParseVertexNormal(const Tag *pt) { Tag *vt = pt->GetTag("Count;"); if (!vt) __ERRRAW__(__LOG_E__ << "No count tag in .\n") vtx_normal.Allocate(vt->GetInteger()); vt = pt->GetTag("Data;"); if (!vt) __ERRRAW__(__LOG_E__ << "No data tag in .\n") if (vtx_normal) { signed char *pbuffer = (signed char *)vt->GetValue().GetBinaryBuffer(); for (uint m = 0; m < vtx_normal.GetCount(); ++m) { vtx_normal[m].x = ((float)pbuffer[0]) / 127.f; vtx_normal[m].y = ((float)pbuffer[1]) / 127.f; vtx_normal[m].z = ((float)pbuffer[2]) / 127.f; vtx_normal[m].Normalize(); pbuffer += 3; } } else __LOG_E__ << "Incomplete vertex normal chunk loading '" << name << "'.\n"; } void Geometry::ParseVertexTangent(const Tag *pt) { Tag *vt = pt->GetTag("Count;"); if (!vt) __ERRRAW__(__LOG_E__ << "No count tag in .\n") vtx_tangent.Allocate(vt->GetInteger()); vt = pt->GetTag("Data;"); if (!vt) __ERRRAW__(__LOG_E__ << "No data tag in .\n") // Allocate vertex normals. if (vtx_tangent) { signed char *pbuffer = (signed char *)vt->GetValue().GetBinaryBuffer(); for (uint m = 0; m < vtx_tangent.GetCount(); ++m) { Vector4 &T = vtx_tangent[m].T, &B = vtx_tangent[m].B; T.x = ((float)pbuffer[0]) / 127.f; T.y = ((float)pbuffer[1]) / 127.f; T.z = ((float)pbuffer[2]) / 127.f; T.Normalize(); B.x = ((float)pbuffer[3]) / 127.f; B.y = ((float)pbuffer[4]) / 127.f; B.z = ((float)pbuffer[5]) / 127.f; B.Normalize(); pbuffer += 6; } } else __LOG_E__ << "Incomplete vertex tangent chunk loading '" << name << "'.\n"; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParseAsciiRGB(const Tag *pt) { Tag *vt = pt->GetTag("Count;"); if (!vt) __ERRRAW__(__LOG_E__ << "No count tag in .\n") rgb.Allocate(vt->GetInteger()); vt = pt->GetTag("Data;"); if (!vt) __ERRRAW__(__LOG_E__ << "No data tag in .\n") if (rgb) { uint rgb_count = 0; NMLTagForeach(rt, *vt) { if (rgb_count == rgb.GetCount()) { __LOG_E__ << "Too many tags in RGB list of geometry '" << name << "'.\n"; break; } Tag *r_tag = rt->GetTypedTag("R;", Variant::VariantFloat), *g_tag = rt->GetTypedTag("G;", Variant::VariantFloat), *b_tag = rt->GetTypedTag("B;", Variant::VariantFloat), *a_tag = rt->GetTypedTag("A;", Variant::VariantFloat); rgb[rgb_count].x = r_tag ? r_tag->GetReal() : 0; rgb[rgb_count].y = g_tag ? g_tag->GetReal() : 0; rgb[rgb_count].z = b_tag ? b_tag->GetReal() : 0; rgb[rgb_count].w = a_tag ? a_tag->GetReal() : 1; ++rgb_count; } if (rgb_count != rgb.GetCount()) __LOG_W__ << "Not enough tags in RGB list of geometry '" << name << "'.\n"; } else __LOG_E__ << "Could not allocate RGB for geometry '" << name << "'.\n"; } void Geometry::ParseRGB(const Tag *pt) { if (Tag *vt = pt->GetTag("Data")) { float *pbuffer = (float *)vt->GetValue().GetBinaryBuffer(); size_t size = vt->GetValue().GetBinarySize(); if (rgb.Allocate(binding.GetCount())) { uint components = (size == binding.GetCount() * 4) ? 4 : 3; for (uint m = 0; m < rgb.GetCount(); m++) { for (uint n = 0; n < components; ++n) Endian::ToHost(&pbuffer[n], 4, Endian::Intel); rgb[m].Set(pbuffer[0], pbuffer[1], pbuffer[2]); rgb[m].w = components == 4 ? pbuffer[3] : 1.f; pbuffer += components; } } else __LOG_E__ << "Could not allocate RGB channel for geometry '" << name << "'.\n"; } else __LOG_E__ << "Expected tag under tag.\n"; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParseUV(const Tag *pt) { uint current_uv = 0; NMLTagForeach(vt, *pt) { if (vt->name != "Data") continue; if (current_uv == __UV_PER_GEOMETRY__) { __LOG_W__ << "Geometry '" << name << "' requires more UV channels than this build can handle.\n"; __LOG_W__ << "Please increase nUVMapCount and recompile to fully import this object.\n"; break; } float *pbuffer = (float *)vt->GetValue().GetBinaryBuffer(); if (uv[current_uv].Allocate(binding.GetCount())) for (uint m = 0; m < binding.GetCount(); ++m) { for (int n = 0; n < 2; ++n) Endian::ToHost(&pbuffer[n], 4, Endian::Intel); uv[current_uv][m].x = pbuffer[0]; uv[current_uv][m].y = pbuffer[1]; pbuffer += 2; } else __LOG_E__ << "could not allocate UV channel for geometry '" << name << "'.\n"; ++current_uv; } } void Geometry::ParseAsciiUV(const Tag *pt) { uint current_uv = 0; NMLTagForeach(vt, *pt) { if (vt->name != "Data") continue; if (uv[current_uv].Allocate(binding.GetCount())) { uint uv_index = 0; NMLTagForeach(uvt, *vt) { if (uv_index == binding.GetCount()) { __LOG_E__ << "Too many tags in UV channel " << current_uv << " of geometry '" << name << "'.\n"; break; } Tag *u_tag = uvt->GetTypedTag("U;", Variant::VariantFloat), *v_tag = uvt->GetTypedTag("V;", Variant::VariantFloat); uv[current_uv][uv_index].x = u_tag ? u_tag->GetReal() : 0; uv[current_uv][uv_index].y = v_tag ? v_tag->GetReal() : 0; uv_index++; } } else __LOG_E__ << "could not allocate UV channel for geometry '" << name << "'.\n"; if (++current_uv == __UV_PER_GEOMETRY__) { __LOG_W__ << "'" << name << "' requires more UV channels than this build can handle.\n"; __LOG_W__ << " Please increase nUVMapCount and recompile nEngine to fully import this object.\n"; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParseMaterials(const Tag *pt) { // First pass, material count. uint slot_count = 0; NMLTagForeach(mt, *pt) if ((mt->name == "Material") || (mt->name == "MaterialRef") || (mt->name == "MaterialRefEx")) slot_count++; // Allocate slots. if (material_table.Allocate(slot_count)) { slot_count = 0; NMLTagForeach(mt, *pt) { String mref; bool use_cache = true; if (mt->name == "Material") IEmbeddedResourceHandler::Get()->ExtractEmbeddedMaterial(mref, *mt, name, slot_count); else if (mt->name == "MaterialRef") mref = mt->GetString(); else if (mt->name == "MaterialRefEx") { if (Tag *t = mt->GetTypedTag("Name", Variant::VariantString)) mref = t->GetString(); if (Tag *t = mt->GetTypedTag("UseCache", Variant::VariantBool)) use_cache = t->GetBool(); } material_table[slot_count].name = mref; material_table[slot_count].use_cache = use_cache; ++slot_count; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Geometry::ParseMisc(const Tag &tag) { NMLTagForeach(pt, tag) { if (pt->name == "LodDistance") lod_distance = pt->GetReal(); else if (pt->name == "LodNull") flag.Raise(FlagNullLodProxy, pt->GetBool()); else if (pt->name == "ShadowNull") flag.Raise(FlagNullShadowProxy, pt->GetBool()); else if (pt->name == "CopyLock") copy_lock = pt->GetString(); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Geometry::FromMetaTag(const Tag &tag) { Benchmark bench(true); // Build binary objects base name. if (tag.name != "Geometry") __ERR__(__LOG_E__ << "Could not parse material, incorrect root tag (" << tag.name << ")...\n", false) Free(); // Parse root tags. Tag *pt; if ((pt = tag.GetTag("Materials")) != NULL) ParseMaterials(pt); if ((pt = tag.GetTag("Vertex")) != NULL) ParseVertex(pt); else if ((pt = tag.GetTag("AVertex")) != NULL) ParseAsciiVertex(pt); if ((pt = tag.GetTag("Polygon")) != NULL) ParsePolygon(pt); else if ((pt = tag.GetTag("APolygon")) != NULL) ParseAsciiPolygon(pt); if ((pt = tag.GetTag("PNormal")) != NULL) ParsePolygonNormal(pt); if ((pt = tag.GetTag("VNormal")) != NULL) ParseVertexNormal(pt); if ((pt = tag.GetTag("VTangent")) != NULL) ParseVertexTangent(pt); if ((pt = tag.GetTag("Skin")) != NULL) ParseSkin(pt); if ((pt = tag.GetTag("RGB")) != NULL) ParseRGB(pt); else if ((pt = tag.GetTag("ARGB")) != NULL) ParseAsciiRGB(pt); if ((pt = tag.GetTag("UV")) != NULL) ParseUV(pt); else if ((pt = tag.GetTag("AUV")) != NULL) ParseAsciiUV(pt); if ((pt = tag.GetTag("LodProxy")) != NULL) lod_proxy = pt->GetString(); if ((pt = tag.GetTag("ShadowProxy")) != NULL) shadow_proxy = pt->GetString(); ParseMisc(tag); // Quick integrity check. if (!vtx.GetCount()) __ERR__(__LOG_E__ << "No vertice in geometry '" << name << "'.\n", false) if (!pol.GetCount()) __ERR__(__LOG_E__ << "No polygon in geometry '" << name << "'.\n", false) if (vtx_tangent.IsNull()) ComputeVertexTangent(); bench.Stop(); __LOG__ << "Geometry::FromMetaTag(Tag &tag) done in " << bench.GetMs() << "ms. " << vtx.GetCount() << " vertice, " << pol.GetCount() << " polygon(s), " << material_table.GetCount() << " material(s).\n"; return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Tag *Geometry::AsMetaTag() const { // Integrity check. if (!vtx.GetCount() || !pol.GetCount() || !material_table.GetCount()) __ERR__(__LOG_E__ << "Cannot serialize '" << name << "', geometry is empty.\n", NULL) Tag *root = new Tag("Geometry"); if (!root) __ERR__(__LOG_E__ << "Could not create root tag to serialize geometry '" << name << "'.\n", NULL) uint n; // Vertice. if (Tag *vertex = root->AddChild("Vertex")) { vertex->AddChild("Count", (int)vtx.GetCount()); if (float *vtx_rl = new float[vtx.GetCount() * 3]) { for (n = 0; n < vtx.GetCount(); n++) { vtx_rl[n * 3 + 0] = vtx[n].x; vtx_rl[n * 3 + 1] = vtx[n].y; vtx_rl[n * 3 + 2] = vtx[n].z; } vertex->AddChild("Data", (uchar *)vtx_rl, vtx.GetCount() * 3 * sizeof(float)); _safe_delete_array(vtx_rl); } else __LOG_E__ << "Failed to allocate raw vertice buffer.\n"; } // Polygons. if (Tag *polygon = root->AddChild("Polygon")) { polygon->AddChild("Count", (int)pol.GetCount()); polygon->AddChild("BindingCount", (int)binding.GetCount()); if (uint *pol_u4 = new uint[binding.GetCount() + pol.GetCount() * 2]) { uint *ptr = pol_u4; for (n = 0; n < pol.GetCount(); n++) { *ptr++ = pol[n].vtx_count; for (uint m = 0; m < pol[n].vtx_count; m++) *ptr++ = pol[n].binding[m]; *ptr++ = (uint)pol[n].material; } polygon->AddChild("Data", (uchar *)pol_u4, (binding.GetCount() + pol.GetCount() * 2) * sizeof(uint)); _safe_delete_array(pol_u4); } else __LOG_E__ << "Failed to allocate raw polygon buffer.\n"; } // Polygon normals. if (pol_normal) if (Tag *pnormal = root->AddChild("PNormal")) { pnormal->AddChild("Count", (int)pol.GetCount()); if (char *pnrm = new char[pol.GetCount() * 3]) { char *ptr = pnrm; for (n = 0; n < pol.GetCount(); n++) { *ptr++ = (char)(pol_normal[n].x * 127.f); *ptr++ = (char)(pol_normal[n].y * 127.f); *ptr++ = (char)(pol_normal[n].z * 127.f); } pnormal->AddChild("Data", (uchar *)pnrm, pol.GetCount() * 3); _safe_delete_array(pnrm); } else __LOG_E__ << "Failed to allocate raw normal buffer.\n"; } // Vertex normals. if (vtx_normal) if (Tag *vnormal = root->AddChild("VNormal")) { vnormal->AddChild("Count", (int)binding.GetCount()); if (char *vnrm = new char[binding.GetCount() * 3]) { char *ptr = vnrm; for (n = 0; n < binding.GetCount(); n++) { *ptr++ = (char)(vtx_normal[n].x * 127.f); *ptr++ = (char)(vtx_normal[n].y * 127.f); *ptr++ = (char)(vtx_normal[n].z * 127.f); } vnormal->AddChild("Data", (uchar *)vnrm, binding.GetCount() * 3); _safe_delete_array(vnrm); } else __LOG_E__ << "Failed to allocate raw normal buffer.\n"; } // Vertex tangent frames. if (vtx_tangent) if (Tag *vtangent = root->AddChild("VTangent")) { vtangent->AddChild("Count", (int)binding.GetCount()); if (char *vfrm = new char[binding.GetCount() * 6]) { char *ptr = vfrm; for (n = 0; n < binding.GetCount(); n++) { *ptr++ = (char)(vtx_tangent[n].T.x * 127.f); *ptr++ = (char)(vtx_tangent[n].T.y * 127.f); *ptr++ = (char)(vtx_tangent[n].T.z * 127.f); *ptr++ = (char)(vtx_tangent[n].B.x * 127.f); *ptr++ = (char)(vtx_tangent[n].B.y * 127.f); *ptr++ = (char)(vtx_tangent[n].B.z * 127.f); } vtangent->AddChild("Data", (uchar *)vfrm, binding.GetCount() * 6); _safe_delete_array(vfrm); } else __LOG_E__ << "Failed to allocate raw tangent frame buffer.\n"; } // Output RGB. if (rgb) if (Tag *rgbtag = root->AddChild("RGB")) { // Test if we need to output alpha. uint num_comp = 3; for (uint m = 0; m < binding.GetCount(); ++m) if (rgb[m].w < 1.f) { num_comp = 4; break; } // Output buffer. if (float *rgbbuf = new float[binding.GetCount() * num_comp]) { float *prgb = rgbbuf; for (uint m = 0; m < binding.GetCount(); ++m) { prgb[0] = rgb[m].x; prgb[1] = rgb[m].y; prgb[2] = rgb[m].z; if (num_comp == 4) prgb[3] = rgb[m].w; prgb += num_comp; } rgbtag->AddChild("Data", (uchar *)rgbbuf, binding.GetCount() * num_comp * sizeof(float)); _safe_delete_array(rgbbuf); } } // Output UVs. if (GetUVCount()) if (Tag *uvtag = root->AddChild("UV")) { uvtag->AddChild("Count", (int)GetUVCount()); // LEGACY if (float *uvbuf = new float[binding.GetCount() * 2]) { for (n = 0; n < __UV_PER_GEOMETRY__; ++n) if (uv[n]) { for (uint m = 0; m < binding.GetCount(); m++) { uvbuf[m * 2 + 0] = uv[n][m].x; uvbuf[m * 2 + 1] = uv[n][m].y; } uvtag->AddChild("Data", (uchar *)uvbuf, binding.GetCount() * 2 * sizeof(float)); } _safe_delete_array(uvbuf); } else __LOG_E__ << "Failed to allocate raw UVs buffer.\n"; } // Output vertex weights. if (bone_name.GetCount()) { Tag *skintag = root->AddChild("Skin"); // Save bone id list. if (Tag *bonetag = skintag->AddChild("Bones")) for (uint n = 0; n < bone_name.GetCount(); ++n) bonetag->AddChild("Id", bone_name[n]); else __LOG_E__ << "Failed to allocate bone id tag.\n"; // Save bone binding matrix. if (Tag *bindtag = skintag->AddChild("Binds")) for (uint n = 0; n < bone_name.GetCount(); ++n) bindtag->AddChild(bone_bind_matrix[n].AsMetaTag("Matrix")); else __LOG_E__ << "Failed to allocate bone binding tag.\n"; // Save bone association/weights. Tag *weighttag = skintag->AddChild("Weights"); for (uint n = 0; n < vtx.GetCount(); ++n) weighttag->AddChild ( "W", String::Format ( "%d,%d,%d,%d:%d,%d,%d,%d", skin[n].bone_index[0], skin[n].bone_index[1], skin[n].bone_index[2], skin[n].bone_index[3], (int)(skin[n].w[0] * 255.f), (int)(skin[n].w[1] * 255.f), (int)(skin[n].w[2] * 255.f), (int)(skin[n].w[3] * 255.f)).toUtf8() ); } // Proxies. if (!lod_proxy.IsEmpty()) root->AddChild("LodProxy", lod_proxy.c_str()); if (lod_distance != Units::Mtr(100.f)) root->AddChild("LodDistance", lod_distance); if (flag.IsSet(FlagNullLodProxy)) root->AddChild("LodNull", true); if (!shadow_proxy.IsEmpty()) root->AddChild("ShadowProxy", shadow_proxy.c_str()); if (flag.IsSet(FlagNullShadowProxy)) root->AddChild("ShadowNull", true); // Serialize materials. if (Tag *mtag = root->AddChild("Materials")) for (n = 0; n < material_table.GetCount(); n++) if (Tag *extag = mtag->AddChild("MaterialRefEx")) { extag->AddChild("Name", material_table[n].name.c_str()); if (!material_table[n].use_cache) extag->AddChild("UseCache", material_table[n].use_cache); } // Copyright lock. if (!copy_lock.IsEmpty()) root->AddChild("CopyLock", copy_lock); return root; } //------------------------------------------------------------------------------