/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use 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 Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * 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. */ // This file contains the logic for converting the scene graph to a // JSON-encoded file that is stored in a zip archive. #include "converter_edge/cross/converter.h" #include #include #include #include "base/file_path.h" #include "base/file_util.h" #include "base/scoped_ptr.h" #include "core/cross/class_manager.h" #include "core/cross/client.h" #include "core/cross/client_info.h" #include "core/cross/effect.h" #include "core/cross/error.h" #include "core/cross/features.h" #include "core/cross/object_manager.h" #include "core/cross/pack.h" #include "core/cross/renderer.h" #include "core/cross/service_locator.h" #include "core/cross/transform.h" #include "core/cross/tree_traversal.h" #include "core/cross/types.h" #include "core/cross/primitive.cc" #include "import/cross/collada.h" #include "import/cross/collada_conditioner.h" #include "import/cross/file_output_stream_processor.h" #include "import/cross/targz_generator.h" #include "import/cross/archive_request.h" #include "serializer/cross/serializer.h" #include "utils/cross/file_path_utils.h" #include "utils/cross/json_writer.h" #include "utils/cross/string_writer.h" #include "utils/cross/temporary_file.h" namespace o3d { namespace { static const float kPi = 3.14159265358979f; static const float kEpsilon = 0.0001f; void AddBinaryElements(const Collada& collada, TarGzGenerator* archive_generator) { const ColladaDataMap& data_map(collada.original_data_map()); std::vector paths = data_map.GetOriginalDataFilenames(); for (std::vector::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) { const std::string& data = data_map.GetOriginalData(*iter); archive_generator->AddFile(FilePathToUTF8(*iter), data.size()); archive_generator->AddFileBytes( reinterpret_cast(data.c_str()), data.length()); } } } // end anonymous namespace namespace converter { // Constructor, make sure points order is unique. // If x-coordinates are the same, compare y-coordinate. // If y values are also the same, compare z. Edge::Edge(Point3 p1, Point3 p2) { bool isRightOrder = false; if (fabs(p1.getX() - p2.getX()) < kEpsilon) { if (fabs(p1.getY() - p2.getY()) < kEpsilon) { if (p1.getZ() < p2.getZ()) isRightOrder = true; } else { if (p1.getY() < p2.getY()) isRightOrder = true; } } else { if (p1.getX() < p2.getX()) isRightOrder = true; } if (isRightOrder) { pts.push_back(p1); pts.push_back(p2); } else { pts.push_back(p2); pts.push_back(p1); } } // less than operator overload, necessary function for edge-triangle map. bool operator<(const Edge& left, const Edge& right) { // compare two edges by their actually coordinates, not indices. // Beceause two different indices may point to one actually coordinates. if (dist(left.pts[0], right.pts[0]) < kEpsilon) { if (fabs(left.pts[1].getX() - right.pts[1].getX()) < kEpsilon) { if (fabs(left.pts[1].getY() - right.pts[1].getY()) < kEpsilon) { return left.pts[1].getZ() < right.pts[1].getZ(); } return left.pts[1].getY() < right.pts[1].getY(); } return left.pts[1].getX() < right.pts[1].getX(); } else { if (fabs(left.pts[0].getX() - right.pts[0].getX()) < kEpsilon) { if (fabs(left.pts[0].getY() - right.pts[0].getY()) < kEpsilon) { return left.pts[0].getZ() < right.pts[0].getZ(); } return left.pts[0].getY() < right.pts[0].getY(); } return left.pts[0].getX() < right.pts[0].getX(); } } void CheckSharpEdge(const Edge& shared_edge, const std::vector& triangle_list, std::vector* sharp_edges, float threshold) { for (size_t i = 0; i < triangle_list.size(); i++) for (size_t j = i + 1; j < triangle_list.size(); j++) { Triangle t1 = triangle_list[i]; Triangle t2 = triangle_list[j]; int same_vertices_count = 0; // Same triangle might be stored twice to represent inner and outer faces. // Check the order of indices of vertices to not mix inner and outer faces // togeter. std::vector same_vertices_pos; for (int k = 0; k < 3; k++) for (int l = 0; l < 3; l++) { if (dist(t1.pts[k], t2.pts[l]) < kEpsilon) { same_vertices_count++; same_vertices_pos.push_back(k); same_vertices_pos.push_back(l); } } if (same_vertices_count != 2) continue; // check the order of positions to make sure triangles are on // the same face. int i1 = same_vertices_pos[2] - same_vertices_pos[0]; int i2 = same_vertices_pos[3] - same_vertices_pos[1]; // if triangles are on different faces. if (!(i1 * i2 == -1 || i1 * i2 == 2 || i1 * i2 == -4)) continue; Vector3 v12 = t1.pts[1] - t1.pts[0]; Vector3 v13 = t1.pts[2] - t1.pts[0]; Vector3 n1 = cross(v12, v13); Vector3 v22 = t2.pts[1] - t2.pts[0]; Vector3 v23 = t2.pts[2] - t2.pts[0]; Vector3 n2 = cross(v22, v23); float iAngle = acos(dot(n1, n2) / (length(n1) * length(n2))); iAngle = iAngle * 180 / kPi; if (iAngle >= threshold) { sharp_edges->push_back(shared_edge); return; } } } // create a single color material and effect and attach it to // sharp edge primitive. Material* GetSingleColorMaterial(Pack::Ref pack, const Vector3& edge_color) { Material* singleColorMaterial = pack->Create(); singleColorMaterial->set_name("singleColorMaterial"); ParamString* lighting_param = singleColorMaterial->CreateParam( Collada::kLightingTypeParamName); lighting_param->set_value(Collada::kLightingTypeConstant); ParamFloat4* emissive_param = singleColorMaterial->CreateParam( Collada::kMaterialParamNameEmissive); emissive_param->set_value(Float4(edge_color.getX(), edge_color.getY(), edge_color.getZ(), 1)); return singleColorMaterial; } // insert edge-triangle pair to edge triangle map. void InsertEdgeTrianglePair(const Edge& edge, const Triangle& triangle, std::map>* et_map) { std::map>::iterator iter1 = et_map->find(edge); if (iter1 == et_map->end()) { std::vector same_edge_triangle_list; same_edge_triangle_list.push_back(triangle); et_map->insert(make_pair(edge, same_edge_triangle_list)); } else { iter1->second.push_back(triangle); } } // Loads the Collada input file and writes it to the zipped JSON output file. bool Convert(const FilePath& in_filename, const FilePath& out_filename, const Options& options, String *error_messages) { // Create a service locator and renderer. ServiceLocator service_locator; EvaluationCounter evaluation_counter(&service_locator); ClassManager class_manager(&service_locator); ClientInfoManager client_info_manager(&service_locator); ObjectManager object_manager(&service_locator); Profiler profiler(&service_locator); ErrorStatus error_status(&service_locator); Features features(&service_locator); Collada::Init(&service_locator); features.Init("MaxCapabilities"); // Collect error messages. ErrorCollector error_collector(&service_locator); scoped_ptr renderer( Renderer::CreateDefaultRenderer(&service_locator)); renderer->InitCommon(); Pack::Ref pack(object_manager.CreatePack()); Transform* root = pack->Create(); root->set_name(String(Serializer::ROOT_PREFIX) + String("root")); // Setup a ParamFloat to be the source to all animations in this file. ParamObject* param_object = pack->Create(); // This is some arbitrary name param_object->set_name(O3D_STRING_CONSTANT("animSourceOwner")); ParamFloat* param_float = param_object->CreateParam("animSource"); Collada::Options collada_options; collada_options.condition_document = options.condition; collada_options.keep_original_data = true; collada_options.base_path = options.base_path; collada_options.file_paths = options.file_paths; collada_options.up_axis = options.up_axis; Collada collada(pack.Get(), collada_options); bool result = collada.ImportFile(in_filename, root, param_float); if (!result || !error_collector.errors().empty()) { if (error_messages) { *error_messages += error_collector.errors(); } return false; } // Remove the animation param_object (and indirectly the param_float) // if there is no animation. if (param_float->output_connections().empty()) { pack->RemoveObject(param_object); } // Add edges whose normals angle is larger than predefined threshold. if (options.enable_add_sharp_edge) { std::vector shapes = pack->GetByClass(); for (unsigned s = 0; s < shapes.size(); ++s) { Shape* shape = shapes[s]; const ElementRefArray& elements = shape->GetElementRefs(); for (unsigned e = 0; e < elements.size(); ++e) { if (elements[e]->IsA(Primitive::GetApparentClass())) { Primitive* primitive = down_cast(elements[e].Get()); // Get vertices and indices of this primitive. if (primitive->primitive_type() != Primitive::TRIANGLELIST) continue; FieldReadAccessor vertices; FieldReadAccessorUnsignedInt indices; if (!GetVerticesAccessor(primitive, 0, &vertices)) return false; unsigned int index_count; if (primitive->indexed()) { if (!Primitive::GetIndexCount(Primitive::TRIANGLELIST, primitive->number_primitives(), &index_count)) continue; if (!GetIndicesAccessor(primitive, &indices, primitive->start_index(), index_count)) continue; index_count = std::min(index_count, indices.max_index()); } else { index_count = primitive->number_vertices(); indices.InitializeJustCount(primitive->start_index(), index_count); } // If there are no vertices then exit early. if (vertices.max_index() == 0) { continue; } // generate triangle list. int prim = 0; std::vector triangle_list; std::vector point_list; for (size_t i = 0; i < 48; i++) { point_list.push_back(vertices[i]); } for (unsigned int prim_base = 0; prim_base + 2 < index_count; prim_base += 3) { Triangle triangle(vertices[indices[prim_base + 0]], vertices[indices[prim_base + 1]], vertices[indices[prim_base + 2]]); triangle_list.push_back(triangle); } // build edge and triangle map. std::map> edge_triangle_map; for (size_t i = 0; i < triangle_list.size(); i++) { Triangle triangle = triangle_list[i]; Edge e1(triangle.pts[0], triangle.pts[1]); Edge e2(triangle.pts[1], triangle.pts[2]); Edge e3(triangle.pts[0], triangle.pts[2]); InsertEdgeTrianglePair(e1, triangle, &edge_triangle_map); InsertEdgeTrianglePair(e2, triangle, &edge_triangle_map); InsertEdgeTrianglePair(e3, triangle, &edge_triangle_map); } // go through the edge-triangle map. std::map>::iterator iter; std::vector sharp_edges; for (iter = edge_triangle_map.begin(); iter != edge_triangle_map.end(); iter++) { if (iter->second.size() < 2) continue; CheckSharpEdge(iter->first, iter->second, &sharp_edges, options.sharp_edge_threshold); } if (sharp_edges.size() > 0) { Primitive* sharp_edge_primitive = pack->Create(); sharp_edge_primitive->SetOwner(shape); sharp_edge_primitive->set_name("sharp_edge_primitive"); StreamBank* stream_bank = pack->Create(); VertexBuffer* vertex_buffer = pack->Create(); vertex_buffer->set_name("sharp_edges_vertex_buffer"); size_t num_vertices = sharp_edges.size() * 2; Field* field = vertex_buffer->CreateField( FloatField::GetApparentClass(), 3); if (!vertex_buffer->AllocateElements(static_cast( sharp_edges.size() * 2))) { O3D_ERROR(&service_locator) << "Failed to allocate vertex buffer"; return NULL; } scoped_array values(new float[num_vertices * 3]); for (unsigned int i = 0; i < num_vertices; i++) { Point3 currentPoint; if (i % 2 == 0) currentPoint = sharp_edges[i / 2].pts[0]; else currentPoint = sharp_edges[i / 2].pts[1]; values[i * 3 + 0] = currentPoint.getX(); values[i * 3 + 1] = currentPoint.getY(); values[i * 3 + 2] = currentPoint.getZ(); } field->SetFromFloats(&values[0], 3, 0, static_cast(num_vertices)); stream_bank->SetVertexStream(Stream::POSITION, 0, field, 0); stream_bank->set_name("sharp_edges_stream_bank"); Material* material = GetSingleColorMaterial(pack, options.sharp_edge_color); sharp_edge_primitive->set_material(material); sharp_edge_primitive->set_primitive_type(Primitive::LINELIST); sharp_edge_primitive->set_number_vertices(static_cast( sharp_edges.size() * 2)); sharp_edge_primitive->set_number_primitives(static_cast( sharp_edges.size())); sharp_edge_primitive->set_stream_bank(stream_bank); } } } } } // Mark all Samplers to use tri-linear filtering if (!options.keep_filters) { std::vector samplers = pack->GetByClass(); for (unsigned ii = 0; ii < samplers.size(); ++ii) { Sampler* sampler = samplers[ii]; sampler->set_mag_filter(Sampler::LINEAR); sampler->set_min_filter(Sampler::LINEAR); sampler->set_mip_filter(Sampler::LINEAR); } } // Mark all Materials that are on Primitives that have no normals as constant. if (!options.keep_materials) { std::vector primitives = pack->GetByClass(); for (unsigned ii = 0; ii < primitives.size(); ++ii) { Primitive* primitive = primitives[ii]; StreamBank* stream_bank = primitive->stream_bank(); if (stream_bank && !stream_bank->GetVertexStream(Stream::NORMAL, 0)) { Material* material = primitive->material(); if (material) { ParamString* lighting_param = material->GetParam( Collada::kLightingTypeParamName); if (lighting_param) { // If the lighting type is lambert, blinn, or phong // copy the diffuse color to the emissive since that's most likely // what the user wants to see. if (lighting_param->value().compare( Collada::kLightingTypeLambert) == 0 || lighting_param->value().compare( Collada::kLightingTypeBlinn) == 0 || lighting_param->value().compare( Collada::kLightingTypePhong) == 0) { // There's 4 cases: (to bad they are not the same names) // 1) Diffuse -> Emissive // 2) DiffuseSampler -> Emissive // 3) Diffuse -> EmissiveSampler // 4) DiffuseSampler -> EmissiveSampler ParamFloat4* diffuse_param = material->GetParam( Collada::kMaterialParamNameDiffuse); ParamFloat4* emissive_param = material->GetParam( Collada::kMaterialParamNameEmissive); ParamSampler* diffuse_sampler_param = material->GetParam( Collada::kMaterialParamNameDiffuseSampler); ParamSampler* emissive_sampler_param = material->GetParam( Collada::kMaterialParamNameEmissive); Param* source_param = diffuse_param ? static_cast(diffuse_param) : static_cast(diffuse_sampler_param); Param* destination_param = emissive_param ? static_cast(emissive_param) : static_cast(emissive_sampler_param); if (!source_param->IsA(destination_param->GetClass())) { // The params do not match type so we need to make the emissive // Param the same as the diffuse Param. material->RemoveParam(destination_param); destination_param = material->CreateParamByClass( diffuse_param ? Collada::kMaterialParamNameEmissive : Collada::kMaterialParamNameEmissiveSampler, source_param->GetClass()); DCHECK(destination_param); } destination_param->CopyDataFromParam(source_param); } lighting_param->set_value(Collada::kLightingTypeConstant); } } } } } // Attempt to open the output file. FILE* out_file = file_util::OpenFile(out_filename, "wb"); if (out_file == NULL) { O3D_ERROR(&service_locator) << "Could not open output file \"" << FilePathToUTF8(out_filename).c_str() << "\""; if (error_messages) { *error_messages += error_collector.errors(); } return false; } // Create an archive file and serialize the JSON scene graph and assets to it. FileOutputStreamProcessor stream_processor(out_file); TarGzGenerator archive_generator(&stream_processor); archive_generator.AddFile(ArchiveRequest::O3D_MARKER, ArchiveRequest::O3D_MARKER_CONTENT_LENGTH); archive_generator.AddFileBytes( reinterpret_cast(ArchiveRequest::O3D_MARKER_CONTENT), ArchiveRequest::O3D_MARKER_CONTENT_LENGTH); // Serialize the created O3D scene graph to JSON. StringWriter out_writer(StringWriter::LF); JsonWriter json_writer(&out_writer, 2); if (!options.pretty_print) { json_writer.BeginCompacting(); } Serializer serializer(&service_locator, &json_writer, &archive_generator); serializer.SerializePack(pack.Get()); json_writer.Close(); out_writer.Close(); if (!options.pretty_print) { json_writer.EndCompacting(); } String json = out_writer.ToString(); archive_generator.AddFile("scene.json", json.length()); archive_generator.AddFileBytes(reinterpret_cast(json.c_str()), json.length()); // Now add original data (e.g. compressed textures) collected during // the loading process. AddBinaryElements(collada, &archive_generator); archive_generator.Close(true); pack->Destroy(); if (error_messages) { *error_messages = error_collector.errors(); } return true; } // Loads the input shader file and validates it. bool Verify(const FilePath& in_filename, const FilePath& out_filename, const Options& options, String* error_messages) { FilePath source_filename(in_filename); // Create a service locator and renderer. ServiceLocator service_locator; EvaluationCounter evaluation_counter(&service_locator); ClassManager class_manager(&service_locator); ObjectManager object_manager(&service_locator); Profiler profiler(&service_locator); ErrorStatus error_status(&service_locator); // Collect error messages. ErrorCollector error_collector(&service_locator); scoped_ptr renderer( Renderer::CreateDefaultRenderer(&service_locator)); renderer->InitCommon(); Pack::Ref pack(object_manager.CreatePack()); Transform* root = pack->Create(); root->set_name(O3D_STRING_CONSTANT("root")); Collada::Options collada_options; collada_options.condition_document = options.condition; collada_options.keep_original_data = false; Collada collada(pack.Get(), collada_options); ColladaConditioner conditioner(&service_locator); String vertex_shader_entry_point; String fragment_shader_entry_point; TemporaryFile temp_file; if (options.condition) { if (!TemporaryFile::Create(&temp_file)) { O3D_ERROR(&service_locator) << "Could not create temporary file"; if (error_messages) { *error_messages = error_collector.errors(); } return false; } SamplerStateList state_list; if (!conditioner.RewriteShaderFile(NULL, in_filename, temp_file.path(), &state_list, &vertex_shader_entry_point, &fragment_shader_entry_point)) { O3D_ERROR(&service_locator) << "Could not rewrite shader file"; if (error_messages) { *error_messages = error_collector.errors(); } return false; } source_filename = temp_file.path(); } else { source_filename = in_filename; } std::string shader_source_in; // Load file into memory if (!file_util::ReadFileToString(source_filename, &shader_source_in)) { O3D_ERROR(&service_locator) << "Could not read shader file " << FilePathToUTF8(source_filename).c_str(); if (error_messages) { *error_messages = error_collector.errors(); } return false; } Effect::MatrixLoadOrder matrix_load_order; scoped_ptr effect(pack->Create()); if (!effect->ValidateFX(shader_source_in, &vertex_shader_entry_point, &fragment_shader_entry_point, &matrix_load_order)) { O3D_ERROR(&service_locator) << "Could not validate shader file"; if (error_messages) { *error_messages = error_collector.errors(); } return false; } if (!conditioner.CompileHLSL(shader_source_in, vertex_shader_entry_point, fragment_shader_entry_point)) { O3D_ERROR(&service_locator) << "Could not HLSL compile shader file"; if (error_messages) { *error_messages = error_collector.errors(); } return false; } if (!conditioner.CompileCg(in_filename, shader_source_in, vertex_shader_entry_point, fragment_shader_entry_point)) { O3D_ERROR(&service_locator) << "Could not Cg compile shader file"; if (error_messages) { *error_messages = error_collector.errors(); } return false; } // If we've validated the file, then we write out the conditioned // shader to the given output file, if there is one. if (options.condition && !out_filename.empty()) { if (file_util::WriteFile(out_filename, shader_source_in.c_str(), static_cast(shader_source_in.size())) == -1) { O3D_ERROR(&service_locator) << "Warning: Could not write to output file '" << FilePathToUTF8(in_filename).c_str() << "'"; } } if (error_messages) { *error_messages = error_collector.errors(); } return true; } } // end namespace converter } // end namespace o3d