blob: 89362137583509008309c69767af61b38899d241 [file] [log] [blame]
/*
* 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 <cmath>
#include <map>
#include <utility>
#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<FilePath> paths = data_map.GetOriginalDataFilenames();
for (std::vector<FilePath>::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<const uint8*>(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>& triangle_list,
std::vector<Edge>* 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<int> 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<Material>();
singleColorMaterial->set_name("singleColorMaterial");
ParamString* lighting_param = singleColorMaterial->CreateParam<ParamString>(
Collada::kLightingTypeParamName);
lighting_param->set_value(Collada::kLightingTypeConstant);
ParamFloat4* emissive_param = singleColorMaterial->CreateParam<ParamFloat4>(
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<Edge, std::vector<Triangle>>* et_map) {
std::map<Edge, std::vector<Triangle>>::iterator iter1 =
et_map->find(edge);
if (iter1 == et_map->end()) {
std::vector<Triangle> 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(
Renderer::CreateDefaultRenderer(&service_locator));
renderer->InitCommon();
Pack::Ref pack(object_manager.CreatePack());
Transform* root = pack->Create<Transform>();
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<ParamObject>();
// This is some arbitrary name
param_object->set_name(O3D_STRING_CONSTANT("animSourceOwner"));
ParamFloat* param_float = param_object->CreateParam<ParamFloat>("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<Shape*> shapes = pack->GetByClass<Shape>();
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<Primitive*>(elements[e].Get());
// Get vertices and indices of this primitive.
if (primitive->primitive_type() != Primitive::TRIANGLELIST)
continue;
FieldReadAccessor<Point3> 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> triangle_list;
std::vector<Point3> 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, std::vector<Triangle>> 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<Edge, std::vector<Triangle>>::iterator iter;
std::vector<Edge> 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<Primitive>();
sharp_edge_primitive->SetOwner(shape);
sharp_edge_primitive->set_name("sharp_edge_primitive");
StreamBank* stream_bank = pack->Create<StreamBank>();
VertexBuffer* vertex_buffer = pack->Create<VertexBuffer>();
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<unsigned int>(
sharp_edges.size() * 2))) {
O3D_ERROR(&service_locator) << "Failed to allocate vertex buffer";
return NULL;
}
scoped_array<float> 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<unsigned int>(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<int>(
sharp_edges.size() * 2));
sharp_edge_primitive->set_number_primitives(static_cast<int>(
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<Sampler*> samplers = pack->GetByClass<Sampler>();
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<Primitive*> primitives = pack->GetByClass<Primitive>();
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<ParamString>(
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<ParamFloat4>(
Collada::kMaterialParamNameDiffuse);
ParamFloat4* emissive_param = material->GetParam<ParamFloat4>(
Collada::kMaterialParamNameEmissive);
ParamSampler* diffuse_sampler_param =
material->GetParam<ParamSampler>(
Collada::kMaterialParamNameDiffuseSampler);
ParamSampler* emissive_sampler_param =
material->GetParam<ParamSampler>(
Collada::kMaterialParamNameEmissive);
Param* source_param = diffuse_param ?
static_cast<Param*>(diffuse_param) :
static_cast<Param*>(diffuse_sampler_param);
Param* destination_param = emissive_param ?
static_cast<Param*>(emissive_param) :
static_cast<Param*>(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<const uint8*>(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<const uint8*>(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(
Renderer::CreateDefaultRenderer(&service_locator));
renderer->InitCommon();
Pack::Ref pack(object_manager.CreatePack());
Transform* root = pack->Create<Transform>();
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> effect(pack->Create<Effect>());
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<int>(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