vrshoot
diff libs/assimp/FBXMeshGeometry.cpp @ 0:b2f14e535253
initial commit
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Sat, 01 Feb 2014 19:58:19 +0200 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/libs/assimp/FBXMeshGeometry.cpp Sat Feb 01 19:58:19 2014 +0200 1.3 @@ -0,0 +1,540 @@ 1.4 +/* 1.5 +Open Asset Import Library (assimp) 1.6 +---------------------------------------------------------------------- 1.7 + 1.8 +Copyright (c) 2006-2012, assimp team 1.9 +All rights reserved. 1.10 + 1.11 +Redistribution and use of this software in source and binary forms, 1.12 +with or without modification, are permitted provided that the 1.13 +following conditions are met: 1.14 + 1.15 +* Redistributions of source code must retain the above 1.16 + copyright notice, this list of conditions and the 1.17 + following disclaimer. 1.18 + 1.19 +* Redistributions in binary form must reproduce the above 1.20 + copyright notice, this list of conditions and the 1.21 + following disclaimer in the documentation and/or other 1.22 + materials provided with the distribution. 1.23 + 1.24 +* Neither the name of the assimp team, nor the names of its 1.25 + contributors may be used to endorse or promote products 1.26 + derived from this software without specific prior 1.27 + written permission of the assimp team. 1.28 + 1.29 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.30 +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.31 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.32 +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1.33 +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1.34 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1.35 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 1.36 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 1.37 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1.38 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 1.39 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.40 + 1.41 +---------------------------------------------------------------------- 1.42 +*/ 1.43 + 1.44 +/** @file FBXMeshGeometry.cpp 1.45 + * @brief Assimp::FBX::MeshGeometry implementation 1.46 + */ 1.47 +#include "AssimpPCH.h" 1.48 + 1.49 +#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER 1.50 + 1.51 +#include <functional> 1.52 + 1.53 +#include "FBXParser.h" 1.54 +#include "FBXDocument.h" 1.55 +#include "FBXImporter.h" 1.56 +#include "FBXImportSettings.h" 1.57 +#include "FBXDocumentUtil.h" 1.58 + 1.59 + 1.60 +namespace Assimp { 1.61 +namespace FBX { 1.62 + 1.63 + using namespace Util; 1.64 + 1.65 + 1.66 +// ------------------------------------------------------------------------------------------------ 1.67 +Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) 1.68 + : Object(id, element,name) 1.69 + , skin() 1.70 +{ 1.71 + const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); 1.72 + BOOST_FOREACH(const Connection* con, conns) { 1.73 + const Skin* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element); 1.74 + if(sk) { 1.75 + skin = sk; 1.76 + break; 1.77 + } 1.78 + } 1.79 +} 1.80 + 1.81 + 1.82 +// ------------------------------------------------------------------------------------------------ 1.83 +Geometry::~Geometry() 1.84 +{ 1.85 + 1.86 +} 1.87 + 1.88 + 1.89 + 1.90 +// ------------------------------------------------------------------------------------------------ 1.91 +MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) 1.92 +: Geometry(id, element,name, doc) 1.93 +{ 1.94 + const Scope* sc = element.Compound(); 1.95 + if (!sc) { 1.96 + DOMError("failed to read Geometry object (class: Mesh), no data scope found"); 1.97 + } 1.98 + 1.99 + // must have Mesh elements: 1.100 + const Element& Vertices = GetRequiredElement(*sc,"Vertices",&element); 1.101 + const Element& PolygonVertexIndex = GetRequiredElement(*sc,"PolygonVertexIndex",&element); 1.102 + 1.103 + // optional Mesh elements: 1.104 + const ElementCollection& Layer = sc->GetCollection("Layer"); 1.105 + 1.106 + std::vector<aiVector3D> tempVerts; 1.107 + ParseVectorDataArray(tempVerts,Vertices); 1.108 + 1.109 + if(tempVerts.empty()) { 1.110 + FBXImporter::LogWarn("encountered mesh with no vertices"); 1.111 + return; 1.112 + } 1.113 + 1.114 + std::vector<int> tempFaces; 1.115 + ParseVectorDataArray(tempFaces,PolygonVertexIndex); 1.116 + 1.117 + if(tempFaces.empty()) { 1.118 + FBXImporter::LogWarn("encountered mesh with no faces"); 1.119 + return; 1.120 + } 1.121 + 1.122 + vertices.reserve(tempFaces.size()); 1.123 + faces.reserve(tempFaces.size() / 3); 1.124 + 1.125 + mapping_offsets.resize(tempVerts.size()); 1.126 + mapping_counts.resize(tempVerts.size(),0); 1.127 + mappings.resize(tempFaces.size()); 1.128 + 1.129 + const size_t vertex_count = tempVerts.size(); 1.130 + 1.131 + // generate output vertices, computing an adjacency table to 1.132 + // preserve the mapping from fbx indices to *this* indexing. 1.133 + unsigned int count = 0; 1.134 + BOOST_FOREACH(int index, tempFaces) { 1.135 + const int absi = index < 0 ? (-index - 1) : index; 1.136 + if(static_cast<size_t>(absi) >= vertex_count) { 1.137 + DOMError("polygon vertex index out of range",&PolygonVertexIndex); 1.138 + } 1.139 + 1.140 + vertices.push_back(tempVerts[absi]); 1.141 + ++count; 1.142 + 1.143 + ++mapping_counts[absi]; 1.144 + 1.145 + if (index < 0) { 1.146 + faces.push_back(count); 1.147 + count = 0; 1.148 + } 1.149 + } 1.150 + 1.151 + unsigned int cursor = 0; 1.152 + for (size_t i = 0, e = tempVerts.size(); i < e; ++i) { 1.153 + mapping_offsets[i] = cursor; 1.154 + cursor += mapping_counts[i]; 1.155 + 1.156 + mapping_counts[i] = 0; 1.157 + } 1.158 + 1.159 + cursor = 0; 1.160 + BOOST_FOREACH(int index, tempFaces) { 1.161 + const int absi = index < 0 ? (-index - 1) : index; 1.162 + mappings[mapping_offsets[absi] + mapping_counts[absi]++] = cursor++; 1.163 + } 1.164 + 1.165 + // if settings.readAllLayers is true: 1.166 + // * read all layers, try to load as many vertex channels as possible 1.167 + // if settings.readAllLayers is false: 1.168 + // * read only the layer with index 0, but warn about any further layers 1.169 + for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) { 1.170 + const TokenList& tokens = (*it).second->Tokens(); 1.171 + 1.172 + const char* err; 1.173 + const int index = ParseTokenAsInt(*tokens[0], err); 1.174 + if(err) { 1.175 + DOMError(err,&element); 1.176 + } 1.177 + 1.178 + if(doc.Settings().readAllLayers || index == 0) { 1.179 + const Scope& layer = GetRequiredScope(*(*it).second); 1.180 + ReadLayer(layer); 1.181 + } 1.182 + else { 1.183 + FBXImporter::LogWarn("ignoring additional geometry layers"); 1.184 + } 1.185 + } 1.186 +} 1.187 + 1.188 + 1.189 +// ------------------------------------------------------------------------------------------------ 1.190 +MeshGeometry::~MeshGeometry() 1.191 +{ 1.192 + 1.193 +} 1.194 + 1.195 + 1.196 + 1.197 +// ------------------------------------------------------------------------------------------------ 1.198 +void MeshGeometry::ReadLayer(const Scope& layer) 1.199 +{ 1.200 + const ElementCollection& LayerElement = layer.GetCollection("LayerElement"); 1.201 + for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) { 1.202 + const Scope& elayer = GetRequiredScope(*(*eit).second); 1.203 + 1.204 + ReadLayerElement(elayer); 1.205 + } 1.206 +} 1.207 + 1.208 + 1.209 +// ------------------------------------------------------------------------------------------------ 1.210 +void MeshGeometry::ReadLayerElement(const Scope& layerElement) 1.211 +{ 1.212 + const Element& Type = GetRequiredElement(layerElement,"Type"); 1.213 + const Element& TypedIndex = GetRequiredElement(layerElement,"TypedIndex"); 1.214 + 1.215 + const std::string& type = ParseTokenAsString(GetRequiredToken(Type,0)); 1.216 + const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex,0)); 1.217 + 1.218 + const Scope& top = GetRequiredScope(element); 1.219 + const ElementCollection candidates = top.GetCollection(type); 1.220 + 1.221 + for (ElementMap::const_iterator it = candidates.first; it != candidates.second; ++it) { 1.222 + const int index = ParseTokenAsInt(GetRequiredToken(*(*it).second,0)); 1.223 + if(index == typedIndex) { 1.224 + ReadVertexData(type,typedIndex,GetRequiredScope(*(*it).second)); 1.225 + return; 1.226 + } 1.227 + } 1.228 + 1.229 + FBXImporter::LogError(Formatter::format("failed to resolve vertex layer element: ") 1.230 + << type << ", index: " << typedIndex); 1.231 +} 1.232 + 1.233 + 1.234 +// ------------------------------------------------------------------------------------------------ 1.235 +void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scope& source) 1.236 +{ 1.237 + const std::string& MappingInformationType = ParseTokenAsString(GetRequiredToken( 1.238 + GetRequiredElement(source,"MappingInformationType"),0) 1.239 + ); 1.240 + 1.241 + const std::string& ReferenceInformationType = ParseTokenAsString(GetRequiredToken( 1.242 + GetRequiredElement(source,"ReferenceInformationType"),0) 1.243 + ); 1.244 + 1.245 + if (type == "LayerElementUV") { 1.246 + if(index >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { 1.247 + FBXImporter::LogError(Formatter::format("ignoring UV layer, maximum number of UV channels exceeded: ") 1.248 + << index << " (limit is " << AI_MAX_NUMBER_OF_TEXTURECOORDS << ")" ); 1.249 + return; 1.250 + } 1.251 + 1.252 + const Element* Name = source["Name"]; 1.253 + uvNames[index] = ""; 1.254 + if(Name) { 1.255 + uvNames[index] = ParseTokenAsString(GetRequiredToken(*Name,0)); 1.256 + } 1.257 + 1.258 + ReadVertexDataUV(uvs[index],source, 1.259 + MappingInformationType, 1.260 + ReferenceInformationType 1.261 + ); 1.262 + } 1.263 + else if (type == "LayerElementMaterial") { 1.264 + if (materials.size() > 0) { 1.265 + FBXImporter::LogError("ignoring additional material layer"); 1.266 + return; 1.267 + } 1.268 + 1.269 + std::vector<int> temp_materials; 1.270 + 1.271 + ReadVertexDataMaterials(temp_materials,source, 1.272 + MappingInformationType, 1.273 + ReferenceInformationType 1.274 + ); 1.275 + 1.276 + // sometimes, there will be only negative entries. Drop the material 1.277 + // layer in such a case (I guess it means a default material should 1.278 + // be used). This is what the converter would do anyway, and it 1.279 + // avoids loosing the material if there are more material layers 1.280 + // coming of which at least one contains actual data (did observe 1.281 + // that with one test file). 1.282 + const size_t count_neg = std::count_if(temp_materials.begin(),temp_materials.end(),std::bind2nd(std::less<int>(),0)); 1.283 + if(count_neg == temp_materials.size()) { 1.284 + FBXImporter::LogWarn("ignoring dummy material layer (all entries -1)"); 1.285 + return; 1.286 + } 1.287 + 1.288 + std::swap(temp_materials, materials); 1.289 + } 1.290 + else if (type == "LayerElementNormal") { 1.291 + if (normals.size() > 0) { 1.292 + FBXImporter::LogError("ignoring additional normal layer"); 1.293 + return; 1.294 + } 1.295 + 1.296 + ReadVertexDataNormals(normals,source, 1.297 + MappingInformationType, 1.298 + ReferenceInformationType 1.299 + ); 1.300 + } 1.301 + else if (type == "LayerElementTangent") { 1.302 + if (tangents.size() > 0) { 1.303 + FBXImporter::LogError("ignoring additional tangent layer"); 1.304 + return; 1.305 + } 1.306 + 1.307 + ReadVertexDataTangents(tangents,source, 1.308 + MappingInformationType, 1.309 + ReferenceInformationType 1.310 + ); 1.311 + } 1.312 + else if (type == "LayerElementBinormal") { 1.313 + if (binormals.size() > 0) { 1.314 + FBXImporter::LogError("ignoring additional binormal layer"); 1.315 + return; 1.316 + } 1.317 + 1.318 + ReadVertexDataBinormals(binormals,source, 1.319 + MappingInformationType, 1.320 + ReferenceInformationType 1.321 + ); 1.322 + } 1.323 + else if (type == "LayerElementColor") { 1.324 + if(index >= AI_MAX_NUMBER_OF_COLOR_SETS) { 1.325 + FBXImporter::LogError(Formatter::format("ignoring vertex color layer, maximum number of color sets exceeded: ") 1.326 + << index << " (limit is " << AI_MAX_NUMBER_OF_COLOR_SETS << ")" ); 1.327 + return; 1.328 + } 1.329 + 1.330 + ReadVertexDataColors(colors[index],source, 1.331 + MappingInformationType, 1.332 + ReferenceInformationType 1.333 + ); 1.334 + } 1.335 +} 1.336 + 1.337 + 1.338 +// ------------------------------------------------------------------------------------------------ 1.339 +// Lengthy utility function to read and resolve a FBX vertex data array - that is, the 1.340 +// output is in polygon vertex order. This logic is used for reading normals, UVs, colors, 1.341 +// tangents .. 1.342 +template <typename T> 1.343 +void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source, 1.344 + const std::string& MappingInformationType, 1.345 + const std::string& ReferenceInformationType, 1.346 + const char* dataElementName, 1.347 + const char* indexDataElementName, 1.348 + size_t vertex_count, 1.349 + const std::vector<unsigned int>& mapping_counts, 1.350 + const std::vector<unsigned int>& mapping_offsets, 1.351 + const std::vector<unsigned int>& mappings) 1.352 +{ 1.353 + std::vector<T> tempUV; 1.354 + ParseVectorDataArray(tempUV,GetRequiredElement(source,dataElementName)); 1.355 + 1.356 + // handle permutations of Mapping and Reference type - it would be nice to 1.357 + // deal with this more elegantly and with less redundancy, but right 1.358 + // now it seems unavoidable. 1.359 + if (MappingInformationType == "ByVertice" && ReferenceInformationType == "Direct") { 1.360 + data_out.resize(vertex_count); 1.361 + for (size_t i = 0, e = tempUV.size(); i < e; ++i) { 1.362 + 1.363 + const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; 1.364 + for (unsigned int j = istart; j < iend; ++j) { 1.365 + data_out[mappings[j]] = tempUV[i]; 1.366 + } 1.367 + } 1.368 + } 1.369 + else if (MappingInformationType == "ByVertice" && ReferenceInformationType == "IndexToDirect") { 1.370 + data_out.resize(vertex_count); 1.371 + 1.372 + std::vector<int> uvIndices; 1.373 + ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); 1.374 + 1.375 + for (size_t i = 0, e = uvIndices.size(); i < e; ++i) { 1.376 + 1.377 + const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; 1.378 + for (unsigned int j = istart; j < iend; ++j) { 1.379 + if(static_cast<size_t>(uvIndices[i]) >= tempUV.size()) { 1.380 + DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); 1.381 + } 1.382 + data_out[mappings[j]] = tempUV[uvIndices[i]]; 1.383 + } 1.384 + } 1.385 + } 1.386 + else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "Direct") { 1.387 + if (tempUV.size() != vertex_count) { 1.388 + FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ") 1.389 + << tempUV.size() << ", expected " << vertex_count 1.390 + ); 1.391 + return; 1.392 + } 1.393 + 1.394 + data_out.swap(tempUV); 1.395 + } 1.396 + else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "IndexToDirect") { 1.397 + data_out.resize(vertex_count); 1.398 + 1.399 + std::vector<int> uvIndices; 1.400 + ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); 1.401 + 1.402 + if (uvIndices.size() != vertex_count) { 1.403 + FBXImporter::LogError("length of input data unexpected for ByPolygonVertex mapping"); 1.404 + return; 1.405 + } 1.406 + 1.407 + unsigned int next = 0; 1.408 + BOOST_FOREACH(int i, uvIndices) { 1.409 + if(static_cast<size_t>(i) >= tempUV.size()) { 1.410 + DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); 1.411 + } 1.412 + 1.413 + data_out[next++] = tempUV[i]; 1.414 + } 1.415 + } 1.416 + else { 1.417 + FBXImporter::LogError(Formatter::format("ignoring vertex data channel, access type not implemented: ") 1.418 + << MappingInformationType << "," << ReferenceInformationType); 1.419 + } 1.420 +} 1.421 + 1.422 +// ------------------------------------------------------------------------------------------------ 1.423 +void MeshGeometry::ReadVertexDataNormals(std::vector<aiVector3D>& normals_out, const Scope& source, 1.424 + const std::string& MappingInformationType, 1.425 + const std::string& ReferenceInformationType) 1.426 +{ 1.427 + ResolveVertexDataArray(normals_out,source,MappingInformationType,ReferenceInformationType, 1.428 + "Normals", 1.429 + "NormalsIndex", 1.430 + vertices.size(), 1.431 + mapping_counts, 1.432 + mapping_offsets, 1.433 + mappings); 1.434 +} 1.435 + 1.436 + 1.437 +// ------------------------------------------------------------------------------------------------ 1.438 +void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope& source, 1.439 + const std::string& MappingInformationType, 1.440 + const std::string& ReferenceInformationType) 1.441 +{ 1.442 + ResolveVertexDataArray(uv_out,source,MappingInformationType,ReferenceInformationType, 1.443 + "UV", 1.444 + "UVIndex", 1.445 + vertices.size(), 1.446 + mapping_counts, 1.447 + mapping_offsets, 1.448 + mappings); 1.449 +} 1.450 + 1.451 + 1.452 +// ------------------------------------------------------------------------------------------------ 1.453 +void MeshGeometry::ReadVertexDataColors(std::vector<aiColor4D>& colors_out, const Scope& source, 1.454 + const std::string& MappingInformationType, 1.455 + const std::string& ReferenceInformationType) 1.456 +{ 1.457 + ResolveVertexDataArray(colors_out,source,MappingInformationType,ReferenceInformationType, 1.458 + "Colors", 1.459 + "ColorIndex", 1.460 + vertices.size(), 1.461 + mapping_counts, 1.462 + mapping_offsets, 1.463 + mappings); 1.464 +} 1.465 + 1.466 + 1.467 +// ------------------------------------------------------------------------------------------------ 1.468 +void MeshGeometry::ReadVertexDataTangents(std::vector<aiVector3D>& tangents_out, const Scope& source, 1.469 + const std::string& MappingInformationType, 1.470 + const std::string& ReferenceInformationType) 1.471 +{ 1.472 + ResolveVertexDataArray(tangents_out,source,MappingInformationType,ReferenceInformationType, 1.473 + "Tangent", 1.474 + "TangentIndex", 1.475 + vertices.size(), 1.476 + mapping_counts, 1.477 + mapping_offsets, 1.478 + mappings); 1.479 +} 1.480 + 1.481 + 1.482 +// ------------------------------------------------------------------------------------------------ 1.483 +void MeshGeometry::ReadVertexDataBinormals(std::vector<aiVector3D>& binormals_out, const Scope& source, 1.484 + const std::string& MappingInformationType, 1.485 + const std::string& ReferenceInformationType) 1.486 +{ 1.487 + ResolveVertexDataArray(binormals_out,source,MappingInformationType,ReferenceInformationType, 1.488 + "Binormal", 1.489 + "BinormalIndex", 1.490 + vertices.size(), 1.491 + mapping_counts, 1.492 + mapping_offsets, 1.493 + mappings); 1.494 +} 1.495 + 1.496 + 1.497 +// ------------------------------------------------------------------------------------------------ 1.498 +void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, const Scope& source, 1.499 + const std::string& MappingInformationType, 1.500 + const std::string& ReferenceInformationType) 1.501 +{ 1.502 + const size_t face_count = faces.size(); 1.503 + ai_assert(face_count); 1.504 + 1.505 + // materials are handled separately. First of all, they are assigned per-face 1.506 + // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect 1.507 + // has a slightly different meaning for materials. 1.508 + ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials")); 1.509 + 1.510 + if (MappingInformationType == "AllSame") { 1.511 + // easy - same material for all faces 1.512 + if (materials_out.empty()) { 1.513 + FBXImporter::LogError(Formatter::format("expected material index, ignoring")); 1.514 + return; 1.515 + } 1.516 + else if (materials_out.size() > 1) { 1.517 + FBXImporter::LogWarn(Formatter::format("expected only a single material index, ignoring all except the first one")); 1.518 + materials_out.clear(); 1.519 + } 1.520 + 1.521 + materials.assign(vertices.size(),materials_out[0]); 1.522 + } 1.523 + else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") { 1.524 + materials.resize(face_count); 1.525 + 1.526 + if(materials_out.size() != face_count) { 1.527 + FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ") 1.528 + << materials_out.size() << ", expected " << face_count 1.529 + ); 1.530 + return; 1.531 + } 1.532 + } 1.533 + else { 1.534 + FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ") 1.535 + << MappingInformationType << "," << ReferenceInformationType); 1.536 + } 1.537 +} 1.538 + 1.539 +} // !FBX 1.540 +} // !Assimp 1.541 + 1.542 +#endif 1.543 +