blob: 68969f25e7b56727d5bcdd82501cb5c2c4243255 [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/cross/converter.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/scoped_ptr.h"
#include "converter/cross/renderer_stub.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 "import/cross/collada.h"
#include "import/cross/collada_conditioner.h"
#include "import/cross/file_output_stream_processor.h"
#include "import/cross/iarchive_generator.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 {
// Writes either to an archive file or to multiple flat files.
class MultiFileWriter : public IArchiveGenerator {
public:
virtual ~MultiFileWriter() {}
// Takes the path of an archive file. If writing multiple flat
// files, then for archive named archive.o3dtgz, creates directory
// "archive/" and writes all files within.
virtual bool OpenTopLevelFile(const FilePath& file_name,
const char* mode) = 0;
// Closes the top-level file and any currently open sub-file.
virtual void CloseTopLevelFile(bool success) = 0;
int AddFileBytes(const uint8* data, size_t n);
//----------------------------------------------------------------------
// IArchiveGenerator
// It seems these virtual functions need to be re-declared here.
// (Compiler bugs on Mac OS X?)
// AddFile either adds a file to the archive, or opens a new flat
// file. If a sub-file is currently open, implicitly closes it.
virtual bool AddFile(const String& file_name,
size_t file_size) = 0;
// Writes data to the current sub-file.
virtual int AddFileBytes(MemoryReadStream* stream, size_t n) = 0;
// Closes the current sub-file.
virtual void Close(bool success) = 0;
protected:
MultiFileWriter() {}
private:
DISALLOW_COPY_AND_ASSIGN(MultiFileWriter);
};
int MultiFileWriter::AddFileBytes(const uint8* data, size_t n) {
MemoryReadStream stream(data, n);
return AddFileBytes(&stream, n);
}
class TarGzWriter : public MultiFileWriter {
public:
TarGzWriter() {}
virtual ~TarGzWriter();
virtual bool OpenTopLevelFile(const FilePath& file_name,
const char* mode);
virtual bool AddFile(const String& file_name,
size_t file_size);
virtual int AddFileBytes(MemoryReadStream* stream,
size_t n);
virtual void Close(bool success);
virtual void CloseTopLevelFile(bool success);
private:
scoped_ptr<FileOutputStreamProcessor> stream_processor_;
scoped_ptr<TarGzGenerator> archive_generator_;
DISALLOW_COPY_AND_ASSIGN(TarGzWriter);
};
TarGzWriter::~TarGzWriter() {
// Need to reset these in the right order.
archive_generator_.reset();
stream_processor_.reset();
}
bool TarGzWriter::OpenTopLevelFile(const FilePath& file_name,
const char* mode) {
FILE* out_file = file_util::OpenFile(file_name, mode);
if (out_file == NULL) {
return false;
}
stream_processor_.reset(new FileOutputStreamProcessor(out_file));
archive_generator_.reset(new TarGzGenerator(stream_processor_.get()));
return true;
}
bool TarGzWriter::AddFile(const String& file_name,
size_t file_size) {
archive_generator_->AddFile(file_name, file_size);
return true;
}
int TarGzWriter::AddFileBytes(MemoryReadStream* stream,
size_t n) {
return archive_generator_->AddFileBytes(stream, n);
}
void TarGzWriter::Close(bool success) {
}
void TarGzWriter::CloseTopLevelFile(bool success) {
archive_generator_->Close(success);
}
class FlatFileWriter : public MultiFileWriter {
public:
FlatFileWriter() {}
virtual ~FlatFileWriter();
virtual bool OpenTopLevelFile(const FilePath& file_name,
const char* mode);
virtual bool AddFile(const String& file_name,
size_t file_size);
virtual int AddFileBytes(MemoryReadStream* stream,
size_t n);
virtual void Close(bool success);
virtual void CloseTopLevelFile(bool success);
private:
FilePath destination_directory_;
String mode_;
scoped_ptr<FileOutputStreamProcessor> stream_processor_;
DISALLOW_COPY_AND_ASSIGN(FlatFileWriter);
};
FlatFileWriter::~FlatFileWriter() {
}
bool FlatFileWriter::OpenTopLevelFile(const FilePath& file_name,
const char* mode) {
destination_directory_ = file_name.RemoveExtension();
if (!file_util::CreateDirectory(destination_directory_)) {
return false;
}
mode_ = String(mode);
return true;
}
bool FlatFileWriter::AddFile(const String& file_name,
size_t file_size) {
Close(true);
FilePath path = destination_directory_.Append(UTF8ToFilePath(file_name));
// We may need to make parent directories.
FilePath parent_dir = path.DirName();
if (!file_util::CreateDirectory(parent_dir)) {
return false;
}
FILE* out_file = file_util::OpenFile(path, mode_.c_str());
// We expect to be able to write the desired file.
DCHECK_NE(out_file, static_cast<FILE*>(NULL));
if (out_file == NULL) {
return false;
}
stream_processor_.reset(new FileOutputStreamProcessor(out_file));
return true;
}
int FlatFileWriter::AddFileBytes(MemoryReadStream* stream,
size_t n) {
if (!stream_processor_.get())
return 0;
StreamProcessor::Status status =
stream_processor_->ProcessBytes(stream, n);
if (status == StreamProcessor::FAILURE)
return 0;
return n;
}
void FlatFileWriter::Close(bool success) {
if (stream_processor_.get()) {
stream_processor_->Close(success);
stream_processor_.reset();
}
}
void FlatFileWriter::CloseTopLevelFile(bool success) {
Close(success);
}
void AddBinaryElements(const Collada& collada,
MultiFileWriter* file_writer) {
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);
file_writer->AddFile(FilePathToUTF8(*iter), data.size());
file_writer->AddFileBytes(
reinterpret_cast<const uint8*>(data.c_str()),
data.length());
}
}
} // end anonymous namespace
namespace converter {
// 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(RendererStub::CreateDefault(&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_options.convert_dds_to_png = options.convert_dds_to_png;
collada_options.convert_cg_to_glsl = options.convert_cg_to_glsl;
collada_options.converter_tool = options.converter_tool;
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);
}
// 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.
scoped_ptr<MultiFileWriter> file_writer;
if (options.archive) {
file_writer.reset(new TarGzWriter);
} else {
file_writer.reset(new FlatFileWriter);
}
// Create an archive file or individual flat files and serialize the
// JSON scene graph and assets to it / them.
if (!file_writer->OpenTopLevelFile(out_filename, "wb")) {
O3D_ERROR(&service_locator) << "Could not open output file \""
<< FilePathToUTF8(out_filename).c_str()
<< "\"";
if (error_messages) {
*error_messages += error_collector.errors();
}
return false;
}
if (options.archive) {
file_writer->AddFile(ArchiveRequest::kO3DMarker,
ArchiveRequest::kO3DMarkerContentLength);
file_writer->AddFileBytes(
reinterpret_cast<const uint8*>(ArchiveRequest::kO3DMarkerContent),
ArchiveRequest::kO3DMarkerContentLength);
}
// 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::Options serializer_options(
options.binary ? Serializer::Options::kBinaryOutputOn :
Serializer::Options::kBinaryOutputOff);
Serializer serializer(
&service_locator, &json_writer, file_writer.get(), serializer_options);
serializer.SerializePack(pack.Get());
json_writer.Close();
out_writer.Close();
if (!options.pretty_print) {
json_writer.EndCompacting();
}
String json = out_writer.ToString();
file_writer->AddFile("scene.json", json.length());
file_writer->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, file_writer.get());
file_writer->CloseTopLevelFile(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(
RendererStub::CreateDefault(&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