diff options
Diffstat (limited to 'o3d/import')
53 files changed, 11444 insertions, 0 deletions
diff --git a/o3d/import/build.scons b/o3d/import/build.scons new file mode 100644 index 0000000..554750b --- /dev/null +++ b/o3d/import/build.scons @@ -0,0 +1,144 @@ +# 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. + + +Import('env') +env.SConscript('fcollada.scons', exports=['env']) + +env.Append( + CPPPATH=[ + env.Dir('$CG_DIR/include'), + env.Dir('$COLLADA_DIR'), + env.Dir('$COLLADA_DIR/LibXML/include'), + env.Dir('$ZLIB_DIR'), + # Include path for generated headers. + env.Dir('$OBJ_ROOT/compiler/technique'), + # Include path for Antlr C runtime headers. + env.Dir('$ANTLRLIBC_DIR/include'), + ], +) + +# Add renderer-specific includes to the environment. +env.Append(CPPPATH = env['RENDERER_INCLUDE_PATH']) + +if env.Bit('windows'): + env.Append( + CCFLAGS = [ + '/Ylo3dImport', + '/FIimport/cross/precompile.h', + ], + ) + pch, pch_obj = env.PCH('cross/precompile.cc') + env['PCH'] = pch + env['PCHSTOP'] = 'import/cross/precompile.h' +else: + pch_obj = 'cross/precompile.cc' + env.Append(CCFLAGS = [['-include', 'import/cross/precompile.h']]) + +collada_inputs = [ + 'cross/collada.cc', + 'cross/collada_zip_archive.cc', + 'cross/zip_archive.cc', + 'cross/gz_compressor.cc', + 'cross/file_output_stream_processor.cc', + 'cross/tar_generator.cc', + 'cross/targz_generator.cc', +] + +archive_inputs = [ + pch_obj, + + 'cross/archive_processor.cc', + 'cross/archive_request.cc', + 'cross/gz_decompressor.cc', + 'cross/memory_stream.cc', + 'cross/raw_data.cc', + 'cross/tar_processor.cc', + 'cross/targz_processor.cc', +] + +conditioner_inputs = ['cross/collada_conditioner.cc'] + +if env.Bit('mac'): + conditioner_inputs += [ + 'mac/collada_conditioner_mac.mm', + ] + FRAMEWORKS = [ + 'Foundation' + ] + +if env.Bit('windows'): + conditioner_inputs += [ + 'win/collada_conditioner_win.cc', + ] + +if env.Bit('linux'): + conditioner_inputs += [ + 'linux/collada_conditioner_linux.cc', + ] + +# Build a library called 'o3dImport' from the input sources. +env.ComponentLibrary('o3dImport', collada_inputs) + +env.ComponentLibrary('o3dArchive', archive_inputs) + +# Build a library that ONLY includes the stubbed-out conditioner code +# TODO: merge this back into o3dImport as soon as the import +# lib is no longer needed by the plugin. +env.ComponentLibrary('o3dImportNoConditioner', + ['cross/collada_conditioner_stub.cc']) + +# Build a library that ONLY includes the active conditioner code +# TODO: merge this back into o3dImport as soon as the import +# lib is no longer needed by the plugin. +conditioner_lib = env.ComponentLibrary('o3dImportConditioner', + conditioner_inputs) + +# The conditioner needs these regardless of what renderer we're +# building for. +if env.Bit('windows'): + env.Requires(conditioner_lib, + env.Replicate('$ARTIFACTS_DIR', + ['$CG_DIR/bin/cgc.exe', + '$CG_DIR/bin/cg.dll', + '$CG_DIR/bin/cgGL.dll', + '$GLEW_DIR/bin/glew32.dll', + ])) + +if env.Bit('linux'): + env.Requires(conditioner_lib, + env.Replicate('$ARTIFACTS_DIR', ['$CG_DIR/bin/cgc', + '$CG_DIR/lib/libCg.so', + '$CG_DIR/lib/libCgGL.so', + '$GLEW_DIR/lib/libGLEW.so.1.5', + ])) + +if env.Bit('mac'): + env.Requires(conditioner_lib, + env.Replicate('$ARTIFACTS_DIR', ['$CG_DIR/bin/cgc'])) diff --git a/o3d/import/cross/archive_processor.cc b/o3d/import/cross/archive_processor.cc new file mode 100644 index 0000000..f02ef6d --- /dev/null +++ b/o3d/import/cross/archive_processor.cc @@ -0,0 +1,137 @@ +/* + * 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. + */ + + +#include "import/cross/precompile.h" + +#include <sys/stat.h> + +#include "import/cross/archive_processor.h" + +#include "base/logging.h" +#include "import/cross/memory_buffer.h" +#include "third_party/zlib/files/zlib.h" + +const int kChunkSize = 16384; + +namespace o3d { + +#ifdef _DEBUG +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// For debugging only, report a zlib or i/o error +void zerr(int ret) { + LOG(ERROR) << "ArchiveProcessor: "; + + switch (ret) { + case Z_ERRNO: + if (ferror(stdin)) + LOG(ERROR) << "error reading stdin\n"; + if (ferror(stdout)) + LOG(ERROR) << "error writing stdout\n"; + break; + case Z_STREAM_ERROR: + LOG(ERROR) << "invalid compression level\n"; + break; + case Z_DATA_ERROR: + LOG(ERROR) << "invalid or incomplete deflate data\n"; + break; + case Z_MEM_ERROR: + LOG(ERROR) << "out of memory\n"; + break; + case Z_VERSION_ERROR: + LOG(ERROR) << "zlib version mismatch!\n"; + break; + } +} +#endif // _DEBUG + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int ArchiveProcessor::ProcessEntireStream(MemoryReadStream *stream) { + int result = Z_OK; + bool has_error = false; + + // decompress until deflate stream ends or error + do { + int remaining = stream->GetRemainingByteCount(); + + int process_this_time = remaining < kChunkSize ? remaining : kChunkSize; + result = ProcessCompressedBytes(stream, process_this_time); + + has_error = (result != Z_OK && result != Z_STREAM_END); + +#ifdef _DEBUG + if (has_error) { + zerr(result); + } +#endif + } while (result != Z_STREAM_END && !has_error); + + if (result == Z_STREAM_END) { + // if we got to the end of stream, then we're good... + result = Z_OK; + } + + return result; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int ArchiveProcessor::ProcessFile(const char *filename) { + struct stat file_info; + int result = stat(filename, &file_info); + if (result != 0) return -1; + + int file_length = file_info.st_size; + if (file_length == 0) return -1; + + MemoryBuffer<uint8> buffer; + buffer.Allocate(file_length); + uint8 *p = buffer; + + // Test by reading in a tar.gz file and sending through the + // progressive streaming system + FILE *fp = fopen(filename, "rb"); + if (!fp) return -1; // can't open file! + fread(p, sizeof(uint8), file_length, fp); + fclose(fp); + + MemoryReadStream stream(p, file_length); + + result = ProcessEntireStream(&stream); + + if (result == Z_STREAM_END) { + // if we got to the end of stream, then we're good... + result = Z_OK; + } + + return result; +} + +} // namespace o3d diff --git a/o3d/import/cross/archive_processor.h b/o3d/import/cross/archive_processor.h new file mode 100644 index 0000000..9bc5368 --- /dev/null +++ b/o3d/import/cross/archive_processor.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + + +#ifndef O3D_IMPORT_CROSS_ARCHIVE_PROCESSOR_H_ +#define O3D_IMPORT_CROSS_ARCHIVE_PROCESSOR_H_ + +#include <string> +#include "base/basictypes.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class ArchiveFileInfo { + public: + ArchiveFileInfo(const std::string &filename, int file_size) + : filename_(filename), file_size_(file_size) {} + + virtual ~ArchiveFileInfo() {} + + const std::string &GetFileName() const { return filename_; } + int GetFileSize() const { return file_size_; } + + private: + std::string filename_; + int file_size_; + + DISALLOW_COPY_AND_ASSIGN(ArchiveFileInfo); +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class ArchiveCallbackClient { + public: + virtual ~ArchiveCallbackClient() {} + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info) = 0; + virtual bool ReceiveFileData(MemoryReadStream *stream, int nbytes) = 0; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class ArchiveProcessor { + public: + explicit ArchiveProcessor(ArchiveCallbackClient *callback_client) {} + + virtual ~ArchiveProcessor() {} + + // Call to "push" bytes into the processor. They will be decompressed and + // the appropriate callbacks on |callback_client| will happen + // as files come in... + // + // Return values (using zlib error codes): + // Z_OK : Processing was successful - but not yet done + // Z_STREAM_END : We're done - archive is completely/successfully processed + // any other value indicates an error condition + // + // Note: even archive formats not based on zlib should use these codes + // (Z_OK, Z_STREAM_END) + // + virtual int ProcessCompressedBytes(MemoryReadStream *stream, + size_t bytes_to_process) = 0; + + // Decompresses the complete file archive, making file callbacks as the files + // come in... + virtual int ProcessFile(const char *filename); + + // Decompresses the complete archive from memory, + // making file callbacks as the files come in... + virtual int ProcessEntireStream(MemoryReadStream *stream); + + protected: + DISALLOW_COPY_AND_ASSIGN(ArchiveProcessor); +}; + +#ifdef _DEBUG +// For debugging, report a zlib or i/o error +extern void zerr(int result); +#endif +} // namespace o3d +#endif // O3D_IMPORT_CROSS_ARCHIVE_PROCESSOR_H_ diff --git a/o3d/import/cross/archive_request.cc b/o3d/import/cross/archive_request.cc new file mode 100644 index 0000000..86d8a57 --- /dev/null +++ b/o3d/import/cross/archive_request.cc @@ -0,0 +1,231 @@ +/* + * 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 definition of the ArchiveRequest class. + +#include "import/cross/precompile.h" + +#include "import/cross/archive_request.h" + +#include "import/cross/targz_processor.h" +#include "core/cross/pack.h" + +#define DEBUG_ARCHIVE_CALLBACKS 0 + +using glue::DownloadStream; + +namespace o3d { + +O3D_DEFN_CLASS(ArchiveRequest, ObjectBase); + +// NOTE: The file starts with "aaaaaaaa" in the hope that most tar.gz creation +// utilties can easily sort with this being the file first in the .tgz +// Otherwise you'll have to manually force it to be the first file. +const char* ArchiveRequest::O3D_MARKER = "aaaaaaaa.o3d"; +const char* ArchiveRequest::O3D_MARKER_CONTENT = "o3d"; +const size_t ArchiveRequest::O3D_MARKER_CONTENT_LENGTH = 3; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ArchiveRequest::ArchiveRequest(ServiceLocator* service_locator, + Pack *pack) + : ObjectBase(service_locator), + pack_(pack), + done_(false), + success_(false), + ready_state_(0), + stream_length_(0), + bytes_received_(0) { + archive_processor_ = new TarGzProcessor(this); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ArchiveRequest::~ArchiveRequest() { + delete archive_processor_; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ArchiveRequest *ArchiveRequest::Create(ServiceLocator* service_locator, + Pack *pack) { + ArchiveRequest *request = new ArchiveRequest(service_locator, pack); + return request; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void ArchiveRequest::NewStreamCallback(DownloadStream *stream) { + // we're starting to stream - make note of the stream length + stream_length_ = stream->GetStreamLength(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int32 ArchiveRequest::WriteReadyCallback(DownloadStream *stream) { + // Setting this too high causes Firefox to timeout in the Write callback. + return 1024; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int32 ArchiveRequest::WriteCallback(DownloadStream *stream, + int32 offset, + int32 length, + void *data) { + // Count the bytes as they stream in + bytes_received_ += length; + + MemoryReadStream memory_stream(reinterpret_cast<uint8*>(data), length); + + // Progressively decompress the bytes we've just been given + int result = + archive_processor_->ProcessCompressedBytes(&memory_stream, length); + + if (result != Z_OK && result != Z_STREAM_END) { + stream->Cancel(); // tell the browser to stop downloading + set_success(false); + set_error("Invalid tar gz file"); + if (onreadystatechange()) + onreadystatechange()->Run(); // javascript callback with failure + } + + return length; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Loads the Archive file, calls the JS callback to notify success. +void ArchiveRequest::FinishedCallback(DownloadStream *stream, + bool success, + const std::string &filename, + const std::string &mime_type) { + set_ready_state(ArchiveRequest::STATE_LOADED); + + // Since the standard codes only go far enough to tell us that the download + // succeeded, we set the success [and implicitly the done] flags to give the + // rest of the story. + set_success(success); + if (!success) { + // I have no idea if an error is already set here but one MUST be set + // so let's check + set_error("Could not download archive. It could be a permission-related " + "issue."); + } + if (onreadystatechange()) + onreadystatechange()->Run(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ArchiveCallbackClient methods + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void ArchiveRequest::ReceiveFileHeader(const ArchiveFileInfo &file_info) { + int file_size = file_info.GetFileSize(); + +#if DEBUG_ARCHIVE_CALLBACKS + printf("\n"); + printf("-----------------------------------------------------------------\n"); + printf("File Name: %s\n", file_info.GetFileName().c_str()); + printf("File Size: %d\n", file_size); + printf("-----------------------------------------------------------------\n"); +#endif + + if (file_size > 0) { // skip over directory entries (with zero file size) + // Save filename for when we create our RawData object + current_filename_ = file_info.GetFileName(); + + temp_buffer_.Allocate(file_size); + file_memory_stream_.Assign(static_cast<uint8*>(temp_buffer_), + file_size); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool ArchiveRequest::ReceiveFileData(MemoryReadStream *input_stream, + int nbytes) { + assert(input_stream->GetRemainingByteCount() >= nbytes); + assert(file_memory_stream_.GetRemainingByteCount() >= nbytes); + + // Buffer file bytes from |input_stream| to |file_memory_stream_| + file_memory_stream_.Write(input_stream->GetDirectMemoryPointer(), nbytes); + input_stream->Skip(nbytes); + + // If we've just filled our file temp buffer then callback + if (file_memory_stream_.GetRemainingByteCount() == 0) { + // We've reached the end of file + + // Check if this is file metadata (extra file attributes) and skip if so + // On the Mac, the tar command marks metadata by + // pre-pending "._" to the filename + bool is_metadata = false; + std::string::size_type j = current_filename_.find("._"); + if (j != std::string::npos) { + if (j == 0 || current_filename_[j - 1] == '/') is_metadata = true; + } + + // Skip ".DS_Store" file which may be created in Mac-generated archives + j = current_filename_.find(".DS_Store"); + if (j != std::string::npos) { + if (j == 0 || current_filename_[j - 1] == '/') is_metadata = true; + } + + if (!is_metadata && onfileavailable()) { + // keep track of the "current" data object which the callback will use + raw_data_ = RawData::Create(service_locator(), + current_filename_, + temp_buffer_, + file_memory_stream_.GetTotalStreamLength() ); + + // keeps them all around until the ArchiveRequest goes away + raw_data_list_.push_back(raw_data_); + + // If it's the first file is must be the O3D_MARKER or else it's an error. + if (raw_data_list_.size() == 1) { + if (raw_data_->uri().compare(O3D_MARKER) != 0 || + raw_data_->StringValue().compare(O3D_MARKER_CONTENT) != 0) { + set_error(String("Archive '") + uri_ + + String("' is not intended for O3D. Missing '") + + O3D_MARKER + String("' as first file in archive.")); + return false; + } + } else { + onfileavailable()->Run(); + } + + // If data hasn't been discarded (inside callback) then writes out to + // temp file so we can get the data back at a later time + raw_data_.Get()->Flush(); + + // Remove the reference to the raw_data so we don't have undefined + // behavior after the callback. + raw_data_.Reset(); + } + } + return true; +} + +} // namespace o3d diff --git a/o3d/import/cross/archive_request.h b/o3d/import/cross/archive_request.h new file mode 100644 index 0000000..2a29bde --- /dev/null +++ b/o3d/import/cross/archive_request.h @@ -0,0 +1,203 @@ +/* + * 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 declaration of the ArchiveRequest class. + +#ifndef O3D_IMPORT_CROSS_ARCHIVE_REQUEST_H_ +#define O3D_IMPORT_CROSS_ARCHIVE_REQUEST_H_ + +#include <algorithm> +#include <string> + +#include "base/scoped_ptr.h" +#include "core/cross/callback.h" +#include "core/cross/object_base.h" +#include "core/cross/pack.h" +#include "import/cross/targz_processor.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/memory_stream.h" +#include "import/cross/raw_data.h" +#include "plugin/cross/download_stream.h" + +namespace o3d { + +typedef Closure ArchiveRequestCallback; + +// An ArchiveRequest object is used to carry out an asynchronous request +// for a file to be loaded. +class ArchiveRequest : public ObjectBase, public ArchiveCallbackClient { + public: + enum ReadyState { // These are copied from XMLHttpRequest. + STATE_INIT = 0, + STATE_OPEN = 1, + STATE_SENT = 2, + STATE_RECEIVING = 3, + STATE_LOADED = 4, + }; + + // A file by this name must be the first file in the archive otherwise + // the archive is rejected. This is a security measure so that O3D + // can not be used to open arbitrary .tgz files but only those files someone + // has specifically prepared for O3D. This file will not be passed to + // the onfileavailable callback. + static const char* O3D_MARKER; + + // The contents of the O3D_MARKER file. Arguably the content should not matter + // but for the sake of completeness we define the content so there is no + // ambiguity. + static const char* O3D_MARKER_CONTENT; + + // The size of the O3D_MARKER_CONTENT. + static const size_t O3D_MARKER_CONTENT_LENGTH; + + public: + typedef SmartPointer<ArchiveRequest> Ref; + + virtual ~ArchiveRequest(); + + static ArchiveRequest *Create(ServiceLocator* service_locator, + Pack *pack); + + + // Streaming callbacks + virtual void NewStreamCallback(glue::DownloadStream *stream); + virtual int32 WriteReadyCallback(glue::DownloadStream *stream); + virtual int32 WriteCallback(glue::DownloadStream *stream, + int32 offset, + int32 length, + void *data); + + virtual void FinishedCallback(glue::DownloadStream *stream, + bool success, + const std::string &filename, + const std::string &mime_type); + + // ArchiveCallbackClient methods + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); + virtual bool ReceiveFileData(MemoryReadStream *stream, int nbytes); + + Pack *pack() { + return pack_.Get(); // Set at creation time and never changed. + } + + ArchiveRequestCallback *onfileavailable() { + return onfileavailable_.get(); + } + void set_onfileavailable(ArchiveRequestCallback *onfileavailable) { + onfileavailable_.reset(onfileavailable); + } + + ArchiveRequestCallback *onreadystatechange() { + return onreadystatechange_.get(); + } + void set_onreadystatechange(ArchiveRequestCallback *onreadystatechange) { + onreadystatechange_.reset(onreadystatechange); + } + + // returns the "current" data object (used by the callback) + RawData *data() { + return raw_data_.Get(); + } + + const String& uri() { + return uri_; + } + void set_uri(const String& uri) { + uri_ = uri; + } + + bool done() { + return done_; + } + + bool success() { + return success_; + } + void set_success(bool success) { + success_ = success; + done_ = true; + pack_.Reset(); // Remove pack reference to allow garbage collection of pack. + } + + const String& error() const { + return error_; + } + void set_error(const String& error) { + error_ = error; + } + + int ready_state() { + return ready_state_; + } + void set_ready_state(int state) { + ready_state_ = state; + } + + int stream_length() { + return stream_length_; + } + + int bytes_received() { + return bytes_received_; + } + + protected: + ArchiveRequest(ServiceLocator* service_locator, Pack *pack); + + Pack::Ref pack_; + scoped_ptr<ArchiveRequestCallback> onreadystatechange_; + scoped_ptr<ArchiveRequestCallback> onfileavailable_; + String uri_; + + // Request state + bool done_; // Set after completion/failure to indicate success_ is valid. + bool success_; // Set after completion/failure to indicate which it is. + int ready_state_; // Like the XMLHttpRequest variable of the same name. + String error_; // Set after completion on failure. + + TarGzProcessor *archive_processor_; + std::vector<RawData::Ref> raw_data_list_; + RawData::Ref raw_data_; + MemoryBuffer<uint8> temp_buffer_; + MemoryWriteStream file_memory_stream_; + String current_filename_; + + int stream_length_; // total length of stream + int bytes_received_; // bytes received so far + + O3D_DECL_CLASS(ArchiveRequest, ObjectBase) + DISALLOW_IMPLICIT_CONSTRUCTORS(ArchiveRequest); +}; // ArchiveRequest + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_ARCHIVE_REQUEST_H_ diff --git a/o3d/import/cross/collada.cc b/o3d/import/cross/collada.cc new file mode 100644 index 0000000..a9cc2e1 --- /dev/null +++ b/o3d/import/cross/collada.cc @@ -0,0 +1,2713 @@ +/* + * 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 functions for importing COLLADA files into O3D. +#include "import/cross/precompile.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/string_util.h" +#include "core/cross/curve.h" +#include "core/cross/error.h" +#include "core/cross/function.h" +#include "core/cross/ierror_status.h" +#include "core/cross/math_utilities.h" +#include "core/cross/matrix4_axis_rotation.h" +#include "core/cross/matrix4_composition.h" +#include "core/cross/matrix4_scale.h" +#include "core/cross/matrix4_translation.h" +#include "core/cross/pack.h" +#include "core/cross/param_operation.h" +#include "core/cross/primitive.h" +#include "core/cross/skin.h" +#include "core/cross/stream.h" +#include "import/cross/collada.h" +#include "import/cross/collada_conditioner.h" +#include "import/cross/collada_zip_archive.h" +#include "utils/cross/file_path_utils.h" + +#define COLLADA_NAMESPACE "collada" +#define COLLADA_NAMESPACE_SEPARATOR "." + +// Macro to provide a uniform prefix for all string constants created by +// COLLADA. +#define COLLADA_STRING_CONSTANT(value) \ + (COLLADA_NAMESPACE COLLADA_NAMESPACE_SEPARATOR value) + + +namespace o3d { + +class TranslationMap : public FCDGeometryIndexTranslationMap { +}; + +namespace { + +Vector3 FMVector3ToVector3(const FMVector3& fmvector3) { + return Vector3(fmvector3.x, fmvector3.y, fmvector3.z); +} + +Vector4 FMVector4ToVector4(const FMVector4& fmvector4) { + return Vector4(fmvector4.x, fmvector4.y, fmvector4.z, fmvector4.w); +} + +Float2 FMVector2ToFloat2(const FMVector2& fmvector2) { + return Float2(fmvector2.x, fmvector2.y); +} + +Matrix4 FMMatrix44ToMatrix4(const FMMatrix44& fmmatrix44) { + return Matrix4(Vector4(fmmatrix44[0][0], + fmmatrix44[0][1], + fmmatrix44[0][2], + fmmatrix44[0][3]), + Vector4(fmmatrix44[1][0], + fmmatrix44[1][1], + fmmatrix44[1][2], + fmmatrix44[1][3]), + Vector4(fmmatrix44[2][0], + fmmatrix44[2][1], + fmmatrix44[2][2], + fmmatrix44[2][3]), + Vector4(fmmatrix44[3][0], + fmmatrix44[3][1], + fmmatrix44[3][2], + fmmatrix44[3][3])); +} +} // anonymous namespace + +// Import the given COLLADA file or ZIP file into the given scene. +// This is the external interface to o3d. +bool Collada::Import(Pack* pack, + const FilePath& filename, + Transform* parent, + ParamFloat* animation_input, + const Options& options) { + Collada collada(pack, options); + return collada.ImportFile(filename, parent, animation_input); +} + +bool Collada::Import(Pack* pack, + const String& filename, + Transform* parent, + ParamFloat* animation_input, + const Options& options) { + return Collada::Import(pack, + UTF8ToFilePath(filename), + parent, + animation_input, + options); +} + +// Parameters: +// pack: The pack into which the scene objects will be placed. +// Returns true on success. +Collada::Collada(Pack* pack, const Options& options) + : service_locator_(pack->service_locator()), + pack_(pack), + options_(options), + dummy_effect_(NULL), + dummy_material_(NULL), + instance_root_(NULL), + cull_enabled_(false), + cull_front_(false), + front_cw_(false), + collada_zip_archive_(NULL), + unique_filename_counter_(0) { +} + +Collada::~Collada() { + delete collada_zip_archive_; +} + +void Collada::ClearData() { + textures_.clear(); + original_data_.clear(); + effects_.clear(); + shapes_.clear(); + skinned_shapes_.clear(); + materials_.clear(); + delete collada_zip_archive_; + collada_zip_archive_ = NULL; + cull_enabled_ = false; + cull_front_ = false; + front_cw_ = false; + delete instance_root_; + instance_root_ = NULL; + base_path_ = FilePath(FilePath::kCurrentDirectory); + unique_filename_counter_ = 0; +} + +std::vector<FilePath> Collada::GetOriginalDataFilenames() const { + std::vector<FilePath> result; + for (OriginalDataMap::const_iterator iter = original_data_.begin(); + iter != original_data_.end(); + ++iter) { + result.push_back(iter->first); + } + return result; +} + +const std::string& Collada::GetOriginalData(const FilePath& filename) const { + static const std::string empty; + OriginalDataMap::const_iterator entry = original_data_.find(filename); + if (entry != original_data_.end()) { + return entry->second; + } else { + return empty; + } +} + +// Import the given COLLADA file or ZIP file under the given parent node. +// Parameters: +// filename: The COLLADA or ZIPped COLLADA file to import. +// parent: The parent node under which the imported nodes will be +// placed. If NULL, nodes will be placed under the +// client's root. +// animation_input: The float parameter used as the input to the animation +// (if present). This is usually time. This can be null +// if there is no animation. +// Returns true on success. +bool Collada::ImportFile(const FilePath& filename, Transform* parent, + ParamFloat* animation_input) { + // Each time we start a new import, we need to clear out data from + // the last import (if any). + ClearData(); + + // Convert the base_path given in the options to an absolute path. + base_path_ = options_.base_path; + file_util::AbsolutePath(&base_path_); + + bool status = false; + if (ZipArchive::IsZipFile(FilePathToUTF8(filename))) { + status = ImportZIP(filename, parent, animation_input); + } else { + status = ImportDAE(filename, parent, animation_input); + } + + if (!status) { + // TODO: this could probably be the original URI instead of some + // filename in the temp folder. + O3D_ERROR(service_locator_) << "Unable to import: " + << FilePathToUTF8(filename).c_str(); + } + + return status; +} + +// Imports the given ZIP file into the given client. +// Parameters: see Import() above. +// Returns true on success. +bool Collada::ImportZIP(const FilePath& filename, Transform* parent, + ParamFloat* animation_input) { + // This uses minizip, which avoids decompressing the zip archive to a + // temp directory... + // + bool status = false; + int result = 0; + + String filename_str = FilePathToUTF8(filename); + collada_zip_archive_ = new ColladaZipArchive(filename_str, &result); + + if (result == UNZ_OK) { + FCollada::Initialize(); + FCDocument* doc = FCollada::NewTopDocument(); + + if (doc) { + std::string model_path = collada_zip_archive_->GetColladaPath().c_str(); + + size_t doc_buffer_size = 0; + char *doc_buffer = collada_zip_archive_->GetFileData(model_path, + &doc_buffer_size); + + if (doc_buffer && doc_buffer_size > 0) { + DLOG(INFO) << "Loading Collada model \"" + << model_path << "\" from zip file \"" + << filename_str << "\""; + + std::wstring model_path_w = UTF8ToWide(model_path); + + bool fc_status = FCollada::LoadDocumentFromMemory(model_path_w.c_str(), + doc, + doc_buffer, + doc_buffer_size); + + + if (fc_status) { + if (options_.condition_document) { + ColladaConditioner conditioner(service_locator_); + if (conditioner.ConditionDocument(doc, collada_zip_archive_)) { + status = ImportDAEDocument(doc, + fc_status, + parent, + animation_input); + } + } else { + status = ImportDAEDocument(doc, fc_status, parent, animation_input); + } + } + } + doc->Release(); + } + FCollada::Release(); + } + + if (!status) { + delete collada_zip_archive_; + collada_zip_archive_ = NULL; + } + + return status; +} + +// Imports the given COLLADA file (.DAE) into the given pack. +// Parameters: +// see Import() above. +// Returns: +// true on success. +bool Collada::ImportDAE(const FilePath& filename, + Transform* parent, + ParamFloat* animation_input) { + if (!parent) { + return false; + } + bool status = false; + FCollada::Initialize(); + FCDocument* doc = FCollada::NewTopDocument(); + if (doc) { + std::wstring filename_w = FilePathToWide(filename); + bool fc_status = FCollada::LoadDocumentFromFile(doc, filename_w.c_str()); + if (options_.condition_document) { + ColladaConditioner conditioner(service_locator_); + if (conditioner.ConditionDocument(doc, NULL)) { + status = ImportDAEDocument(doc, fc_status, parent, animation_input); + } + } else { + status = ImportDAEDocument(doc, fc_status, parent, animation_input); + } + doc->Release(); + } + FCollada::Release(); + return status; +} + +// Imports the given FCDocument document (already loaded) into the given pack. +// Returns: +// true on success. +bool Collada::ImportDAEDocument(FCDocument* doc, + bool fc_status, + Transform* parent, + ParamFloat* animation_input) { + if (!parent) { + return false; + } + bool status = false; + if (doc) { + if (fc_status) { + Vector3 up_axis = options_.up_axis; + FMVector3 up(up_axis.getX(), up_axis.getY(), up_axis.getZ()); + // Transform the document to the given up vector + + FCDocumentTools::StandardizeUpAxisAndLength(doc, up); + + // Import all the textures in the file. Even if they are not used by + // materials or models the user put them in the file and might need them + // at runtime. + // + // TODO: Add option to skip this step if user just wants what's + // actually used by models. The rest of the code already deals with this. + FCDImageLibrary* image_library = doc->GetImageLibrary(); + for (int i = 0; i < image_library->GetEntityCount(); i++) { + FCDEntity* entity = image_library->GetEntity(i); + LOG_ASSERT(entity); + LOG_ASSERT(entity->GetType() == FCDEntity::IMAGE); + FCDImage* image = down_cast<FCDImage*>(entity); + BuildTextureFromImage(image); + } + + // Import all the materials in the file. Even if they are not used by + // models the user put them in the file and might need them at runtime. + // + // TODO: Add option to skip this step if user just wants what's + // actually used by models. The rest of the code already deals with this. + FCDMaterialLibrary* material_library = doc->GetMaterialLibrary(); + for (int i = 0; i < material_library->GetEntityCount(); i++) { + FCDEntity* entity = material_library->GetEntity(i); + LOG_ASSERT(entity); + LOG_ASSERT(entity->GetType() == FCDEntity::MATERIAL); + FCDMaterial* collada_material = down_cast<FCDMaterial*>(entity); + BuildMaterial(doc, collada_material); + } + + // Import the scene objects, starting at the root. + FCDSceneNode* scene = doc->GetVisualSceneInstance(); + if (scene) { + instance_root_ = CreateInstanceTree(scene); + ImportTree(instance_root_, parent, animation_input); + ImportTreeInstances(doc, instance_root_); + delete instance_root_; + instance_root_ = NULL; + status = true; + } + } + } + return status; +} + +namespace { + +Curve::Infinity ConvertInfinity(FUDaeInfinity::Infinity infinity) { + switch (infinity) { + case FUDaeInfinity::LINEAR : + return Curve::LINEAR; + case FUDaeInfinity::CYCLE : + return Curve::CYCLE; + case FUDaeInfinity::CYCLE_RELATIVE : + return Curve::CYCLE_RELATIVE; + case FUDaeInfinity::OSCILLATE : + return Curve::OSCILLATE; + default: + return Curve::CONSTANT; + } +} + +StepCurveKey* BuildStepKey(Curve* curve, FCDAnimationKey* fcd_key, + float output_scale) { + StepCurveKey* key = curve->Create<StepCurveKey>(); + key->SetInput(fcd_key->input); + key->SetOutput(fcd_key->output * output_scale); + return key; +} + +LinearCurveKey* BuildLinearKey(Curve* curve, FCDAnimationKey* fcd_key, + float output_scale) { + LinearCurveKey* key = curve->Create<LinearCurveKey>(); + key->SetInput(fcd_key->input); + key->SetOutput(fcd_key->output * output_scale); + return key; +} + +BezierCurveKey* BuildBezierKey(Curve* curve, FCDAnimationKeyBezier* fcd_key, + float output_scale) { + BezierCurveKey* key = curve->Create<BezierCurveKey>(); + key->SetInput(fcd_key->input); + key->SetOutput(fcd_key->output * output_scale); + Float2 in_tangent = FMVector2ToFloat2(fcd_key->inTangent); + in_tangent[1] *= output_scale; + key->SetInTangent(in_tangent); + Float2 out_tangent = FMVector2ToFloat2(fcd_key->outTangent); + out_tangent[1] *= output_scale; + key->SetOutTangent(out_tangent); + return key; +} + +void BindParams(ParamObject* input_object, const char* input_param_name, + ParamObject* output_object, const char* output_param_name) { + Param* input_param = input_object->GetUntypedParam(input_param_name); + DCHECK(input_param); + + Param* output_param = output_object->GetUntypedParam(output_param_name); + DCHECK(output_param); + + bool ok = input_param->Bind(output_param); + DCHECK_EQ(ok, true); +} + +void BindParams(ParamObject* input_object, const char* input_param_name, + Param* output_param) { + Param* input_param = input_object->GetUntypedParam(input_param_name); + DCHECK(input_param); + + bool ok = input_param->Bind(output_param); + DCHECK_EQ(ok, true); +} + +void BindParams(Param* input_param, + ParamObject* output_object, const char* output_param_name) { + Param* output_param = output_object->GetUntypedParam(output_param_name); + DCHECK(output_param); + + bool ok = input_param->Bind(output_param); + DCHECK_EQ(ok, true); +} +} // namespace anonymous + +bool Collada::BuildFloatAnimation(ParamFloat* result, + FCDAnimated* animated, + const char* qualifier, + ParamFloat* animation_input, + float output_scale, + float default_value) { + if (animated != NULL) { + FCDAnimationCurve* fcd_curve = animated->FindCurve(qualifier); + if (fcd_curve != NULL) { + FunctionEval* function_eval = pack_->Create<FunctionEval>(); + BindParams(function_eval, FunctionEval::kInputParamName, animation_input); + + Curve* curve = pack_->Create<Curve>(); + function_eval->set_function_object(curve); + + curve->set_pre_infinity(ConvertInfinity(fcd_curve->GetPreInfinity())); + curve->set_post_infinity(ConvertInfinity(fcd_curve->GetPostInfinity())); + + for (int i = 0; i != fcd_curve->GetKeyCount(); ++i) { + FCDAnimationKey* fcd_key = fcd_curve->GetKey(i); + switch (fcd_key->interpolation) { + case FUDaeInterpolation::STEP: + BuildStepKey(curve, fcd_key, output_scale); + break; + case FUDaeInterpolation::BEZIER: + BuildBezierKey(curve, static_cast<FCDAnimationKeyBezier*>(fcd_key), + output_scale); + break; + default: + BuildLinearKey(curve, fcd_key, output_scale); + break; + } + } + + BindParams(result, function_eval, FunctionEval::kOutputParamName); + return true; + } + } + + result->set_value(default_value * output_scale); + return false; +} + +bool Collada::BuildFloat3Animation(ParamFloat3* result, FCDAnimated* animated, + ParamFloat* animation_input, + const Float3& default_value) { + ParamOp3FloatsToFloat3* to_float3 = + pack_->Create<ParamOp3FloatsToFloat3>(); + + static const char* const kQualifiers[3] = { ".X", ".Y", ".Z" }; + static const char* const kInputs[3] = { + ParamOp3FloatsToFloat3::kInput0ParamName, + ParamOp3FloatsToFloat3::kInput1ParamName, + ParamOp3FloatsToFloat3::kInput2ParamName, + }; + bool any_animated = false; + for (int i = 0; i != 3; ++i) { + ParamFloat* to_float3_input = to_float3->GetParam<ParamFloat>(kInputs[i]); + any_animated |= BuildFloatAnimation(to_float3_input, + animated, kQualifiers[i], + animation_input, 1.0f, + default_value[i]); + } + + if (any_animated) { + BindParams(result, to_float3, ParamOp3FloatsToFloat3::kOutputParamName); + return true; + } else { + pack_->RemoveObject(to_float3); + result->set_value(default_value); + return false; + } +} + +ParamMatrix4* Collada::BuildComposition(FCDTMatrix* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input) { + Matrix4Composition* composition = pack_->Create<Matrix4Composition>(); + Matrix4 matrix = FMMatrix44ToMatrix4(transform->ToMatrix()); + + ParamOp16FloatsToMatrix4* to_matrix4 = + pack_->Create<ParamOp16FloatsToMatrix4>(); + bool any_animated = false; + for (int i = 0; i != 16; ++i) { + int row = i / 4; + int column = i % 4; + + std::stringstream input_name; + input_name << "input" << i; + ParamFloat* to_matrix4_input = to_matrix4->GetParam<ParamFloat>( + input_name.str().c_str()); + + std::stringstream qualifier; + qualifier << "(" << row << ")" << "(" << column << ")"; + any_animated |= BuildFloatAnimation(to_matrix4_input, + transform->GetAnimated(), + qualifier.str().c_str(), + animation_input, 1.0f, + matrix[row][column]); + } + + if (any_animated) { + BindParams(composition, Matrix4Composition::kLocalMatrixParamName, + to_matrix4, ParamOp16FloatsToMatrix4::kOutputParamName); + } else { + pack_->RemoveObject(to_matrix4); + composition->set_local_matrix(matrix); + } + + BindParams(composition, Matrix4Composition::kInputMatrixParamName, + input_matrix); + return composition->GetParam<ParamMatrix4>( + Matrix4Composition::kOutputMatrixParamName); +} + +ParamMatrix4* Collada::BuildComposition(const Matrix4& matrix, + ParamMatrix4* input_matrix) { + Matrix4Composition* composition = pack_->Create<Matrix4Composition>(); + composition->set_local_matrix(matrix); + BindParams(composition, Matrix4Composition::kInputMatrixParamName, + input_matrix); + return composition->GetParam<ParamMatrix4>( + Matrix4Composition::kOutputMatrixParamName); +} + +ParamMatrix4* Collada::BuildTranslation(FCDTTranslation* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input) { + Matrix4Translation* translation = pack_->Create<Matrix4Translation>(); + + ParamFloat3* animated_param = translation->GetParam<ParamFloat3>( + Matrix4Translation::kTranslationParamName); + Vector3 default_value = FMVector3ToVector3(transform->GetTranslation()); + BuildFloat3Animation(animated_param, transform->GetAnimated(), + animation_input, Float3(default_value)); + + BindParams(translation, Matrix4Composition::kInputMatrixParamName, + input_matrix); + return translation->GetParam<ParamMatrix4>( + Matrix4Composition::kOutputMatrixParamName); +} + +ParamMatrix4* Collada::BuildRotation(FCDTRotation* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input) { + Matrix4AxisRotation* rotation = pack_->Create<Matrix4AxisRotation>(); + + ParamFloat3* animated_axis = rotation->GetParam<ParamFloat3>( + Matrix4AxisRotation::kAxisParamName); + Vector3 default_axis = FMVector3ToVector3(transform->GetAxis()); + BuildFloat3Animation(animated_axis, transform->GetAnimated(), + animation_input, Float3(default_axis)); + + ParamFloat* animated_angle = rotation->GetParam<ParamFloat>( + Matrix4AxisRotation::kAngleParamName); + float default_angle = transform->GetAngle(); + BuildFloatAnimation(animated_angle, + transform->GetAnimated(), + ".ANGLE", + animation_input, + o3d::kPi / 180.0f, + default_angle); + + BindParams(rotation, Matrix4Composition::kInputMatrixParamName, input_matrix); + return rotation->GetParam<ParamMatrix4>( + Matrix4Composition::kOutputMatrixParamName); +} + +ParamMatrix4* Collada::BuildScaling(FCDTScale* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input) { + Matrix4Scale* scaling = pack_->Create<Matrix4Scale>(); + + ParamFloat3* animated_param = scaling->GetParam<ParamFloat3>( + Matrix4Scale::kScaleParamName); + Vector3 default_value = FMVector3ToVector3(transform->GetScale()); + BuildFloat3Animation(animated_param, transform->GetAnimated(), + animation_input, Float3(default_value)); + + BindParams(scaling, Matrix4Composition::kInputMatrixParamName, input_matrix); + return scaling->GetParam<ParamMatrix4>( + Matrix4Composition::kOutputMatrixParamName); +} + +// Builds a Transform node corresponding to the transform elements of +// a given node. All transformations (rotation, translation, scale, +// etc) are collapsed into a single Transform. +// Parameters: +// node: The FCollada node whose transforms are replicated. +// Return value: +// The new Transform. +Transform* Collada::BuildTransform(FCDSceneNode* node, + Transform* parent_transform, + ParamFloat* animation_input) { + const wchar_t* name = node->GetName().c_str(); + + String name_utf8 = WideToUTF8(name); + + Transform* transform = pack_->Create<Transform>(); + transform->set_name(name_utf8); + transform->SetParent(parent_transform); + + bool any_animated = false; + for (size_t i = 0; i < node->GetTransformCount(); i++) { + FCDTransform* fcd_transform = node->GetTransform(i); + any_animated |= fcd_transform->IsAnimated(); + } + + if (any_animated) { + // At least one of the collada transforms is animated so construct the + // transform hierarchy and connect its output to the local_matrix for + // this node. + ParamMatrix4* input_matrix = NULL; + for (size_t i = 0; i < node->GetTransformCount(); i++) { + FCDTransform* fcd_transform = node->GetTransform(i); + switch (fcd_transform->GetType()) { + case FCDTransform::MATRIX : + input_matrix = BuildComposition( + static_cast<FCDTMatrix*>(fcd_transform), + input_matrix, animation_input); + break; + case FCDTransform::TRANSLATION: + input_matrix = BuildTranslation( + static_cast<FCDTTranslation*>(fcd_transform), + input_matrix, animation_input); + break; + case FCDTransform::ROTATION: + input_matrix = BuildRotation( + static_cast<FCDTRotation*>(fcd_transform), + input_matrix, animation_input); + break; + case FCDTransform::SCALE: + input_matrix = BuildScaling( + static_cast<FCDTScale*>(fcd_transform), + input_matrix, animation_input); + break; + default: + input_matrix = BuildComposition( + FMMatrix44ToMatrix4(fcd_transform->ToMatrix()), + input_matrix); + break; + } + } + + ParamMatrix4* local_matrix_param = transform->GetParam<ParamMatrix4>( + Transform::kLocalMatrixParamName); + local_matrix_param->Bind(input_matrix); + } else { + // None of collada transforms are animated so just compute the + // overall transform and set it as the value of the local_matrix + // for this node. This saves memory and improves performance but + // more importantly it will allow JavaScript code to set the value of + // the local_matrix directly without first having to unbind it. + Matrix4 local_matrix(Matrix4::identity()); + for (size_t i = 0; i < node->GetTransformCount(); i++) { + FCDTransform* fcd_transform = node->GetTransform(i); + local_matrix *= FMMatrix44ToMatrix4(fcd_transform->ToMatrix()); + } + transform->set_local_matrix(local_matrix); + } + + return transform; +} + +NodeInstance *Collada::CreateInstanceTree(FCDSceneNode *node) { + NodeInstance *instance = new NodeInstance(node); + NodeInstance::NodeInstanceList &children = instance->children(); + for (size_t i = 0; i < node->GetChildrenCount(); ++i) { + FCDSceneNode *child_node = node->GetChild(i); + NodeInstance *child_instance = CreateInstanceTree(child_node); + children.push_back(child_instance); + } + return instance; +} + +NodeInstance *NodeInstance::FindNodeInTree(FCDSceneNode *node) { + if (node == node_) + return this; + for (unsigned int i = 0; i < children_.size(); ++i) { + NodeInstance *instance = children_[i]->FindNodeInTree(node); + if (instance) + return instance; + } + return NULL; +} + +NodeInstance *Collada::FindNodeInstance(FCDSceneNode *node) { + // First try the fast path, in the case where the node is not instanced more + // than once. + NodeInstance *instance = FindNodeInstanceFast(node); + if (!instance) { + // If it fails, look in the whole instance tree. + instance = instance_root_->FindNodeInTree(node); + } + return instance; +} + +NodeInstance *Collada::FindNodeInstanceFast(FCDSceneNode *node) { + if (node == instance_root_->node()) + return instance_root_; + // If the node is instanced more than once, fail. + if (node->GetParentCount() != 1) + return NULL; + NodeInstance *parent_instance = FindNodeInstanceFast(node->GetParent(0)); + // Look for self in parent's childrens. + return parent_instance->FindNodeShallow(node); +} + +// Recursively imports a tree of nodes from FCollada, rooted at the +// given node, into the O3D scene. +// Parameters: +// instance: The root NodeInstance from which to import. +// parent_transform: The parent Transform under which the tree will be placed. +// animation_input: The float parameter used as the input to the animation +// (if present). This is usually time. This can be null +// if there is no animation. +void Collada::ImportTree(NodeInstance *instance, + Transform* parent_transform, + ParamFloat* animation_input) { + FCDSceneNode *node = instance->node(); + Transform* transform = BuildTransform(node, parent_transform, + animation_input); + instance->set_transform(transform); + + // recursively import the rest of the nodes in the tree + const NodeInstance::NodeInstanceList &children = instance->children(); + for (size_t i = 0; i < children.size(); ++i) { + ImportTree(children[i], transform, animation_input); + } +} + +void Collada::ImportTreeInstances(FCDocument* doc, + NodeInstance *node_instance) { + FCDSceneNode *node = node_instance->node(); + // recursively import the rest of the nodes in the tree + const NodeInstance::NodeInstanceList &children = node_instance->children(); + for (size_t i = 0; i < children.size(); ++i) { + ImportTreeInstances(doc, children[i]); + } + + Transform* transform = node_instance->transform(); + for (size_t i = 0; i < node->GetInstanceCount(); ++i) { + FCDEntityInstance* instance = node->GetInstance(i); + FCDCamera* camera(NULL); + FCDGeometryInstance* geom_instance(NULL); + + LOG_ASSERT(instance != 0); + // Import each node based on what kind of entity it is + // TODO: add more entity types as they are supported + switch (instance->GetEntityType()) { + case FCDEntity::CAMERA: + // camera entity + camera = down_cast<FCDCamera*>(instance->GetEntity()); + BuildCamera(doc, camera, transform, node); + break; + case FCDEntity::GEOMETRY: { + // geometry entity + geom_instance = static_cast<FCDGeometryInstance*>(instance); + Shape* shape = GetShape(doc, geom_instance); + if (shape) { + transform->AddShape(shape); + } + break; + } + case FCDEntity::CONTROLLER: { + FCDControllerInstance* controller_instance = + static_cast<FCDControllerInstance*> (instance); + Shape* shape = GetSkinnedShape(doc, controller_instance, node_instance); + if (shape) { + transform->AddShape(shape); + } + break; + } + default: + // do nothing + break; + } + } +} + +// Converts an FCollada vertex attribute semantic into an O3D +// vertex attribute semantic. Returns the O3D semantic, or +// UNKNOWN_SEMANTIC on failure. +static Stream::Semantic C2G3DSemantic(FUDaeGeometryInput::Semantic semantic) { + switch (semantic) { + case FUDaeGeometryInput::POSITION: return Stream::POSITION; + case FUDaeGeometryInput::VERTEX: return Stream::POSITION; + case FUDaeGeometryInput::NORMAL: return Stream::NORMAL; + case FUDaeGeometryInput::TEXTANGENT: return Stream::TANGENT; + case FUDaeGeometryInput::TEXBINORMAL: return Stream::BINORMAL; + case FUDaeGeometryInput::TEXCOORD: return Stream::TEXCOORD; + case FUDaeGeometryInput::COLOR: return Stream::COLOR; + default: return Stream::UNKNOWN_SEMANTIC; + } +} + +// Imports information from a Collada camera node and places it in Params of +// the transform corresponding to the node it's parented under. +// Parameters: +// doc: The FCollada document from which to import nodes. +// camera: The FCDCamera node in question +// transform: The O3D Transform on which the camera parameters +// will be created. It cannot be NULL. +// parent_node: The FCDSceneNode that the parent transform was created +// from. This node is used to extract possible LookAt +// elements. It cannot be NULL. +void Collada::BuildCamera(FCDocument* doc, + FCDCamera* camera, + Transform* transform, + FCDSceneNode* parent_node) { + LOG_ASSERT(doc && camera && transform && parent_node); + + // Create a transform node for the camera + String camera_name = WideToUTF8(camera->GetName().c_str()); + + // Tag this node as a camera + ParamString* param_tag = transform->CreateParam<ParamString>( + COLLADA_STRING_CONSTANT("tags")); + + // Add more tags when required + param_tag->set_value("camera"); + + // Create the rest of the params + + // Projection type: either 'orthographic' or 'perspective' + ParamString* param_proj_type = + transform->CreateParam<ParamString>( + COLLADA_STRING_CONSTANT("projectionType")); + + // Aspect ratio + float camera_aspect_ratio; + ParamFloat* param_proj_aspect_ratio = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("projectionAspectRatio")); + + // Near/far z-planes + float camera_near_z, camera_far_z; + ParamFloat* param_proj_nearz = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("projectionNearZ")); + ParamFloat* param_proj_farz = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("projectionFarZ")); + + // Calculate shared params + camera_near_z = camera->GetNearZ(); + camera_far_z = camera->GetFarZ(); + param_proj_nearz->set_value(camera_near_z); + param_proj_farz->set_value(camera_far_z); + + if (camera->GetProjectionType() == FCDCamera::ORTHOGRAPHIC) { + // Orthographic projection + param_proj_type->set_value("orthographic"); + + // Additional params + // Horizontal and vertical magnifications + ParamFloat* param_proj_mag_x = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("projectionMagX")); + ParamFloat* param_proj_mag_y = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("projectionMagY")); + + // Find aspect ratio and magnifications + float camera_mag_x, camera_mag_y; + if (camera->HasHorizontalMag() && camera->HasVerticalMag()) + camera_aspect_ratio = camera->GetMagY() / camera->GetMagX(); + else + camera_aspect_ratio = camera->GetAspectRatio(); + + if (camera->HasHorizontalMag()) + camera_mag_x = camera->GetMagX(); + else + camera_mag_x = camera->GetMagY() * camera_aspect_ratio; + + if (camera->HasVerticalMag()) + camera_mag_y = camera->GetMagY(); + else + camera_mag_y = camera->GetMagX() / camera_aspect_ratio; + + // Set the values of the additional params + param_proj_mag_x->set_value(camera_mag_x); + param_proj_mag_y->set_value(camera_mag_y); + param_proj_aspect_ratio->set_value(camera_aspect_ratio); + } else if (camera->GetProjectionType() == FCDCamera::PERSPECTIVE) { + // Perspective projection + param_proj_type->set_value("perspective"); + + // Additional params + // Vertical field of view + ParamFloat* param_proj_fov_y = + transform->CreateParam<ParamFloat>( + COLLADA_STRING_CONSTANT("perspectiveFovY")); + + // Find aspect ratio and vertical FOV + float camera_fov_y; + if (camera->HasHorizontalFov() && camera->HasVerticalFov()) + camera_aspect_ratio = camera->GetFovY() / camera->GetFovX(); + else + camera_aspect_ratio = camera->GetAspectRatio(); + + if (camera->HasVerticalFov()) + camera_fov_y = camera->GetFovY(); + else + camera_fov_y = camera->GetFovX() / camera_aspect_ratio; + + // Set the values of the additional params + param_proj_fov_y->set_value(camera_fov_y); + param_proj_aspect_ratio->set_value(camera_aspect_ratio); + } + + // Search the FCDSceneNode for a LookAt element, extract the eye, target, + // and up values and store them as Params on the Transform. If multiple + // LookAt elements are defined under the parent node, we only pick the first + // one. + for (size_t i = 0; i < parent_node->GetTransformCount(); i++) { + FCDTransform* transform_object = parent_node->GetTransform(i); + if (transform_object->GetType() == FCDTransform::LOOKAT) { + FCDTLookAt* look_at = static_cast<FCDTLookAt*>(transform_object); + FMVector3 position = look_at->GetPosition(); + FMVector3 target = look_at->GetTarget(); + FMVector3 up = look_at->GetUp(); + + // Get the world matrix of the transform above the camera transform. + // We use this value to transform the eye, target and up to the world + // coordinate system so that they can be used directly to make a camera + // view matrix. + Matrix4 parent_world = Matrix4::identity(); + if (transform->parent()) + parent_world = transform->parent()->GetUpdatedWorldMatrix(); + + ParamFloat3* param_eye_position = + transform->CreateParam<ParamFloat3>( + COLLADA_STRING_CONSTANT("eyePosition")); + Vector4 world_eye = parent_world * Point3(position.x, + position.y, + position.z); + param_eye_position->set_value(Float3(world_eye.getX(), + world_eye.getY(), + world_eye.getZ())); + + ParamFloat3* param_target_position = + transform->CreateParam<ParamFloat3>( + COLLADA_STRING_CONSTANT("targetPosition")); + Vector4 world_target = parent_world * Point3(target.x, + target.y, + target.z); + param_target_position->set_value(Float3(world_target.getX(), + world_target.getY(), + world_target.getZ())); + + ParamFloat3* param_up_vector = + transform->CreateParam<ParamFloat3>( + COLLADA_STRING_CONSTANT("upVector")); + Vector4 world_up = parent_world * Vector4(up.x, up.y, up.z, 0.0f); + param_up_vector->set_value(Float3(world_up.getX(), + world_up.getY(), + world_up.getZ())); + + break; + } + } + +} + +Shape* Collada::GetShape(FCDocument* doc, + FCDGeometryInstance* geom_instance) { + Shape* shape = NULL; + FCDGeometry* geom = static_cast<FCDGeometry*>(geom_instance->GetEntity()); + if (geom) { + fm::string geom_id = geom->GetDaeId(); + shape = shapes_[geom_id.c_str()]; + if (!shape) { + shape = BuildShape(doc, geom_instance, geom, NULL); + if (!shape) + return NULL; + shapes_[geom_id.c_str()] = shape; + } + } + return shape; +} + +Shape* Collada::GetSkinnedShape(FCDocument* doc, + FCDControllerInstance* instance, + NodeInstance *parent_node_instance) { + Shape* shape = NULL; + FCDController* controller = + static_cast<FCDController*>(instance->GetEntity()); + if (controller && controller->IsSkin()) { + fm::string id = controller->GetDaeId(); + shape = skinned_shapes_[id.c_str()]; + if (!shape) { + shape = BuildSkinnedShape(doc, instance, parent_node_instance); + if (!shape) + return NULL; + skinned_shapes_[id.c_str()] = shape; + } + } + return shape; +} + +// Builds an O3D shape node corresponding to a given FCollada geometry +// instance. +// Parameters: +// doc: The FCollada document from which to import nodes. +// geom_instance: The FCollada geometry node to be converted. +Shape* Collada::BuildShape(FCDocument* doc, + FCDGeometryInstance* geom_instance, + FCDGeometry* geom, + TranslationMap* translationMap) { + Shape* shape = NULL; + LOG_ASSERT(doc && geom_instance && geom); + if (geom && geom->IsMesh()) { + String geom_name = WideToUTF8(geom->GetName().c_str()); + shape = pack_->Create<Shape>(); + shape->set_name(geom_name); + FCDGeometryMesh* mesh = geom->GetMesh(); + LOG_ASSERT(mesh); + FCDGeometryPolygonsTools::Triangulate(mesh); + FCDGeometryPolygonsTools::GenerateUniqueIndices(mesh, NULL, translationMap); + size_t num_polygons = mesh->GetPolygonsCount(); + size_t num_indices = mesh->GetFaceVertexCount(); + if (num_polygons <= 0 || num_indices <= 0) return NULL; + FCDGeometrySource* pos_source = + mesh->FindSourceByType(FUDaeGeometryInput::POSITION); + if (pos_source == NULL) return NULL; + size_t num_vertices = pos_source->GetValueCount(); + size_t num_sources = mesh->GetSourceCount(); + + // Create vertex streams corresponding to the COLLADA sources. + // These streams are common to all polygon sets in this mesh. + // The BuildSkinnedShape code assumes this so if you change it you'll need + // to fix BuildSkinnedShape. + StreamBank* stream_bank = pack_->Create<StreamBank>(); + stream_bank->set_name(geom_name); + + int semantic_counts[Stream::TEXCOORD + 1] = { 0, }; + + scoped_array<Field*> fields(new Field*[num_sources]); + + VertexBuffer* vertex_buffer = pack_->Create<VertexBuffer>(); + vertex_buffer->set_name(geom_name); + + // first create all the fields. + for (size_t s = 0; s < num_sources; ++s) { + FCDGeometrySource* source = mesh->GetSource(s); + LOG_ASSERT(source); + Stream::Semantic semantic = C2G3DSemantic(source->GetType()); + LOG_ASSERT(semantic <= Stream::TEXCOORD); + if (semantic == Stream::UNKNOWN_SEMANTIC) continue; + + // The call to GenerateUniqueIndices() above should have made + // all sources the same length. + LOG_ASSERT(source->GetValueCount() == num_vertices); + + int stride = source->GetStride(); + if (semantic == Stream::COLOR && stride == 4) { + fields[s] = vertex_buffer->CreateField(UByteNField::GetApparentClass(), + stride); + } else { + fields[s] = vertex_buffer->CreateField(FloatField::GetApparentClass(), + stride); + } + } + + if (!vertex_buffer->AllocateElements(num_vertices)) { + O3D_ERROR(service_locator_) << "Failed to allocate vertex buffer"; + return NULL; + } + + for (size_t s = 0; s < num_sources; ++s) { + FCDGeometrySource* source = mesh->GetSource(s); + LOG_ASSERT(source); + Stream::Semantic semantic = C2G3DSemantic(source->GetType()); + LOG_ASSERT(semantic <= Stream::TEXCOORD); + if (semantic == Stream::UNKNOWN_SEMANTIC) continue; + int stride = source->GetStride(); + + const float* source_data = source->GetData(); + if (semantic == Stream::TANGENT || semantic == Stream::BINORMAL) { + // FCollada uses the convention that the tangent points + // along -u and the binormal along -v in model space. + // Convert to the more common convention where the tangent + // points along +u and the binormal along +v. This is what, for + // example, tools that convert height maps to normal maps tend to + // assume. It is also what the O3D shaders assume. + size_t num_values = source->GetDataCount(); + scoped_array<float>values(new float[num_values]); + for (size_t i = 0; i < num_values ; ++i) { + values[i] = -source_data[i]; + } + fields[s]->SetFromFloats(&values[0], stride, 0, num_vertices); + } else { + fields[s]->SetFromFloats(source_data, stride, 0, num_vertices); + } + + stream_bank->SetVertexStream(semantic, semantic_counts[semantic], + fields[s], 0); + // NOTE: This doesn't really seem like the correct thing to do but I'm not + // sure we have enough info to do the correct thing. The issue is we + // need to connect these streams to the shader, the shader needs to know + // which streams go with which varying parameters but for the standard + // collada materials I don't think any such information is available. + ++semantic_counts[semantic]; + } + + for (size_t p = 0; p < num_polygons; ++p) { + FCDGeometryPolygons* polys = mesh->GetPolygons(p); + FCDGeometryPolygonsInput* input = polys->GetInput(0); + + size_t size = input->GetIndexCount(); + size_t vertices_per_primitive = 0; + Primitive::PrimitiveType primitive_type; + switch (polys->GetPrimitiveType()) { + case FCDGeometryPolygons::POLYGONS: + vertices_per_primitive = 3; + primitive_type = Primitive::TRIANGLELIST; + break; + case FCDGeometryPolygons::LINES: + vertices_per_primitive = 2; + primitive_type = Primitive::LINELIST; + break; + default: + // unsupported geometry type; skip it + continue; + } + + // If there are no vertices, don't make this primitive. + if (size == 0) continue; + + // If we don't have a multiple of the verts-per-primitive, bail now. + if (size % vertices_per_primitive != 0) { + O3D_ERROR(service_locator_) << "Geometry \"" << geom_name + << "\" contains " << size + << " vertices, which is not a multiple of " + << vertices_per_primitive << "; skipped"; + continue; + } + + // Get the material for this polygon set. + Material* material = NULL; + FCDMaterialInstance* mat_instance = geom_instance->FindMaterialInstance( + polys->GetMaterialSemantic()); + if (mat_instance) { + FCDMaterial* collada_material = mat_instance->GetMaterial(); + material = BuildMaterial(doc, collada_material); + } + if (!material) { + material = GetDummyMaterial(); + } + // Create an index buffer for this group of polygons. + + String primitive_name(geom_name + "|" + material->name()); + + IndexBuffer* indexBuffer = pack_->Create<IndexBuffer>(); + indexBuffer->set_name(primitive_name); + if (!indexBuffer->AllocateElements(size)) { + O3D_ERROR(service_locator_) << "Failed to allocate index buffer."; + return NULL; + } + indexBuffer->index_field()->SetFromUInt32s(input->GetIndices(), 1, 0, + size); + + // Create a primitive for this group of polygons. + Primitive* primitive = pack_->Create<Primitive>(); + primitive->set_name(primitive_name); + + primitive->set_material(material); + + primitive->SetOwner(shape); + primitive->set_primitive_type(primitive_type); + size_t num_prims = size / vertices_per_primitive; + primitive->set_number_primitives(static_cast<unsigned int>(num_prims)); + primitive->set_number_vertices(static_cast<unsigned int>(num_vertices)); + + // Set the index buffer for this primitive. + primitive->set_index_buffer(indexBuffer); + + // Set the vertex streams for this primitive to the common set for this + // mesh. + primitive->set_stream_bank(stream_bank); + } + } + return shape; +} + +Shape* Collada::BuildSkinnedShape(FCDocument* doc, + FCDControllerInstance* instance, + NodeInstance *parent_node_instance) { + // TODO: Handle chained controllers. Morph->Skin->... + Shape* shape = NULL; + LOG_ASSERT(doc && instance); + FCDController* controller = + static_cast<FCDController*>(instance->GetEntity()); + if (controller && controller->IsSkin()) { + FCDSkinController* skin_controller = controller->GetSkinController(); + TranslationMap translationMap; + shape = BuildShape(doc, + instance, + controller->GetBaseGeometry(), + &translationMap); + if (!shape) { + return NULL; + } + + // Convert the translation table to new->old map. + size_t num_vertices = 0; + std::vector<unsigned> new_to_old_indices; + { + // walk the map once to figure out the number of vertices. + TranslationMap::iterator end = translationMap.end(); + for (TranslationMap::iterator it = translationMap.begin(); + it != end; + ++it) { + num_vertices += it->second.size(); + } + + // Init our array to UINT_MAX so we can check for collisions + new_to_old_indices.resize(num_vertices, UINT_MAX); + + // walk the map again and fill out our remap table. + for (TranslationMap::iterator it = translationMap.begin(); + it != end; + ++it) { + const UInt32List& intlist = it->second; + for (size_t gg = 0; gg < intlist.size(); ++gg) { + unsigned new_index = intlist[gg]; + LOG_ASSERT(new_to_old_indices.at(new_index) == UINT_MAX); + new_to_old_indices[new_index] = it->first; + } + } + } + + // There's a BIG assumption here. We assume the first primitive on the shape + // has vertex buffers that are shared on all the primitives under this shape + // such that we only need to copy the first primitive's vertex buffers to + // skin everything. This is actually what was BuildShape was doing at the + // time this code was written. + const ElementRefArray& elements = shape->GetElementRefs(); + if (elements.empty() || !elements[0]->IsA(Primitive::GetApparentClass())) { + return NULL; + } + Primitive* primitive = down_cast<Primitive*>(elements[0].Get()); + + String controller_name = WideToUTF8(controller->GetName().c_str()); + + ParamArray* matrices = pack_->Create<ParamArray>(); + Skin* skin = pack_->Create<Skin>(); + skin->set_name(controller_name); + SkinEval* skin_eval = pack_->Create<SkinEval>(); + skin_eval->set_name(controller_name); + + skin_eval->set_skin(skin); + skin_eval->set_matrices(matrices); + + // Bind bones to matrices + size_t num_bones = instance->GetJointCount(); + if (num_bones > 0) { + matrices->CreateParam<ParamMatrix4>(num_bones - 1); + for (size_t ii = 0; ii < num_bones; ++ii) { + FCDSceneNode* node = instance->GetJoint(ii); + LOG_ASSERT(node); + // Note: in case of instancing, the intended instance is ill-defined, + // but that is a problem in the Collada document itself. So we'll assume + // the file is somewhat well defined. + // First we try the single instance case. + NodeInstance *node_instance = FindNodeInstanceFast(node); + if (!node_instance) { + // Second we try nodes underneath the same parent as the controller + // instance. Max and Maya seem to do that. + node_instance = parent_node_instance->FindNodeInTree(node); + } + if (!node_instance) { + // Third we try in the entire tree. + node_instance = instance_root_->FindNodeInTree(node); + } + if (!node_instance) { + String bone_name = WideToUTF8(node->GetName().c_str()); + O3D_ERROR(service_locator_) + << "Could not find node instance for bone " << bone_name.c_str(); + continue; + } + Transform* bone = node_instance->transform(); + LOG_ASSERT(bone); + matrices->GetUntypedParam(ii)->Bind( + bone->GetUntypedParam(Transform::kWorldMatrixParamName)); + } + } + + Matrix4 bind_shape_matrix( + FMMatrix44ToMatrix4(skin_controller->GetBindShapeTransform())); + Matrix4 inverse_bind_shape_matrix(inverse(bind_shape_matrix)); + + // Get the bind pose inverse matrices + LOG_ASSERT(num_bones == skin_controller->GetJointCount()); + for (size_t ii = 0; ii < num_bones; ++ii) { + FCDSkinControllerJoint* joint = skin_controller->GetJoint(ii); + skin->SetInverseBindPoseMatrix( + ii, FMMatrix44ToMatrix4(joint->GetBindPoseInverse())); + } + + // Get Influences. + for (size_t ii = 0; ii < num_vertices; ++ii) { + unsigned old_index = new_to_old_indices[ii]; + FCDSkinControllerVertex* vertex = + skin_controller->GetVertexInfluence(old_index); + Skin::Influences influences; + size_t num_influences = vertex->GetPairCount(); + for (size_t jj = 0; jj < num_influences; ++jj) { + FCDJointWeightPair* weight_pair = vertex->GetPair(jj); + influences.push_back(Skin::Influence(weight_pair->jointIndex, + weight_pair->weight)); + } + skin->SetVertexInfluences(ii, influences); + } + + Matrix4 matrix(bind_shape_matrix); + + // Copy shape->primitive buffers. + StreamBank* stream_bank = primitive->stream_bank(); + SourceBuffer* buffer = pack_->Create<SourceBuffer>(); + const StreamParamVector& source_stream_params = + stream_bank->vertex_stream_params(); + std::vector<Field*> new_fields(source_stream_params.size(), NULL); + // first make all the fields. + for (unsigned ii = 0; ii < source_stream_params.size(); ++ii) { + const Stream& source_stream = source_stream_params[ii]->stream(); + const Field& field = source_stream.field(); + if (field.IsA(FloatField::GetApparentClass()) && + (field.num_components() == 3 || + field.num_components() == 4)) { + switch (source_stream.semantic()) { + case Stream::POSITION: + case Stream::NORMAL: + case Stream::BINORMAL: + case Stream::TANGENT: { + unsigned num_source_components = field.num_components(); + unsigned num_source_vertices = source_stream.GetMaxVertices(); + if (num_source_vertices != num_vertices) { + O3D_ERROR(service_locator_) + << "Number of vertices in stream_bank '" + << stream_bank->name().c_str() + << "' does not equal the number of vertices in the Skin '" + << skin->name().c_str() << "'"; + return NULL; + } + new_fields[ii] = buffer->CreateField(FloatField::GetApparentClass(), + num_source_components); + } + } + } + } + + if (!buffer->AllocateElements(num_vertices)) { + O3D_ERROR(service_locator_) + << "Failed to allocate destination vertex buffer"; + return NULL; + } + + for (unsigned ii = 0; ii < source_stream_params.size(); ++ii) { + const Stream& source_stream = source_stream_params[ii]->stream(); + const Field& field = source_stream.field(); + if (field.IsA(FloatField::GetApparentClass()) && + (field.num_components() == 3 || + field.num_components() == 4)) { + switch (source_stream.semantic()) { + case Stream::POSITION: + case Stream::NORMAL: + case Stream::BINORMAL: + case Stream::TANGENT: { + unsigned num_source_components = field.num_components(); + Field* new_field = new_fields[ii]; + + std::vector<float> data(num_vertices * num_source_components); + field.GetAsFloats(0, &data[0], num_source_components, + num_vertices); + // TODO: Remove this matrix multiply. I don't think it is + // needed. + for (unsigned vv = 0; vv < num_vertices; ++vv) { + float* values = &data[vv * num_source_components]; + switch (field.num_components()) { + case 3: { + if (source_stream.semantic() == Stream::POSITION) { + Vector4 result(matrix * Point3(values[0], + values[1], + values[2])); + values[0] = result.getElem(0); + values[1] = result.getElem(1); + values[2] = result.getElem(2); + } else { + Vector4 result(matrix * Vector3(values[0], + values[1], + values[2])); + values[0] = result.getElem(0); + values[1] = result.getElem(1); + values[2] = result.getElem(2); + } + break; + } + case 4: { + Vector4 result(matrix * Vector4(values[0], + values[1], + values[2], + values[3])); + values[0] = result.getElem(0); + values[1] = result.getElem(1); + values[2] = result.getElem(2); + values[3] = result.getElem(3); + break; + } + } + } + new_field->SetFromFloats(&data[0], num_source_components, 0, + num_vertices); + // Bind streams + skin_eval->SetVertexStream(source_stream.semantic(), + source_stream.semantic_index(), + new_field, + 0); + stream_bank->BindStream(skin_eval, + source_stream.semantic(), + source_stream.semantic_index()); + break; + } + } + } + } + } + return shape; +} + +Texture* Collada::BuildTextureFromImage(FCDImage* image) { + const fstring filename = image->GetFilename(); + Texture* tex = textures_[filename.c_str()]; + if (!tex) { + FilePath file_path = WideToFilePath(filename.c_str()); + FilePath uri = file_path; + + std::string tempfile; + if (collada_zip_archive_) { + // If we're getting data from a zip archive, then we just strip + // the "/" from the beginning of the name, since that represents + // the root of the archive, and we can assume all the paths in + // the archive are relative to that. + if (uri.value()[0] == FILE_PATH_LITERAL('/')) { + uri = FilePath(uri.value().substr(1)); + } + // NOTE: We have the opportunity to simply extract a memory + // buffer for the image data here, but currently the image loaders expect + // to read a file, so we write out a temp file... + + // filename_utf8 points to the name of the file inside the archive + // (it doesn't actually live on the filesystem so we make a temp file) + if (collada_zip_archive_->GetTempFileFromFile(FilePathToUTF8(file_path), + &tempfile)) { + file_path = UTF8ToFilePath(tempfile); + } + } else { + GetRelativePathIfPossible(base_path_, uri, &uri); + } + + tex = Texture::Ref( + pack_->CreateTextureFromFile(FilePathToUTF8(uri), + file_path, + Bitmap::UNKNOWN, + options_.generate_mipmaps)); + if (tex) { + const fstring name(image->GetName()); + tex->set_name(WideToUTF8(name.c_str())); + } + + if (options_.keep_original_data) { + // Cache the original data by URI so we can recover it later. + std::string contents; + file_util::ReadFileToString(file_path, &contents); + original_data_[uri] = contents; + } + + if (tempfile.size() > 0) ZipArchive::DeleteFile(tempfile); + textures_[filename.c_str()] = tex; + } + return tex; +} + +Texture* Collada::BuildTexture(FCDEffectParameterSurface* surface) { + // Create the texture. + Texture* tex = NULL; + if (surface->GetImageCount() > 0) { + FCDImage* image = surface->GetImage(0); + tex = BuildTextureFromImage(image); + } + return tex; +} + +// Sets an O3D parameter value from a given FCollada effect parameter. +// instance. +// Parameters: +// param_object: The param_object whose parameter is to be modified. +// param_name: The name of the parameter to be set. +// fc_param: The FCollada effect parameter whose value is used. +// Returns true on success. +bool Collada::SetParamFromFCEffectParam(ParamObject* param_object, + const String ¶m_name, + FCDEffectParameter *fc_param) { + if (!param_object || !fc_param) return false; + FCDEffectParameter::Type type = fc_param->GetType(); + bool rc = false; + if (type == FCDEffectParameter::FLOAT) { + FCDEffectParameterFloat* float_param = + static_cast<FCDEffectParameterFloat*>(fc_param); + ParamFloat* param = param_object->CreateParam<ParamFloat>(param_name); + if (param) { + param->set_value(float_param->GetValue()); + rc = true; + } + } else if (type == FCDEffectParameter::FLOAT2) { + FCDEffectParameterFloat2* float2_param = + static_cast<FCDEffectParameterFloat2*>(fc_param); + FMVector2 v = float2_param->GetValue(); + ParamFloat2* param = param_object->CreateParam<ParamFloat2>(param_name); + if (param) { + Float2 f(v.x, v.y); + param->set_value(f); + rc = true; + } + } else if (type == FCDEffectParameter::FLOAT3) { + FCDEffectParameterFloat3* float3_param = + static_cast<FCDEffectParameterFloat3*>(fc_param); + FMVector3 v = float3_param->GetValue(); + ParamFloat3* param = param_object->CreateParam<ParamFloat3>(param_name); + if (!param) + return false; + Float3 f(v.x, v.y, v.z); + param->set_value(f); + rc = true; + } else if (type == FCDEffectParameter::VECTOR) { + FCDEffectParameterVector* vector_param = + static_cast<FCDEffectParameterVector*>(fc_param); + FMVector4 v = vector_param->GetValue(); + ParamFloat4* param = param_object->CreateParam<ParamFloat4>(param_name); + if (param) { + Float4 f(v.x, v.y, v.z, v.w); + param->set_value(f); + rc = true; + } + } else if (type == FCDEffectParameter::INTEGER) { + FCDEffectParameterInt* int_param = + static_cast<FCDEffectParameterInt*>(fc_param); + ParamInteger* param = param_object->CreateParam<ParamInteger>(param_name); + if (param) { + param->set_value(int_param->GetValue()); + rc = true; + } + } else if (type == FCDEffectParameter::BOOLEAN) { + FCDEffectParameterBool* bool_param = + static_cast<FCDEffectParameterBool*>(fc_param); + ParamBoolean* param = param_object->CreateParam<ParamBoolean>(param_name); + if (param) { + param->set_value(bool_param->GetValue()); + rc = true; + } + } else if (type == FCDEffectParameter::MATRIX) { + FCDEffectParameterMatrix* matrix_param = + static_cast<FCDEffectParameterMatrix*>(fc_param); + ParamMatrix4* param = param_object->CreateParam<ParamMatrix4>(param_name); + if (param) { + param->set_value(FMMatrix44ToMatrix4(matrix_param->GetValue())); + rc = true; + } + } else if (type == FCDEffectParameter::SAMPLER) { + FCDEffectParameterSampler* sampler = + static_cast<FCDEffectParameterSampler*>(fc_param); + ParamSampler* sampler_param = param_object->CreateParam<ParamSampler>( + param_name); + if (sampler_param) { + Sampler* o3d_sampler = pack_->Create<Sampler>(); + o3d_sampler->set_name(param_name); + sampler_param->set_value(o3d_sampler); + + FCDEffectParameterSurface* surface = sampler->GetSurface(); + if (surface) { + Texture* tex = BuildTexture(surface); + + // Set the texture on the sampler. + if (tex) { + o3d_sampler->set_texture(tex); + rc = true; + } + } + SetSamplerStates(sampler, o3d_sampler); + } + } else if (type == FCDEffectParameter::SURFACE) { + // TODO: This code is here to handle the NV_import profile + // exported by Max's DirectX Shader materials, which references + // only references texture params (not samplers). Once we move + // completely to using samplers and add sampler blocks to our + // collada file then we should eliminate this codepath. + + FCDEffectParameterSurface* surface = + static_cast<FCDEffectParameterSurface*>(fc_param); + + Texture* tex = BuildTexture(surface); + if (tex) { + ParamTexture* param = param_object->CreateParam<ParamTexture>(param_name); + if (param) { + param->set_value(tex); + rc = true; + } + } + } + return rc; +} + +Effect* Collada::GetDummyEffect() { + if (!dummy_effect_) { + // Create a dummy effect, just so we can see something + dummy_effect_ = pack_->Create<Effect>(); + dummy_effect_->set_name( + COLLADA_STRING_CONSTANT("substituteForMissingOrBadEffect")); + dummy_effect_->LoadFromFXString( + "float4x4 worldViewProj : WorldViewProjection;" + "float4 vs(float4 v : POSITION ) : POSITION {" + " return mul(v, worldViewProj);" + "}" + "float4 ps() : COLOR {" + " return float4(1, 0, 1, 1);" + "}\n" + "// #o3d VertexShaderEntryPoint vs\n" + "// #o3d PixelShaderEntryPoint ps\n"); + } + return dummy_effect_; +} + +Material* Collada::GetDummyMaterial() { + if (!dummy_material_) { + dummy_material_ = pack_->Create<Material>(); + dummy_material_->set_name( + COLLADA_STRING_CONSTANT("substituteForMissingOrBadMaterial")); + dummy_material_->set_effect(GetDummyEffect()); + } + return dummy_material_; +} + +static const char* GetLightingType(FCDEffectStandard* std_profile) { + FCDEffectStandard::LightingType type = std_profile->GetLightingType(); + switch (type) { + case FCDEffectStandard::CONSTANT: + return "constant"; + case FCDEffectStandard::PHONG: + return "phong"; + case FCDEffectStandard::BLINN: + return "blinn"; + case FCDEffectStandard::LAMBERT: + return "lambert"; + default: + return "unknown"; + } +} + +static FCDEffectProfileFX* FindProfileFX(FCDEffect* effect) { + FCDEffectProfile* profile = effect->FindProfile(FUDaeProfileType::HLSL); + if (!profile) { + profile = effect->FindProfile(FUDaeProfileType::CG); + if (!profile) { + return NULL; + } + } + return static_cast<FCDEffectProfileFX*>(profile); +} + +Material* Collada::BuildMaterial(FCDocument* doc, + FCDMaterial* collada_material) { + if (!doc || !collada_material) { + return NULL; + } + + fm::string material_id = collada_material->GetDaeId(); + Material* material = materials_[material_id.c_str()]; + if (!material) { + Effect* effect = NULL; + FCDEffect* collada_effect = collada_material->GetEffect(); + if (collada_effect) { + effect = GetEffect(doc, collada_effect); + } + + String collada_material_name = WideToUTF8( + collada_material->GetName().c_str()); + Material* material = pack_->Create<Material>(); + material->set_name(collada_material_name); + material->set_effect(effect); + SetParamsFromMaterial(collada_material, material); + + // If this is a COLLADA-FX profile, add the render states from the + // COLLADA-FX sections. + FCDEffectProfileFX* profile_fx = FindProfileFX(collada_effect); + if (profile_fx) { + if (profile_fx->GetTechniqueCount() > 0) { + FCDEffectTechnique* technique = profile_fx->GetTechnique(0); + if (technique->GetPassCount() > 0) { + FCDEffectPass* pass = technique->GetPass(0); + State* state = pack_->Create<State>(); + state->set_name("pass_state"); + cull_enabled_ = cull_front_ = front_cw_ = false; + for (size_t i = 0; i < pass->GetRenderStateCount(); ++i) { + AddRenderState(pass->GetRenderState(i), state); + } + material->set_state(state); + } + } + } else { + FCDEffectStandard* std_profile = static_cast<FCDEffectStandard *>( + collada_effect->FindProfile(FUDaeProfileType::COMMON)); + if (std_profile) { + ParamString* type_tag = material->CreateParam<ParamString>( + COLLADA_STRING_CONSTANT("lightingType")); + type_tag->set_value(GetLightingType(std_profile)); + } + } + materials_[material_id.c_str()] = material; + } + return material; +} + +Effect* Collada::GetEffect(FCDocument* doc, FCDEffect* collada_effect) { + Effect* effect = NULL; + fm::string effect_id = collada_effect->GetDaeId(); + effect = effects_[effect_id.c_str()]; + if (!effect) { + effect = BuildEffect(doc, collada_effect); + if (effect) effects_[effect_id.c_str()] = effect; + } + return effect; +} + +// Builds an O3D effect from a COLLADA effect. If a COLLADA-FX +// (Cg/HLSL) effect is present, it will be used and a programmable +// Effect generated. If not, an attempt is made to use one of the fixed- +// function profiles if present (eg., Constant, Lambert). +// Parameters: +// doc: The FCollada document from which to import nodes. +// collada_effect: The COLLADA effect from which shaders will be taken. +// Returns: +// the newly-created effect, or NULL or error. +Effect* Collada::BuildEffect(FCDocument* doc, FCDEffect* collada_effect) { + if (!doc || !collada_effect) return NULL; + // TODO: Remove all of this to be replaced by parsing Collada-FX + // and profile_O3D only. + Effect* effect = NULL; + FCDEffectProfileFX* profile_fx = FindProfileFX(collada_effect); + if (profile_fx) { + if (profile_fx->GetCodeCount() > 0) { + FCDEffectCode* code = profile_fx->GetCode(0); + String effect_string; + FilePath file_path; + if (code->GetType() == FCDEffectCode::CODE) { + fstring code_string = code->GetCode(); + effect_string = WideToUTF8(code_string.c_str()); + unique_filename_counter_++; + String file_name = "embedded-shader-" + + IntToString(unique_filename_counter_) + ".fx"; + file_path = FilePath(FILE_PATH_LITERAL("shaders")); + file_path.Append(UTF8ToFilePath(file_name)); + } else if (code->GetType() == FCDEffectCode::INCLUDE) { + fstring path = code->GetFilename(); + file_path = WideToFilePath(path.c_str()); + if (collada_zip_archive_ && !file_path.empty()) { + // Make absolute path be relative to root of archive. + if (file_path.value()[0] == FILE_PATH_LITERAL('/')) { + file_path = FilePath(file_path.value().substr(1)); + } + size_t effect_data_size; + char *effect_data = + collada_zip_archive_->GetFileData(FilePathToUTF8(file_path), + &effect_data_size); + if (effect_data) { + effect_string = effect_data; + free(effect_data); + } else { + O3D_ERROR(service_locator_) + << "Unable to read effect data for effect '" + << FilePathToUTF8(file_path) << "'"; + return NULL; + } + } else { + file_util::ReadFileToString(file_path, &effect_string); + } + } + String collada_effect_name = WideToUTF8( + collada_effect->GetName().c_str()); + effect = pack_->Create<Effect>(); + effect->set_name(collada_effect_name); + + ParamString* param = effect->CreateParam<ParamString>( + O3D_STRING_CONSTANT("uri")); + DCHECK(param != NULL); + param->set_value(FilePathToUTF8(file_path)); + + if (!effect->LoadFromFXString(effect_string)) { + pack_->RemoveObject(effect); + O3D_ERROR(service_locator_) << "Unable to load effect '" + << FilePathToUTF8(file_path).c_str() + << "'"; + return NULL; + } + if (options_.keep_original_data) { + // Cache the original data by URI so we can recover it later. + original_data_[file_path] = effect_string; + } + } + } else { + FCDExtra* extra = collada_effect->GetExtra(); + if (extra && extra->GetTypeCount() > 0) { + FCDEType* type = extra->GetType(0); + if (type) { + FCDETechnique* technique = type->FindTechnique("NV_import"); + if (technique) { + FCDENode* node = technique->FindChildNode("import"); + if (node) { + FCDEAttribute* url_attrib = node->FindAttribute("url"); + if (url_attrib) { + fstring url = url_attrib->GetValue(); + const FUFileManager* mgr = doc->GetFileManager(); + LOG_ASSERT(mgr != NULL); + FUUri uri = mgr->GetCurrentUri(); + FUUri effect_uri = uri.Resolve(url_attrib->GetValue()); + fstring path = effect_uri.GetAbsolutePath(); + + String collada_effect_name = WideToUTF8( + collada_effect->GetName().c_str()); + + FilePath file_path = WideToFilePath(path.c_str()); + + String effect_string; + + if (collada_zip_archive_ && !file_path.empty()) { + // Make absolute path be relative to root of archive. + if (file_path.value()[0] == FILE_PATH_LITERAL('/')) { + file_path = FilePath(file_path.value().substr(1)); + } + // shader file can be extracted in memory from zip archive + // so lets get the data and turn it into a string + size_t effect_data_size; + char *effect_data = + collada_zip_archive_->GetFileData(FilePathToUTF8(file_path), + &effect_data_size); + if (effect_data) { + effect_string = effect_data; + free(effect_data); + } else { + O3D_ERROR(service_locator_) + << "Unable to read effect data for effect '" + << FilePathToUTF8(file_path) << "'"; + return NULL; + } + } else { + file_util::ReadFileToString(file_path, &effect_string); + } + + effect = pack_->Create<Effect>(); + effect->set_name(collada_effect_name); + + ParamString* param = effect->CreateParam<ParamString>( + O3D_STRING_CONSTANT("uri")); + DCHECK(param != NULL); + param->set_value(FilePathToUTF8(file_path)); + + if (!effect->LoadFromFXString(effect_string)) { + pack_->RemoveObject(effect); + O3D_ERROR(service_locator_) << "Unable to load effect '" + << FilePathToUTF8(file_path).c_str() + << "'"; + return NULL; + } + if (options_.keep_original_data) { + // Cache the original data by URI so we can recover it later. + original_data_[file_path] = effect_string; + } + } + } + } + } + } + } + return effect; +} + +// Gets a typed value from an FCollada state. T is the type to get, +// state is the state from which to retrieve the value, and offset is +// the index into the state's data (in sizeof T objects) at which the +// value is located. +template <class T> +static T GetStateValue(FCDEffectPassState* state, size_t offset) { + LOG_ASSERT(offset * sizeof(T) < state->GetDataSize()); + return *(reinterpret_cast<T*>(state->GetData()) + offset); +} + +// Converts an FCollada blend type into an O3D state blending function. +// On error, an error string is logged. "type" is the FCollada type to convert, +// and "collada" is the Collada importer used for logging errors. +static State::BlendingFunction ConvertBlendType( + ServiceLocator* service_locator, + FUDaePassStateBlendType::Type type) { + switch (type) { + case FUDaePassStateBlendType::ZERO: + return State::BLENDFUNC_ZERO; + case FUDaePassStateBlendType::ONE: + return State::BLENDFUNC_ONE; + case FUDaePassStateBlendType::SOURCE_COLOR: + return State::BLENDFUNC_SOURCE_COLOR; + case FUDaePassStateBlendType::ONE_MINUS_SOURCE_COLOR: + return State::BLENDFUNC_INVERSE_SOURCE_COLOR; + case FUDaePassStateBlendType::SOURCE_ALPHA: + return State::BLENDFUNC_SOURCE_ALPHA; + case FUDaePassStateBlendType::ONE_MINUS_SOURCE_ALPHA: + return State::BLENDFUNC_INVERSE_SOURCE_ALPHA; + case FUDaePassStateBlendType::DESTINATION_ALPHA: + return State::BLENDFUNC_DESTINATION_ALPHA; + case FUDaePassStateBlendType::ONE_MINUS_DESTINATION_ALPHA: + return State::BLENDFUNC_INVERSE_DESTINATION_ALPHA; + case FUDaePassStateBlendType::DESTINATION_COLOR: + return State::BLENDFUNC_DESTINATION_COLOR; + case FUDaePassStateBlendType::ONE_MINUS_DESTINATION_COLOR: + return State::BLENDFUNC_INVERSE_DESTINATION_COLOR; + case FUDaePassStateBlendType::SOURCE_ALPHA_SATURATE: + return State::BLENDFUNC_SOURCE_ALPHA_SATUTRATE; + default: + O3D_ERROR(service_locator) << "Invalid blend type"; + return State::BLENDFUNC_ONE; + } +} + +// Converts an FCollada blend equation into an O3D state blending +// equation. On error, an error string is logged. "equation" is the +// FCollada equation to convert, and "collada" is the Collada importer +// used for logging errors. +static State::BlendingEquation ConvertBlendEquation( + ServiceLocator* service_locator, + FUDaePassStateBlendEquation::Equation equation) { + switch (equation) { + case FUDaePassStateBlendEquation::ADD: + return State::BLEND_ADD; + case FUDaePassStateBlendEquation::SUBTRACT: + return State::BLEND_SUBTRACT; + case FUDaePassStateBlendEquation::REVERSE_SUBTRACT: + return State::BLEND_REVERSE_SUBTRACT; + case FUDaePassStateBlendEquation::MIN: + return State::BLEND_MIN; + case FUDaePassStateBlendEquation::MAX: + return State::BLEND_MAX; + default: + O3D_ERROR(service_locator) << "Invalid blend equation"; + return State::BLEND_ADD; + } +} + +// Converts an FCollada comparison function into an O3D state blending +// equation. On error, an error string is logged. "function" is the +// FCollada comparison function to convert, and "collada" is the Collada +// importer used for logging errors. +static State::Comparison ConvertComparisonFunction( + ServiceLocator* service_locator, + FUDaePassStateFunction::Function function) { + switch (function) { + case FUDaePassStateFunction::NEVER: + return State::CMP_NEVER; + case FUDaePassStateFunction::LESS: + return State::CMP_LESS; + case FUDaePassStateFunction::LESS_EQUAL: + return State::CMP_LEQUAL; + case FUDaePassStateFunction::EQUAL: + return State::CMP_EQUAL; + case FUDaePassStateFunction::GREATER: + return State::CMP_GREATER; + case FUDaePassStateFunction::NOT_EQUAL: + return State::CMP_NOTEQUAL; + case FUDaePassStateFunction::GREATER_EQUAL: + return State::CMP_GEQUAL; + case FUDaePassStateFunction::ALWAYS: + return State::CMP_ALWAYS; + default: + O3D_ERROR(service_locator) << "Invalid comparison function"; + return State::CMP_NEVER; + } +} + +// Converts an FCollada polygon fill mode into an O3D fill mode. +// On error, an error string is logged. "mode" is the FCollada fill +// mode to convert, and "collada" is the Collada importer used for +// logging errors. +static State::Fill ConvertFillMode(ServiceLocator* service_locator, + FUDaePassStatePolygonMode::Mode mode) { + switch (mode) { + case FUDaePassStatePolygonMode::POINT: + return State::POINT; + case FUDaePassStatePolygonMode::LINE: + return State::WIREFRAME; + case FUDaePassStatePolygonMode::FILL: + return State::SOLID; + default: + O3D_ERROR(service_locator) << "Invalid polygon fill mode"; + return State::SOLID; + } +} + +// Converts an FCollada stencil operation into an O3D stencil operation. +// On error, an error string is logged. "operation" is the FCollada operation +// to convert, and "collada" is the Collada importer used for logging errors. +static State::StencilOperation ConvertStencilOp( + ServiceLocator* service_locator, + FUDaePassStateStencilOperation::Operation operation) { + switch (operation) { + case FUDaePassStateStencilOperation::KEEP: + return State::STENCIL_KEEP; + case FUDaePassStateStencilOperation::ZERO: + return State::STENCIL_ZERO; + case FUDaePassStateStencilOperation::REPLACE: + return State::STENCIL_REPLACE; + case FUDaePassStateStencilOperation::INCREMENT: + return State::STENCIL_INCREMENT_SATURATE; + case FUDaePassStateStencilOperation::DECREMENT: + return State::STENCIL_DECREMENT_SATURATE; + case FUDaePassStateStencilOperation::INVERT: + return State::STENCIL_INVERT; + case FUDaePassStateStencilOperation::INCREMENT_WRAP: + return State::STENCIL_INCREMENT; + case FUDaePassStateStencilOperation::DECREMENT_WRAP: + return State::STENCIL_DECREMENT; + default: + O3D_ERROR(service_locator) << "Invalid stencil operation"; + return State::STENCIL_KEEP; + } +} + +// Updates the O3D state object's cull mode from the OpenGL-style +// cull modes used by COLLADA-FX sections. "state" is the O3D state +// on which to set the cull mode. If "cull_front_" is true, the system is +// culling front-facing polygons, otherwise it's culling back-facing. If +// "front_cw_" is true, polygons with clockwise winding are considered +// front-facing, otherwise counter-clockwise winding is considered +// front-facing. if "cull_enabled_" is false, culling is disabled. +void Collada::UpdateCullingState(State* state) { + ParamInteger* face = state->GetStateParam<ParamInteger>( + State::kCullModeParamName); + if (cull_front_ ^ front_cw_) { + face->set_value(cull_enabled_ ? State::CULL_CCW : State::CULL_NONE); + } else { + face->set_value(cull_enabled_ ? State::CULL_CW : State::CULL_NONE); + } +} + +// Sets a boolean state param in an O3D state object. "state" is the +// state object, "name" is the name of the state param to set, and "value" +// is the boolean value. If the state param does not exist, or is not +// of the correct type, an assert is logged. +static void SetBoolState(State* state, const char* name, bool value) { + ParamBoolean* param = state->GetStateParam<ParamBoolean>(name); + LOG_ASSERT(param); + param->set_value(value); +} + +// Sets a floating-point state param in an O3D state object. +// "state" is the state object, "name" is the name of the state param +// to set, and "value" is the floating-point value. If the state +// param does not exist, or is not of the correct type, an assert +// is logged. +static void SetFloatState(State* state, const char* name, float value) { + ParamFloat* param = state->GetStateParam<ParamFloat>(name); + LOG_ASSERT(param); + param->set_value(value); +} + +// Sets a Float4 state param in an O3D state object. "state" is +// the state object, "name" is the name of the state param to set, and +// "value" is the Float4 value. If the state param does not exist, or +// is not of the correct type, an assert is logged. +static void SetFloat4State(State* state, + const char* name, + const Float4& value) { + ParamFloat4* param = state->GetStateParam<ParamFloat4>(name); + LOG_ASSERT(param); + param->set_value(value); +} + +// Sets a integer state param in an O3D state object. "state" is +// the state object, "name" is the name of the state param to set, and +// "value" is the Float4 value. If the state param does not exist, or +// is not of the correct type, an assert is logged. +static void SetIntState(State* state, const char* name, int value) { + ParamInteger* param = state->GetStateParam<ParamInteger>(name); + LOG_ASSERT(param); + param->set_value(value); +} + +// Sets the stencil state params for polygons with clockwise +// winding. "state" is the O3D state object in which to set +// state, "fail", "zfail", and "zpass" are the corresponding stencil +// settings. +static void SetCWStencilSettings(State* state, + State::StencilOperation fail, + State::StencilOperation zfail, + State::StencilOperation zpass) { + SetIntState(state, State::kStencilFailOperationParamName, fail); + SetIntState(state, State::kStencilZFailOperationParamName, zfail); + SetIntState(state, State::kStencilPassOperationParamName, zpass); +} + +// Sets the stencil state params for polygons with counter-clockwise +// winding. "state" is the O3D state object in which to set +// state, "fail", "zfail", and "zpass" are the corresponding stencil +// settings. +static void SetCCWStencilSettings(State* state, + State::StencilOperation fail, + State::StencilOperation zfail, + State::StencilOperation zpass) { + SetIntState(state, State::kCCWStencilFailOperationParamName, fail); + SetIntState(state, State::kCCWStencilZFailOperationParamName, zfail); + SetIntState(state, State::kCCWStencilPassOperationParamName, zpass); +} + +// Sets the stencil state params for polygons with the given winding +// order. "state" is the O3D state object in which to set +// state, "fail", "zfail", and "zpass" are the corresponding stencil +// settings. If "cw" is true, clockwise winding settings will be set, +// otherwise counter-clockwise winding settings are set. +static void SetStencilSettings(State* state, + bool cw, + State::StencilOperation fail, + State::StencilOperation zfail, + State::StencilOperation zpass) { + if (cw) { + SetCWStencilSettings(state, fail, zfail, zpass); + } else { + SetCCWStencilSettings(state, fail, zfail, zpass); + } +} + +// Adds the appropriate O3D state params to the given state object +// corresponding to a given FCollada Pass State. If unsupported or +// invalid states are specified, an error message is set. +void Collada::AddRenderState(FCDEffectPassState* pass_state, State* state) { + switch (pass_state->GetType()) { + case FUDaePassState::ALPHA_FUNC: { + State::Comparison function = ConvertComparisonFunction( + service_locator_, + GetStateValue<FUDaePassStateFunction::Function>(pass_state, 0)); + float value = GetStateValue<float>(pass_state, 1); + SetIntState(state, State::kAlphaComparisonFunctionParamName, function); + SetFloatState(state, State::kAlphaReferenceParamName, value); + break; + } + case FUDaePassState::BLEND_FUNC: { + State::BlendingFunction src = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 0)); + State::BlendingFunction dest = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 1)); + SetIntState(state, State::kSourceBlendFunctionParamName, src); + SetIntState(state, State::kDestinationBlendFunctionParamName, dest); + SetBoolState(state, State::kSeparateAlphaBlendEnableParamName, false); + break; + } + case FUDaePassState::BLEND_FUNC_SEPARATE: { + State::BlendingFunction src_rgb = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 0)); + State::BlendingFunction dest_rgb = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 1)); + State::BlendingFunction src_alpha = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 2)); + State::BlendingFunction dest_alpha = ConvertBlendType( + service_locator_, + GetStateValue<FUDaePassStateBlendType::Type>(pass_state, 3)); + SetIntState(state, State::kSourceBlendFunctionParamName, src_rgb); + SetIntState(state, State::kDestinationBlendFunctionParamName, dest_rgb); + SetIntState(state, State::kSourceBlendAlphaFunctionParamName, src_alpha); + SetIntState(state, State::kDestinationBlendAlphaFunctionParamName, + dest_alpha); + SetBoolState(state, State::kSeparateAlphaBlendEnableParamName, true); + break; + } + case FUDaePassState::BLEND_EQUATION: { + State::BlendingEquation value = ConvertBlendEquation( + service_locator_, + GetStateValue<FUDaePassStateBlendEquation::Equation>(pass_state, 0)); + SetIntState(state, State::kBlendEquationParamName, value); + SetIntState(state, State::kBlendAlphaEquationParamName, value); + break; + } + case FUDaePassState::BLEND_EQUATION_SEPARATE: { + State::BlendingEquation rgb = ConvertBlendEquation( + service_locator_, + GetStateValue<FUDaePassStateBlendEquation::Equation>(pass_state, 0)); + State::BlendingEquation alpha = ConvertBlendEquation( + service_locator_, + GetStateValue<FUDaePassStateBlendEquation::Equation>(pass_state, 1)); + SetIntState(state, State::kBlendEquationParamName, rgb); + SetIntState(state, State::kBlendAlphaEquationParamName, alpha); + break; + } + case FUDaePassState::CULL_FACE: { + FUDaePassStateFaceType::Type culled_faces = + GetStateValue<FUDaePassStateFaceType::Type>(pass_state, 0); + switch (culled_faces) { + case FUDaePassStateFaceType::FRONT: { + cull_front_ = true; + break; + } + case FUDaePassStateFaceType::BACK: { + cull_front_ = false; + break; + } + case FUDaePassStateFaceType::FRONT_AND_BACK: { + O3D_ERROR(service_locator_) + << "FRONT_AND_BACK culling is unsupported"; + break; + } + } + UpdateCullingState(state); + break; + } + case FUDaePassState::DEPTH_FUNC: { + State::Comparison function = ConvertComparisonFunction( + service_locator_, + GetStateValue<FUDaePassStateFunction::Function>(pass_state, 0)); + SetIntState(state, State::kZComparisonFunctionParamName, function); + break; + } + case FUDaePassState::FRONT_FACE: { + FUDaePassStateFrontFaceType::Type type = + GetStateValue<FUDaePassStateFrontFaceType::Type>(pass_state, 0); + front_cw_ = type == FUDaePassStateFrontFaceType::CLOCKWISE; + break; + } + case FUDaePassState::POLYGON_MODE: { + FUDaePassStateFaceType::Type face = + GetStateValue<FUDaePassStateFaceType::Type>(pass_state, 0); + State::Fill mode = ConvertFillMode( + service_locator_, + GetStateValue<FUDaePassStatePolygonMode::Mode>(pass_state, 1)); + if (face != FUDaePassStateFaceType::FRONT_AND_BACK) { + O3D_ERROR(service_locator_) + << "Separate polygon fill modes are unsupported"; + } + SetIntState(state, State::kFillModeParamName, mode); + break; + } + case FUDaePassState::STENCIL_FUNC: { + State::Comparison func = ConvertComparisonFunction( + service_locator_, + GetStateValue<FUDaePassStateFunction::Function>(pass_state, 0)); + uint8 ref = GetStateValue<uint8>(pass_state, 4); + uint8 mask = GetStateValue<uint8>(pass_state, 5); + SetIntState(state, State::kStencilComparisonFunctionParamName, func); + SetIntState(state, State::kStencilReferenceParamName, ref); + SetIntState(state, State::kStencilMaskParamName, mask); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, false); + break; + } + case FUDaePassState::STENCIL_FUNC_SEPARATE: { + State::Comparison front = ConvertComparisonFunction( + service_locator_, + GetStateValue<FUDaePassStateFunction::Function>(pass_state, 0)); + State::Comparison back = ConvertComparisonFunction( + service_locator_, + GetStateValue<FUDaePassStateFunction::Function>(pass_state, 1)); + uint8 ref = GetStateValue<uint8>(pass_state, 8); + uint8 mask = GetStateValue<uint8>(pass_state, 9); + SetIntState(state, State::kStencilComparisonFunctionParamName, front); + SetIntState(state, State::kCCWStencilComparisonFunctionParamName, back); + SetIntState(state, State::kStencilReferenceParamName, ref); + SetIntState(state, State::kStencilMaskParamName, mask); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, true); + break; + } + case FUDaePassState::STENCIL_OP: { + State::StencilOperation fail = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 0)); + State::StencilOperation zfail = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 1)); + State::StencilOperation zpass = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 2)); + SetStencilSettings(state, front_cw_, fail, zfail, zpass); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, false); + break; + } + case FUDaePassState::STENCIL_OP_SEPARATE: { + FUDaePassStateFaceType::Type type = + GetStateValue<FUDaePassStateFaceType::Type>(pass_state, 0); + State::StencilOperation fail = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 0)); + State::StencilOperation zfail = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 1)); + State::StencilOperation zpass = ConvertStencilOp( + service_locator_, + GetStateValue<FUDaePassStateStencilOperation::Operation>(pass_state, + 2)); + switch (type) { + case FUDaePassStateFaceType::FRONT: + SetStencilSettings(state, front_cw_, fail, zfail, zpass); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, true); + break; + case FUDaePassStateFaceType::BACK: + SetStencilSettings(state, !front_cw_, fail, zfail, zpass); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, true); + break; + case FUDaePassStateFaceType::FRONT_AND_BACK: + SetStencilSettings(state, front_cw_, fail, zfail, zpass); + SetStencilSettings(state, !front_cw_, fail, zfail, zpass); + SetBoolState(state, State::kTwoSidedStencilEnableParamName, false); + break; + default: + O3D_ERROR(service_locator_) + << "Unknown polygon face mode in STENCIL_OP_SEPARATE"; + break; + } + break; + } + case FUDaePassState::STENCIL_MASK: { + uint32 mask = GetStateValue<uint8>(pass_state, 0); + SetIntState(state, State::kStencilWriteMaskParamName, mask); + break; + } + case FUDaePassState::STENCIL_MASK_SEPARATE: { + O3D_ERROR(service_locator_) << "Separate stencil mask is unsupported"; + break; + } + case FUDaePassState::COLOR_MASK: { + bool red = GetStateValue<bool>(pass_state, 0); + bool green = GetStateValue<bool>(pass_state, 1); + bool blue = GetStateValue<bool>(pass_state, 2); + bool alpha = GetStateValue<bool>(pass_state, 3); + int mask = 0x0; + if (red) mask |= 0x1; + if (green) mask |= 0x2; + if (blue) mask |= 0x4; + if (alpha) mask |= 0x8; + SetIntState(state, State::kColorWriteEnableParamName, mask); + break; + } + case FUDaePassState::DEPTH_MASK: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kZWriteEnableParamName, value); + break; + } + case FUDaePassState::POINT_SIZE: { + float value = GetStateValue<float>(pass_state, 0); + SetFloatState(state, State::kPointSizeParamName, value); + break; + } + case FUDaePassState::POLYGON_OFFSET: { + float value1 = GetStateValue<float>(pass_state, 0); + float value2 = GetStateValue<float>(pass_state, 1); + SetFloatState(state, State::kPolygonOffset1ParamName, value1); + SetFloatState(state, State::kPolygonOffset2ParamName, value2); + break; + } + case FUDaePassState::BLEND_COLOR: { + FMVector4 value = GetStateValue<FMVector4>(pass_state, 0); + Float4 v(value.x, value.y, value.z, value.w); + SetFloat4State(state, State::kPolygonOffset1ParamName, v); + break; + } + case FUDaePassState::ALPHA_TEST_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kAlphaTestEnableParamName, value); + break; + } + case FUDaePassState::BLEND_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kAlphaBlendEnableParamName, value); + break; + } + case FUDaePassState::CULL_FACE_ENABLE: { + cull_enabled_ = GetStateValue<bool>(pass_state, 0); + UpdateCullingState(state); + break; + } + case FUDaePassState::DEPTH_TEST_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kZEnableParamName, value); + break; + } + case FUDaePassState::DITHER_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kDitherEnableParamName, value); + break; + } + case FUDaePassState::LINE_SMOOTH_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kLineSmoothEnableParamName, value); + break; + } + case FUDaePassState::STENCIL_TEST_ENABLE: { + bool value = GetStateValue<bool>(pass_state, 0); + SetBoolState(state, State::kStencilEnableParamName, value); + break; + } + } +} + +// Converts an FCollada texture sampler wrap mode to an O3D Sampler +// AddressMode. +static Sampler::AddressMode ConvertSamplerAddressMode( + FUDaeTextureWrapMode::WrapMode wrap_mode) { + switch (wrap_mode) { + case FUDaeTextureWrapMode::WRAP: + return Sampler::WRAP; + case FUDaeTextureWrapMode::MIRROR: + return Sampler::MIRROR; + case FUDaeTextureWrapMode::CLAMP: + return Sampler::CLAMP; + case FUDaeTextureWrapMode::BORDER: + return Sampler::BORDER; + default: + return Sampler::WRAP; + } +} + +// Converts an FCollada filter func to an O3D Sampler FilterType. +// Since the Collada filter spec allows both GL-style combo mag/min filters, +// and DX-style (separate min/mag/mip filters), this function extracts +// only the first (min) part of a GL-style filter. +static Sampler::FilterType ConvertFilterType( + FUDaeTextureFilterFunction::FilterFunction filter_function, + bool allow_none) { + switch (filter_function) { + case FUDaeTextureFilterFunction::NEAREST: + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_NEAREST: + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR: + return Sampler::POINT; + case FUDaeTextureFilterFunction::LINEAR: + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST: + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR: + return Sampler::LINEAR; + // TODO: Once FCollada supports COLLADA v1.5, turn this on: + // case FUDaeTextureFilterFunction::ANISOTROPIC: + // return Sampler::ANISOTROPIC; + case FUDaeTextureFilterFunction::NONE: + return allow_none ? Sampler::NONE : Sampler::LINEAR; + default: + return Sampler::LINEAR; + } +} + + +// Retrieves the mipmap part of a GL-style filter function. +// If no mipmap part is specified, it is assumed to be POINT. +static Sampler::FilterType ConvertMipmapFilter( + FUDaeTextureFilterFunction::FilterFunction filter_function) { + switch (filter_function) { + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_NEAREST: + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST: + case FUDaeTextureFilterFunction::UNKNOWN: + return Sampler::POINT; + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR: + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR: + return Sampler::LINEAR; + default: + return Sampler::NONE; + } +} + +// Sets the texture sampler states on an O3D sampler from the +// settings found in an FCollada sampler. +// Parameters: +// effect_sampler: The FCollada sampler. +// o3d_sampler: The O3D sampler object. +void Collada::SetSamplerStates(FCDEffectParameterSampler* effect_sampler, + Sampler* o3d_sampler) { + FUDaeTextureWrapMode::WrapMode wrap_s = effect_sampler->GetWrapS(); + FUDaeTextureWrapMode::WrapMode wrap_t = effect_sampler->GetWrapT(); + Texture *texture = o3d_sampler->texture(); + if (texture && texture->IsA(TextureCUBE::GetApparentClass())) { + // Our default is WRAP, but cube maps should use CLAMP + if (wrap_s == FUDaeTextureWrapMode::UNKNOWN) + wrap_s = FUDaeTextureWrapMode::CLAMP; + if (wrap_t == FUDaeTextureWrapMode::UNKNOWN) + wrap_t = FUDaeTextureWrapMode::CLAMP; + } + + FUDaeTextureFilterFunction::FilterFunction min_filter = + effect_sampler->GetMinFilter(); + FUDaeTextureFilterFunction::FilterFunction mag_filter = + effect_sampler->GetMagFilter(); + + FUDaeTextureFilterFunction::FilterFunction mip_filter = + effect_sampler->GetMipFilter(); + // TODO: Once FCollada supports COLLADA v1.5, turn this on: + // int max_anisotropy = effect_sampler->GetMaxAnisotropy(); + + o3d_sampler->set_address_mode_u(ConvertSamplerAddressMode(wrap_s)); + o3d_sampler->set_address_mode_v(ConvertSamplerAddressMode(wrap_t)); + + // The Collada spec allows for both DX-style and GL-style specification + // of texture filtering modes. In DX-style, Min, Mag and Mip filters + // are specified separately, and may be Linear, Point or None. + // In GL-style, only Min and Mag are specified, with the Mip filter + // encoded as a combo setting in the Min filter. E.g., + // LinearMipmapLinear => Min Linear, Mip Linear, + // LinearMipmapNearest => Min Linear, Mip Point, + // Linear => Min Linear, Mip None (no mipmapping). + + // In order to sort this out, if the Mip filter is "unknown" (missing), + // we assume GL-style specification, and extract the Mip setting from + // the latter part of the GL-style Min setting. If the Mip filter is + // specified, we assume a DX-style specification, and the three + // components are assigned separately. Any GL-style combo + // modes used in DX mode are ignored (only the first part is used). + + o3d_sampler->set_min_filter(ConvertFilterType(min_filter, false)); + o3d_sampler->set_mag_filter(ConvertFilterType(mag_filter, false)); + + // If the mip filter is set to "UNKNOWN", we assume it's a GL-style + // mode, and use the 2nd part of the Min filter for the mip type. Otherwise, + // we use the first part. + if (mip_filter == FUDaeTextureFilterFunction::UNKNOWN) { + o3d_sampler->set_mip_filter(ConvertMipmapFilter(min_filter)); + } else { + o3d_sampler->set_mip_filter(ConvertFilterType(mip_filter, true)); + } + + // TODO: Once FCollada supports COLLADA v1.5, turn this on: + // o3d_sampler->set_max_anisotropy(max_anisotropy); +} + +// Sets the value of a Param on the given ParamObject from an FCollada +// standard-profile effect parameter. If the FCollada parameter +// contains a texture, the sampler_param_name and channel is used to set +// a Sampler Param in o3d from the surface. If not, the +// color_param_name is used to create set a vector Param value. +// Parameters: +// effect_standard: The fixed-function FCollada effect from which +// to retrieve the parameter value. +// param_object: The ParamObject on which parameters will be set. +// color_param_mame: The name of the param to set, if a vector. +// sampler_param_mame: The name of the param to set, if a texture sampler. +// color_param: The FCollada parameter from which to retrieve the +// new value. +// channel: The texture channel to use (if any). +void Collada::SetParamFromStandardEffectParam( + FCDEffectStandard* effect_standard, + ParamObject* param_object, + const char* color_param_name, + const char* sampler_param_name, + FCDEffectParameter* color_param, + int channel) { + if (effect_standard->GetTextureCount(channel) > 0) { + FCDTexture* texture = effect_standard->GetTexture(channel, 0); + FCDEffectParameterSampler* sampler = texture->GetSampler(); + SetParamFromFCEffectParam(param_object, sampler_param_name, sampler); + } else if (color_param) { + SetParamFromFCEffectParam(param_object, color_param_name, color_param); + } +} + +// Sets the values of a ParamObject parameters from a given FCollada material +// node. If a corresponding ParamObject parameter is not found, the FCollada +// parameter is ignored. +// TODO: Should we ignore params not found? Maybe the user wants those for +// things other than rendering. +// Parameters: +// material: The FCollada material node from which to retrieve values. +// param_object: The ParamObject on which parameters will be set. +void Collada::SetParamsFromMaterial(FCDMaterial* material, + ParamObject* param_object) { + size_t pcount = material->GetEffectParameterCount(); + // TODO: This test (for determining if we used the + // programmable profile or the fixed-func profile) is not very robust. + // Remove this once the Material changes are in. + if (pcount > 0) { + for (size_t i = 0; i < pcount; ++i) { + FCDEffectParameter* p = material->GetEffectParameter(i); + LOG_ASSERT(p); + String param_name(p->GetReference()); + // Check for an effect binding + FCDEffect* effect = material->GetEffect(); + FCDEffectProfileFX* profile_fx = FindProfileFX(material->GetEffect()); + if (profile_fx) { + FCDEffectTechnique* technique = profile_fx->GetTechnique(0); + if (technique->GetPassCount() > 0) { + FCDEffectPass* pass = technique->GetPass(0); + for (size_t j = 0; j < pass->GetShaderCount(); ++j) { + FCDEffectPassShader* shader = pass->GetShader(j); + FCDEffectPassBind* bind = + shader->FindBindingReference(p->GetReference()); + if (bind) { + param_name = *bind->symbol; + break; + } + } + } + } + SetParamFromFCEffectParam(param_object, param_name, p); + } + } else { + FCDEffect* effect = material->GetEffect(); + FCDEffectStandard* effect_standard; + if (effect && (effect_standard = static_cast<FCDEffectStandard *>( + effect->FindProfile(FUDaeProfileType::COMMON))) != NULL) { + SetParamFromStandardEffectParam(effect_standard, + param_object, + "emissive", + "emissiveSampler", + effect_standard->GetEmissionColorParam(), + FUDaeTextureChannel::EMISSION); + SetParamFromStandardEffectParam(effect_standard, + param_object, + "ambient", + "ambientSampler", + effect_standard->GetAmbientColorParam(), + FUDaeTextureChannel::AMBIENT); + SetParamFromStandardEffectParam(effect_standard, + param_object, + "diffuse", + "diffuseSampler", + effect_standard->GetDiffuseColorParam(), + FUDaeTextureChannel::DIFFUSE); + SetParamFromStandardEffectParam(effect_standard, + param_object, + "specular", + "specularSampler", + effect_standard->GetSpecularColorParam(), + FUDaeTextureChannel::SPECULAR); + SetParamFromStandardEffectParam(effect_standard, + param_object, + NULL, + "bumpSampler", + NULL, + FUDaeTextureChannel::BUMP); + SetParamFromFCEffectParam(param_object, + "shininess", + effect_standard->GetShininessParam()); + SetParamFromFCEffectParam(param_object, + "specularFactor", + effect_standard->GetSpecularFactorParam()); + } + } +} +} // namespace o3d diff --git a/o3d/import/cross/collada.h b/o3d/import/cross/collada.h new file mode 100644 index 0000000..67538ea --- /dev/null +++ b/o3d/import/cross/collada.h @@ -0,0 +1,438 @@ +/* + * 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 declares functions for importing COLLADA files into O3D. + +#ifndef O3D_IMPORT_CROSS_COLLADA_H_ +#define O3D_IMPORT_CROSS_COLLADA_H_ + +#include <map> +#include <string> +#include <vector> +#include "base/file_path.h" +#include "core/cross/param.h" +#include "core/cross/types.h" + +class FCDocument; +class FCDAnimated; +class FCDCamera; +class FCDSceneNode; +class FCDGeometry; +class FCDGeometryInstance; +class FCDControllerInstance; +class FCDMaterial; +class FCDEffect; +class FCDEffectStandard; +class FCDEffectParameter; +class FCDEffectParameterSampler; +class FCDEffectParameterSurface; +class FCDEffectPassState; +class FCDImage; +class FCDTMatrix; +class FCDTTranslation; +class FCDTRotation; +class FCDTScale; + +class FilePath; + +namespace o3d { + +class ColladaZipArchive; +class Effect; +class IErrorStatus; +class Material; +class Pack; +class ParamObject; +class Sampler; +class ShaderBuilderHelper; +class Shape; +class State; +class Texture; +class Transform; +class TranslationMap; + +// This class keeps an association between a Collada node instance and a +// transform. +// This takes ownership of its children NodeInstances. +class NodeInstance { + public: + typedef std::vector<NodeInstance *> NodeInstanceList; + + explicit NodeInstance(FCDSceneNode *node) : node_(node), transform_(NULL) {} + ~NodeInstance() { + for (unsigned int i = 0; i < children_.size(); ++i) { + delete children_[i]; + } + } + + // Gets the Collada node associated with this node instance. + FCDSceneNode *node() const { return node_; } + + // Gets the Transform associated with this node instance. + Transform *transform() const { return transform_; } + + // Sets the Transform associated with this node instance. + void set_transform(Transform *transform) { transform_ = transform; } + + // Gets the list of this node instance's children. + NodeInstanceList &children() { return children_; } + + // Finds the NodeInstance representing a scene node in the direct children of + // this NodeInstance. + NodeInstance *FindNodeShallow(FCDSceneNode *node) { + for (unsigned int i = 0; i < children_.size(); ++i) { + NodeInstance *child = children_[i]; + if (child->node() == node) return child; + } + return NULL; + } + + // Finds the NodeInstance representing a scene node in the sub-tree starting + // at this NodeInstance. + NodeInstance *FindNodeInTree(FCDSceneNode *node); + + private: + FCDSceneNode *node_; + Transform *transform_; + std::vector<NodeInstance *> children_; +}; + +class Collada { + public: + struct Options { + Options() + : generate_mipmaps(true), + condition_document(false), + keep_original_data(false), + up_axis(0.0f, 0.0f, 0.0f), + base_path(FilePath::kCurrentDirectory) {} + // Whether or not to generate mip-maps on the textures we load. + bool generate_mipmaps; + + // Whether or not to retain the original form for textures for later + // access by filename. + bool keep_original_data; + + // Whether or not to condition documents for o3d as part of + // loading them. + bool condition_document; + + // What the up-axis of the imported geometry should be. + Vector3 up_axis; + + // The base path to use for determining the relative paths for + // asset URIs. + FilePath base_path; + }; + + // Use this if you need access to data after the import (as the + // converter does). + Collada(Pack* pack, const Options& options); + virtual ~Collada(); + + // Imports the given COLLADA file or ZIP file into the given scene. + // This is the external interface to o3d. + // Parameters: + // pack: The pack into which the scene objects will be placed. + // filename: The COLLADA or ZIPped COLLADA file to import. + // parent: The parent node under which the imported nodes will be placed. + // If NULL, nodes will be placed under the client's root. + // animation_input: The counter parameter used to control transform + // animation in the collada file. + // options: The Options structure (see above) that describes any + // desired options. + // Returns true on success. + static bool Import(Pack* pack, + const FilePath& filename, + Transform* parent, + ParamFloat* animation_input, + const Options& options); + + // Same thing but with String filename. + static bool Import(Pack* pack, + const String& filename, + Transform* parent, + ParamFloat* animation_input, + const Options& options); + + // Imports the given COLLADA file or ZIP file into the pack given to + // the constructor. + bool ImportFile(const FilePath& filename, Transform* parent, + ParamFloat* animation_input); + + // Access to the filenames of the original data for texture and + // sound assets imported when ImportFile was called. These will + // only return results after an import if the keep_original_data + // option was set to true when the Collada object was created. + std::vector<FilePath> GetOriginalDataFilenames() const; + const std::string& GetOriginalData(const FilePath& filename) const; + + private: + // Imports the given ZIP file into the given pack. + bool ImportZIP(const FilePath& filename, Transform* parent, + ParamFloat* animation_input); + + // Imports the given COLLADA file (.DAE) into the current pack. + bool ImportDAE(const FilePath& filename, + Transform* parent, + ParamFloat* animation_input); + + // Imports the given FCDocument (already loaded) into the current pack. + bool ImportDAEDocument(FCDocument* doc, + bool fc_status, + Transform* parent, + ParamFloat* animation_input); + + // Creates the instance tree corresponding to the collada scene node DAG. + // A separate NodeInstance is created every time a particular node is + // traversed. The caller must destroy the returned NodeInstance. + static NodeInstance *CreateInstanceTree(FCDSceneNode *node); + + // Recursively imports a tree of nodes from FCollada, rooted at the + // given node, into the O3D scene. + void ImportTree(NodeInstance *instance, + Transform* parent, + ParamFloat* animation_input); + + // Recursively imports a tree of instances (shapes, etc..) from FCollada, + // rooted at the given node, into the O3D scene. This is a separate step + // from ImportTree because various kinds of instances can reference other + // parts of the tree. + void ImportTreeInstances(FCDocument* doc, + NodeInstance* instance); + + bool BuildFloatAnimation(ParamFloat* result, + FCDAnimated* animated, + const char* qualifier, + ParamFloat* animation_input, + float output_scale, + float default_value); + + bool BuildFloat3Animation(ParamFloat3* result, FCDAnimated* animated, + ParamFloat* animation_input, + const Float3& default_value); + + ParamMatrix4* BuildComposition(FCDTMatrix* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input); + + ParamMatrix4* BuildComposition(const Matrix4& matrix, + ParamMatrix4* input_matrix); + + ParamMatrix4* BuildTranslation(FCDTTranslation* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input); + + ParamMatrix4* BuildRotation(FCDTRotation* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input); + + ParamMatrix4* BuildScaling(FCDTScale* transform, + ParamMatrix4* input_matrix, + ParamFloat* animation_input); + + // Builds a Transform node corresponding to the transform elements of + // a given node. All transformations (rotation, translation, scale, + // etc) are collapsed into a single Transform. + Transform* BuildTransform(FCDSceneNode* node, + Transform* parent_transform, + ParamFloat* animation_input); + + // Extracts the various camera parameters from a Collada Camera object and + // stored them as Params on an O3D Transform. + void BuildCamera(FCDocument* doc, + FCDCamera* camera, + Transform* transform, + FCDSceneNode* parent_node); + + // Gets an O3D shape corresponding to a given FCollada geometry instance. + // If the Shape does not exist, Builds one. + Shape* GetShape(FCDocument* doc, + FCDGeometryInstance* geom_instance); + + // Builds O3D shape corresponding to a given FCollada geometry instance. + Shape* BuildShape(FCDocument* doc, + FCDGeometryInstance* geom_instance, + FCDGeometry* geom, + TranslationMap* translationMap); + + // Gets an O3D skinned shape corresponding to a given FCollada controller + // instance. If the Shape does not exist, Builds one. + Shape* GetSkinnedShape(FCDocument* doc, + FCDControllerInstance* instance, + NodeInstance *parent_node_instance); + + // Builds O3D skinned shape corresponding to a given FCollada controller + // instance. + Shape* BuildSkinnedShape(FCDocument* doc, + FCDControllerInstance* instance, + NodeInstance *parent_node_instance); + + // Builds an O3D texture corresponding to a given FCollada surface + // parameter. + Texture* BuildTexture(FCDEffectParameterSurface* surface); + + // Builds an O3D texture corresponding to a given FCDImage. + Texture* BuildTextureFromImage(FCDImage* image); + + // Builds an O3D material from a COLLADA effect and material. If a + // COLLADA-FX (Cg/HLSL) effect is present, it will be used and a programmable + // Effect generated. If not, an attempt is made to use one of the fixed- + // function profiles if present (eg., Constant, Lambert). + Material* BuildMaterial(FCDocument* doc, FCDMaterial* material); + + // Gets an O3D effect correpsonding to a given FCollada effect. If the + // effect does not already exist it is created. + Effect* GetEffect(FCDocument* doc, FCDEffect* effect); + + // Builds an O3D effect from a COLLADA effect and material. If a + // COLLADA-FX (Cg/HLSL) effect is present, it will be used and a programmable + // Effect generated. If not, an attempt is made to use one of the fixed- + // function profiles if present (eg., Constant, Lambert). + Effect* BuildEffect(FCDocument* doc, FCDEffect* effect); + + // Copies the texture sampler states from an FCollada sampler to an O3D + // sampler. + void SetSamplerStates(FCDEffectParameterSampler* effect_sampler, + Sampler* o3d_sampler); + + // Converts the given COLLADA pass state into one or more state + // parameters on the given O3D state object. + void AddRenderState(FCDEffectPassState* pass_state, State* state); + + // Sets the O3D culling state based on the OpenGL-style states that + // COLLADA-FX uses, cached in cull_enabled_, cull_front_ and front_cw_. + void UpdateCullingState(State* state); + + // Sets an O3D parameter value from a given FCollada effect parameter. + bool SetParamFromFCEffectParam(ParamObject *param_object, + const String ¶m_name, + FCDEffectParameter *fc_param); + + // Sets the value of a Param on the given ParamObject from an FCollada + // standard-profile effect parameter. If the FCollada parameter + // contains a texture, the sampler_param_name and channel is used to set + // a Sampler Param in o3d from the surface. If not, the + // color_param_name is used to create set a vector Param value. + void SetParamFromStandardEffectParam(FCDEffectStandard* effect_standard, + ParamObject* param_object, + const char* color_param_name, + const char* sampler_param_name, + FCDEffectParameter* color_param, + int channel); + + // Sets the values of shape parameters from a given FCollada material node. + // If a corresponding shape parameter is not found, the FCollada parameter + // is ignored. + void SetParamsFromMaterial(FCDMaterial* material, ParamObject* param_object); + + // Finds a node instance corresponding to a scene node. Since a particular + // scene node can be instanced multiple times, this will return an arbitrary + // instance. + NodeInstance *FindNodeInstance(FCDSceneNode *node); + + // Finds the node instance corresponding to a scene node if it is not + // instanced, by following the only parent of the nodes until it reaches the + // root. This will return NULL if the node can't be found, or if the node is + // instanced more than once. + NodeInstance *FindNodeInstanceFast(FCDSceneNode *node); + + // Clears out any residual data from the last import. Doesn't + // affect the Pack or the options, just intermediate data structures + // in this object. + void ClearData(); + + // Gets a dummy effect or creates one if none exists. + Effect* GetDummyEffect(); + + // Gets a dummy material or creates one if none exists. + Material* GetDummyMaterial(); + + ServiceLocator* service_locator_; + + // The object from which error status is retreived. + IErrorStatus* error_status_; + + // The Pack into which newly-created nodes will be placed. + Pack* pack_; + + // The import options to use; + Options options_; + + // The effect used if we can't create an effect. + Effect* dummy_effect_; + + // The material used if we can't create a material. + Material* dummy_material_; + + // The root of the instance node tree. + NodeInstance* instance_root_; + + // A map of the Textures created by the importer, indexed by filename. + std::map<const std::wstring, Texture*> textures_; + + // A map containing the original data (still in original format) + // used to create the textures, sounds, etc., indexed by filename. + typedef std::map<FilePath, std::string> OriginalDataMap; + OriginalDataMap original_data_; + + // A map of the Effects created by the importer, indexed by DAE id. + std::map<const std::string, Effect*> effects_; + + // A map of the Shapes created by the importer, indexed by DAE id. + std::map<const std::string, Shape*> shapes_; + + // A map of the Skinned Shapes created by the importer, indexed by DAE id. + std::map<const std::string, Shape*> skinned_shapes_; + + // A map of the Materials created by the importer, indexed by DAE id. + std::map<const std::string, Material*> materials_; + + // All the errors accumlated during loading. + String errors_; + + // The absolute path to the top of the model hierarchy, to use for + // determining the relative paths to other files. + FilePath base_path_; + + ColladaZipArchive *collada_zip_archive_; + // Some temporaries used by the state importer + bool cull_enabled_; + bool cull_front_; + bool front_cw_; + + int unique_filename_counter_; + + DISALLOW_COPY_AND_ASSIGN(Collada); +}; +} +#endif // O3D_IMPORT_CROSS_COLLADA_H_ diff --git a/o3d/import/cross/collada_conditioner.cc b/o3d/import/cross/collada_conditioner.cc new file mode 100644 index 0000000..484865f --- /dev/null +++ b/o3d/import/cross/collada_conditioner.cc @@ -0,0 +1,578 @@ +/* + * 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. + */ + + +#include "import/cross/precompile.h" + +#include "import/cross/collada_conditioner.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/string_util.h" +#include "compiler/technique/technique_parser.h" +#include "core/cross/error.h" +#include "core/cross/types.h" +#include "import/cross/collada.h" +#include "import/cross/collada_zip_archive.h" +#include "utils/cross/file_path_utils.h" +#include "utils/cross/temporary_file.h" + +namespace o3d { +namespace { +FUDaeTextureFilterFunction::FilterFunction LookupFilterFunction( + const char* name) { + struct { + const char* name; + FUDaeTextureFilterFunction::FilterFunction func; + } functions[] = { + "None", FUDaeTextureFilterFunction::NONE, + "Linear", FUDaeTextureFilterFunction::LINEAR, + "Point", FUDaeTextureFilterFunction::NEAREST, // DX + "Nearest", FUDaeTextureFilterFunction::NEAREST, // GL + "LinearMipmapLinear", FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR, + "LinearMipmapNearest", FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST, + "NearestMipmapNearest", FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST, + "NearestMipmapLinear", FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR, + // TODO: Once FCollada supports the COLLADA v1.5 spec, + // turn this on. + // "Anisotropic", FUDaeTextureFilterFunction::ANISOTROPIC, + }; + for (int i = 0; i < sizeof(functions) / sizeof(functions[0]); ++i) { + if (!base::strcasecmp(functions[i].name, name)) { + return functions[i].func; + } + } + return FUDaeTextureFilterFunction::UNKNOWN; +} + +#undef CLAMP + +FUDaeTextureWrapMode::WrapMode LookupWrapMode(const char* name) { + struct { + const char* name; + FUDaeTextureWrapMode::WrapMode mode; + } modes[] = { + "None", FUDaeTextureWrapMode::NONE, + // DX-style names: + "Wrap", FUDaeTextureWrapMode::WRAP, + "Mirror", FUDaeTextureWrapMode::MIRROR, + "Clamp", FUDaeTextureWrapMode::CLAMP, + "Border", FUDaeTextureWrapMode::BORDER, + // GL-style names: + "Repeat", FUDaeTextureWrapMode::WRAP, + "MirroredRepeat", FUDaeTextureWrapMode::MIRROR, + "ClampToEdge", FUDaeTextureWrapMode::CLAMP, + "ClampToBorder", FUDaeTextureWrapMode::BORDER, + }; + for (int i = 0; i < sizeof(modes) / sizeof(modes[0]); ++i) { + if (!base::strcasecmp(modes[i].name, name)) { + return modes[i].mode; + } + } + return FUDaeTextureWrapMode::UNKNOWN; +} + +FCDEffectParameter* FindParameter(FCDMaterial* material, const char* name) { + for (size_t i = 0; i < material->GetEffectParameterCount(); ++i) { + FCDEffectParameter* p = material->GetEffectParameter(i); + if (!strcmp(p->GetReference(), name)) { + return p; + } + } + return NULL; +} + +void SetSamplerStates(const SamplerState& sampler, + FCDEffectParameterSampler* sampler_out) { + sampler_out->SetReference(sampler.name.c_str()); + sampler_out->SetMinFilter(LookupFilterFunction(sampler.min_filter.c_str())); + sampler_out->SetMagFilter(LookupFilterFunction(sampler.mag_filter.c_str())); + sampler_out->SetMipFilter(LookupFilterFunction(sampler.mip_filter.c_str())); + sampler_out->SetWrapS(LookupWrapMode(sampler.address_u.c_str())); + sampler_out->SetWrapT(LookupWrapMode(sampler.address_u.c_str())); + + // TODO: Once FCollada supports the COLLADA v1.5 spec, turn this on. + // sampler_out->SetMaxAnisotropy(atoi(sampler.max_anisotropy.c_str())); +} + +const PassDeclaration* FindValidTechnique( + const TechniqueDeclarationList& technique_list) { + + // Look for a ps2_0/vs2_0 technique, or an arbfp1/arbvp1 technique + TechniqueDeclarationList::const_iterator i; + for (i = technique_list.begin(); i != technique_list.end(); ++i) { + // Skip all multi-pass techniques + if (i->pass.size() != 1) continue; + + const PassDeclaration& pass = i->pass[0]; + if (pass.vertex_shader_profile == "vs_2_0" && + pass.fragment_shader_profile == "ps_2_0" || + pass.vertex_shader_profile == "arbvp1" && + pass.fragment_shader_profile == "arbfp1") { + return &pass; + } + } + return NULL; +} + +bool IsColumnMajor(const PassDeclaration* pass) { + return pass->vertex_shader_profile == "arbvp1" || + pass->fragment_shader_profile == "arbfp1"; +} +} // end anonymous namespace. + +ColladaConditioner::ColladaConditioner(ServiceLocator* service_locator) + : service_locator_(service_locator) { +} + +ColladaConditioner::~ColladaConditioner() { +} + +bool ColladaConditioner::HandleEmbeddedShaders(FCDEffect* collada_effect, + FCDEffectProfileFX* profile_fx, + ColladaZipArchive* archive) { + bool found = false; + size_t len = profile_fx->GetTechniqueCount(); + for (size_t j = 0; j < len && !found; ++j) { + FCDEffectTechnique* technique = profile_fx->GetTechnique(j); + if (!technique) continue; + // We only support single-pass effects (for now). + if (technique->GetPassCount() != 1) continue; + FCDEffectPass* pass = technique->GetPass(0); + if (!pass) continue; + FCDEffectPassShader* vertex_shader = pass->GetVertexShader(); + if (!vertex_shader) continue; + FCDEffectPassShader* fragment_shader = pass->GetFragmentShader(); + if (!fragment_shader) continue; + // Note: We ignore the compiler targets in the ColladaFX section, + // since they are often wrong (ColladaMAX puts ps 3.0/vs 3.0 in, + // regardless of the actual shader), and consult the shader file + // itself instead. + SamplerStateList sampler_list; + FCDEffectCode* code = vertex_shader->GetCode(); + String shader_source_out; + if (code->GetType() == FCDEffectCode::CODE) { + fstring code_string = code->GetCode(); + String shader_source_in(WideToUTF8(code_string.c_str())); + FilePath stdin_path(FILE_PATH_LITERAL("<stdin>")); + if (RewriteShader(shader_source_in, + &shader_source_out, + stdin_path, + &sampler_list, + NULL, + NULL)) { + found = true; + } + code->SetCode(UTF8ToWide(shader_source_out).c_str()); + } else if (code->GetType() == FCDEffectCode::INCLUDE) { + FilePath file_path(WideToFilePath(code->GetFilename().c_str())); + TemporaryFile temp_file; + if (!TemporaryFile::Create(&temp_file)) { + O3D_ERROR(service_locator_) << "Unable to create temporary file '" + << FilePathToUTF8(temp_file.path()).c_str() + << "' for rewriting shader."; + return false; + } + if (!RewriteShaderFile(archive, + file_path, + temp_file.path(), + &sampler_list, + NULL, + NULL)) { + return false; + } + + // Read the shader from the temp file, and add it to the output map. + String shader_source; + if (!file_util::ReadFileToString(temp_file.path(), &shader_source)) { + O3D_ERROR(service_locator_) << "Unable to read temporary file."; + return false; + } + code->SetCode(UTF8ToWide(shader_source).c_str()); + } + } + + if (!found) { + String effect_name = WideToUTF8(collada_effect->GetName().c_str()); + O3D_ERROR(service_locator_) << "No valid technique found for effect \"" + << effect_name << "\"."; + return false; + } + return true; +} + +// This non-standard effect technique (NV_import) is the one used by +// 3ds Max when exporting files using native DX materials, so +// unfortunately we have to support it. +bool ColladaConditioner::HandleNVImport(FCDocument* doc, + FCDEffect* collada_effect, + const FUUri& original_uri, + ColladaZipArchive* archive) { + FCDExtra* extra = collada_effect->GetExtra(); + // There is no actual type tag in the XML, but FCollada constructs one + // for us anyway. + if (extra && extra->GetTypeCount() > 0) { + FCDEType* type = extra->GetType(0); + if (type) { + FCDETechnique* technique = type->FindTechnique("NV_import"); + if (technique) { + FCDENode* node = technique->FindChildNode("import"); + if (node) { + FCDEAttribute* url_attrib = node->FindAttribute("url"); + if (url_attrib) { + FUFileManager* mgr = doc->GetFileManager(); + DCHECK(mgr != NULL); + // Escape any %hex values in the URL, resolve it relative + // to the document root, and convert it to an absolute path. + fstring url = FUXmlParser::XmlToString(url_attrib->GetValue()); + FUUri effect_uri = original_uri.Resolve(url); + fstring path = effect_uri.GetAbsolutePath(); + + FilePath in_filename(WideToFilePath(path.c_str())); + TemporaryFile temp_file; + if (!TemporaryFile::Create(&temp_file)) { + O3D_ERROR(service_locator_) << "Unable to create temporary file."; + } + SamplerStateList sampler_list; + // Check that the file exists; error if not. + if (!mgr->FileExists(path)) { + O3D_ERROR(service_locator_) << "Shader file \"" + << WideToUTF8(path.c_str()).c_str() + << "\" does not exist."; + return false; + } + + String vs_entry; + String ps_entry; + if (!RewriteShaderFile(archive, + in_filename, + temp_file.path(), + &sampler_list, + &vs_entry, + &ps_entry)) { + return false; + } + + // Create a new HLSL profile to hold the rewritten effect. + FCDEffectProfile* profile = + collada_effect->AddProfile(FUDaeProfileType::HLSL); + FCDEffectProfileFX* profile_fx = + down_cast<FCDEffectProfileFX*>(profile); + // Move the shader file to the COLLADA-FX section. + FCDEffectTechnique* fx_technique = profile_fx->AddTechnique(); + + FCDEffectCode* code = profile_fx->AddCode(); + + // Read the shader from the temp file, and add it to the + // output map. + String shader_source; + if (!file_util::ReadFileToString(temp_file.path(), + &shader_source)) { + O3D_ERROR(service_locator_) << "Unable to read temporary file."; + return false; + } + + // Set the embedded code to the rewritten shader. + code->SetCode(UTF8ToWide(shader_source).c_str()); + + FCDEffectPass* pass = fx_technique->AddPass(); + FCDEffectPassShader* vertex_shader = pass->AddVertexShader(); + FCDEffectPassShader* fragment_shader = + pass->AddFragmentShader(); + + vertex_shader->SetCode(code); + vertex_shader->SetName(vs_entry.c_str()); + + fragment_shader->SetCode(code); + fragment_shader->SetName(ps_entry.c_str()); + + // Change the setparams + SamplerStateList::const_iterator it; + for (it = sampler_list.begin(); it != sampler_list.end(); ++it) { + // Create a <sampler> tag. + FCDEffectParameterSampler* sampler = + down_cast<FCDEffectParameterSampler*>( + profile->AddEffectParameter( + FCDEffectParameter::SAMPLER)); + LOG_ASSERT(sampler != 0); + // Set the sampler parameters from the ones found in + // the FX file. + SetSamplerStates(*it, sampler); + } + + // For each material which uses this effect, + FCDMaterialLibrary* lib = doc->GetMaterialLibrary(); + for (int i = 0; i < lib->GetEntityCount(); ++i) { + FCDMaterial* material = lib->GetEntity(i); + LOG_ASSERT(material != 0); + if (material->GetEffect() == collada_effect) { + // For each sampler in the FX file, + SamplerStateList::const_iterator it; + for (it = sampler_list.begin(); + it != sampler_list.end(); + ++it) { + // Create a <sampler> tag. + FCDEffectParameterSampler* sampler = + down_cast<FCDEffectParameterSampler*>( + material->AddEffectParameter( + FCDEffectParameter::SAMPLER)); + LOG_ASSERT(sampler != 0); + // Set the sampler states from FX file states + SetSamplerStates(*it, sampler); + // Set this sampler to be a modifier, so it appears + // as a <setparam> tag in the COLLADA file. + sampler->SetModifier(); + // Set its surface to be the mapping from the texture + // attribute of the sampler_state in the FX file. + FCDEffectParameter* surface = FindParameter( + material, it->texture.c_str()); + if (surface && surface->GetType() == + FCDEffectParameter::SURFACE) { + sampler->SetSurface( + down_cast<FCDEffectParameterSurface*>(surface)); + } + } + } + } + + // Remove the NV_import technique from the COLLADA DOM. + technique->Release(); + } + } + } + } + } + return true; +} + +// Verifies that all shaders conform to O3D's shader language +// (intersection of Cg and HLSL), and fix the shader and image file URLs +// to be relative to the output file root. +bool ColladaConditioner::ConditionDocument(FCDocument* doc, + ColladaZipArchive* archive) { + FUUri original_uri = doc->GetFileUrl(); + FCDEffectLibrary* effectLibrary = doc->GetEffectLibrary(); + + for (size_t i = 0; i < effectLibrary->GetEntityCount(); ++i) { + FCDEffect* collada_effect = effectLibrary->GetEntity(i); + DCHECK(collada_effect != NULL); + FCDEffectProfile* profile = + collada_effect->FindProfile(FUDaeProfileType::HLSL); + if (!profile) { + profile = collada_effect->FindProfile(FUDaeProfileType::CG); + } + if (profile) { + // Handle embedded shaders. + FCDEffectProfileFX* profile_fx = + static_cast<FCDEffectProfileFX*>(profile); + if (!HandleEmbeddedShaders(collada_effect, profile_fx, archive)) { + return false; + } + } else { + if (!HandleNVImport(doc, collada_effect, original_uri, archive)) { + return false; + } + } + } + return true; +} + +// Rewrites the given shader file to conform to o3d specs. This +// function finds a valid ps2.0/vs2.0 or arbvp/fp technique, writes +// out the entry points in our comment format, and writes out the +// shader without technique blocks. Returns false if in_filename +// can't be opened for reading, out_filename can't be opened for +// writing, or if the shader is not valid (see RewriteShader). If the +// archive is non-NULL, reads its input from the given zip archive +// instead of from the file system. +bool ColladaConditioner::RewriteShaderFile(ColladaZipArchive* archive, + const FilePath& in_filename, + const FilePath& out_filename, + SamplerStateList* sampler_list, + String* vs_entry, + String* ps_entry) { + FilePath input_file = in_filename; + TemporaryFile temporary_output_file; + TemporaryFile temporary_input_file; + if (!TemporaryFile::Create(&temporary_output_file)) { + O3D_ERROR(service_locator_) << "Unable to create temporary file."; + return false; + } + if (archive) { + if (!TemporaryFile::Create(&temporary_input_file)) { + O3D_ERROR(service_locator_) << "Unable to create temporary file."; + return false; + } + input_file = temporary_input_file.path(); + size_t size = 0; + char* contents = archive->GetColladaAssetData(FilePathToUTF8(in_filename), + &size); + if (file_util::WriteFile(input_file, contents, size) == -1) { + O3D_ERROR(service_locator_) << "Unable to write to temporary file."; + return false; + } + } + if (!PreprocessShaderFile(input_file, temporary_output_file.path())) { + return false; + } + String shader_source_in; + if (!file_util::ReadFileToString(temporary_output_file.path(), + &shader_source_in)) { + O3D_ERROR(service_locator_) << "Unable to read temporary file."; + return false; + } + + String shader_source_out; + if (!RewriteShader(shader_source_in, + &shader_source_out, + in_filename, + sampler_list, + vs_entry, + ps_entry)) { + return false; + } + + if (file_util::WriteFile(out_filename, + shader_source_out.c_str(), + shader_source_out.size()) == -1) { + O3D_ERROR(service_locator_) << "Couldn't write temporary shader file " + << FilePathToUTF8(out_filename).c_str(); + return false; + } + return true; +} + +// Rewrites the given shader to conform to o3d specs. +// This function finds a valid ps2.0/vs2.0 or arbvp/fp technique, +// strips the technique block from the input shader, and adds the +// vertex and fragment shader entry points in our comment format. +// Returns false if no valid technique could be found, or if the +// resulting shader cannot not be compiled as both HLSL and Cg. +bool ColladaConditioner::RewriteShader(const String& shader_source_in, + String* shader_source_out, + const FilePath& in_filename, + SamplerStateList* sampler_list, + String* ps_entry, + String* vs_entry) { + TechniqueDeclarationList technique_list; + bool column_major; + + // Parse out the technique block and samplers from the file + String error_string; + if (!ParseFxString(shader_source_in, + shader_source_out, + sampler_list, + &technique_list, + &error_string)) { + O3D_ERROR(service_locator_) << error_string; + return false; + } + + const PassDeclaration* pass = FindValidTechnique(technique_list); + + if (!pass) { + O3D_ERROR(service_locator_) + << "Couldn't find compatible technique in effect file \"" + << FilePathToUTF8(in_filename).c_str() << "\"."; + return false; + } + + if (!CompileHLSL(shader_source_out->c_str(), + pass->vertex_shader_entry, + pass->fragment_shader_entry)) { + O3D_ERROR(service_locator_) << "Shader file \"" + << FilePathToUTF8(in_filename).c_str() + << "\" could not be compiled as HLSL.\n"; + return false; + } + + if (!CompileCg(in_filename, + shader_source_out->c_str(), + pass->vertex_shader_entry, + pass->fragment_shader_entry)) { + O3D_ERROR(service_locator_) << "Shader file \"" + << FilePathToUTF8(in_filename).c_str() + << "\" could not be compiled as Cg.\n"; + return false; + } + + if (vs_entry) *vs_entry = pass->vertex_shader_entry; + if (ps_entry) *ps_entry = pass->fragment_shader_entry; + column_major = IsColumnMajor(pass); + *shader_source_out += "// #o3d VertexShaderEntryPoint "; + *shader_source_out += pass->vertex_shader_entry + "\n"; + *shader_source_out += "// #o3d PixelShaderEntryPoint "; + *shader_source_out += pass->fragment_shader_entry + "\n"; + *shader_source_out += "// #o3d MatrixLoadOrder "; + *shader_source_out += column_major ? "ColumnMajor" : "RowMajor"; + *shader_source_out += "\n"; + + return true; +} + +bool ColladaConditioner::CompileCg(const FilePath& filename, + const String& shader_source, + const String& vs_entry, + const String& ps_entry) { + bool retval = false; + String shader_source_cg = shader_source; + shader_source_cg += + "technique t {\n" + " pass p {\n" + " VertexShader = compile arbvp1 " + vs_entry + "();\n" + " PixelShader = compile arbfp1 " + ps_entry + "();\n" + " }\n" + "};\n"; + + // Create a Cg context in which to compile the given .FX file. + CGcontext context = cgCreateContext(); + + cgGLRegisterStates(context); + + // Create a Cg effect from the FX file. + CGeffect effect = cgCreateEffect(context, shader_source_cg.c_str(), NULL); + if (!effect || !cgIsEffect(effect) || cgGetError() != CG_NO_ERROR) { + const char* errors = cgGetLastListing(context); + String message = FilePathToUTF8(filename) + ":\n"; + if (errors) { + message += String(errors) + "\n"; + } else { + message += "Unknown Cg compilation error.\n"; + } + O3D_ERROR(service_locator_) << message; + } else { + cgDestroyEffect(effect); + retval = true; + } + cgDestroyContext(context); + return retval; +} +} // namespace o3d diff --git a/o3d/import/cross/collada_conditioner.h b/o3d/import/cross/collada_conditioner.h new file mode 100644 index 0000000..72df84d --- /dev/null +++ b/o3d/import/cross/collada_conditioner.h @@ -0,0 +1,136 @@ +/* + * 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 declarations for the functions in the +// O3D COLLADA conditioner namespace. These functions do most of +// the actual work of conditioning and packagaging a DAE file for use +// in O3D. + +#ifndef O3D_IMPORT_CROSS_COLLADA_CONDITIONER_H_ +#define O3D_IMPORT_CROSS_COLLADA_CONDITIONER_H_ + +#include "core/cross/types.h" +#include "compiler/technique/technique_structures.h" + +class FCDEffect; +class FCDEffectProfileFX; +class FCDocument; +class FUUri; +class FilePath; + +namespace o3d { + +class ColladaZipArchive; +class ServiceLocator; + +class ColladaConditioner { + public: + explicit ColladaConditioner(ServiceLocator* service_locator); + ~ColladaConditioner(); + + // This function conditions the given document for use in O3D. This + // mainly includes checking that the referenced shaders compile + // against both the Cg (all platforms) and D3D (windows only) + // runtimes, and converting the shaders to the common shader + // language used by O3D. The converts all the shaders to inline + // "code" shaders instead of "included" shaders. It also handles + // converting "NV_Import" shaders produced by 3dsMax to something + // o3d can read. Returns false on failure. If archive is non-NULL, + // then it attempts to read shader files from the give zip archive + // instead of from the disk. + bool ConditionDocument(FCDocument* doc, ColladaZipArchive* archive); + + // This takes the given shader file and rewrites it to have the + // proper form for O3D. If the archive parameter is non-NULL, then + // the input file is read from the archive instead of from the + // regular filesystem. + bool RewriteShaderFile(ColladaZipArchive* archive, + const FilePath& in_filename, + const FilePath& out_filename, + SamplerStateList* sampler_list, + String* vs_entry, + String* ps_entry); + + // This test-compiles the given shader source with the HLSL + // compiler, if there is one available on the platform. This + // function is implemented separately for each platform. If there + // is no HLSL compiler available on this platform, this function + // returns 'true'. + bool CompileHLSL(const String& shader_source, + const String& vs_entry, + const String& ps_entry); + + // This test-compiles the given shader source with the Cg compiler, + // if there is one available on the platform. This function is + // implemented separately for each platform. If there is no Cg + // compiler available on this platform, this function returns + // 'true'. + bool CompileCg(const FilePath& filename, + const String& shader_source, + const String& vs_entry, + const String& ps_entry); + + protected: + // This preprocesses the given file using the Cgc compiler. It + // doesn't compile the shader, it just preprocesses it. This is + // implemented separately for each platform, since it invokes the + // compiler as a separate process. + bool PreprocessShaderFile(const FilePath& in_filename, + const FilePath& out_filename); + + // This takes the given COLLADA shader source code and rewrites it + // to have the proper form for O3D. It finds the entry points and + // sets vs_entry and ps_entry to what it finds. + bool RewriteShader(const String& shader_source_in, + String* shader_source_out, + const FilePath& in_filename, + SamplerStateList* sampler_list, + String* vs_entry, + String* ps_entry); + + // Handle all the embedded shaders in the document. + bool HandleEmbeddedShaders(FCDEffect* collada_effect, + FCDEffectProfileFX* profile_fx, + ColladaZipArchive* archive); + + bool HandleNVImport(FCDocument* doc, + FCDEffect* collada_effect, + const FUUri& original_uri, + ColladaZipArchive* archive); + private: + ServiceLocator* service_locator_; + + DISALLOW_COPY_AND_ASSIGN(ColladaConditioner); +}; +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_COLLADA_CONDITIONER_H_ diff --git a/o3d/import/cross/collada_conditioner_stub.cc b/o3d/import/cross/collada_conditioner_stub.cc new file mode 100644 index 0000000..6c1be1e --- /dev/null +++ b/o3d/import/cross/collada_conditioner_stub.cc @@ -0,0 +1,90 @@ +/* + * 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. + */ + + +#include "import/cross/precompile.h" + +#include "import/cross/collada_conditioner.h" + +#include "core/cross/service_locator.h" +#include "import/cross/collada.h" + +namespace o3d { +ColladaConditioner::ColladaConditioner(ServiceLocator* service_locator) + : service_locator_(service_locator) { +} + +ColladaConditioner::~ColladaConditioner() { +} + +bool ColladaConditioner::HandleEmbeddedShaders(FCDEffect* collada_effect, + FCDEffectProfileFX* profile_fx, + ColladaZipArchive* archive) { + return true; +} + +bool ColladaConditioner::HandleNVImport(FCDocument* doc, + FCDEffect* collada_effect, + const FUUri& original_uri, + ColladaZipArchive* archive) { + return true; +} + +bool ColladaConditioner::ConditionDocument(FCDocument* doc, + ColladaZipArchive* archive) { + return true; +} + +bool ColladaConditioner::RewriteShaderFile(ColladaZipArchive* archive, + const FilePath& in_filename, + const FilePath& out_filename, + SamplerStateList* sampler_list, + String* vs_entry, + String* ps_entry) { + return true; +} + +bool ColladaConditioner::RewriteShader(const String& shader_source_in, + String* shader_source_out, + const FilePath& in_filename, + SamplerStateList* sampler_list, + String* ps_entry, + String* vs_entry) { + return true; +} + +bool ColladaConditioner::CompileCg(const FilePath& filename, + const String& shader_source, + const String& vs_entry, + const String& ps_entry) { + return true; +} +} // namespace o3d diff --git a/o3d/import/cross/collada_zip_archive.cc b/o3d/import/cross/collada_zip_archive.cc new file mode 100644 index 0000000..45bc523 --- /dev/null +++ b/o3d/import/cross/collada_zip_archive.cc @@ -0,0 +1,80 @@ +/* + * 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. + */ + + +// A basic C++ wrapper for a collada zip file +// it looks for the first .dae file in the archive and is able to resolve +// partial pathnames (from the image URI's in the collada file) to files in the +// archive + +#include "base/string_util.h" +#include "import/cross/collada_zip_archive.h" + +using std::vector; +using std::string; + +namespace o3d { + +ColladaZipArchive::ColladaZipArchive(const std::string &zip_filename, + int *result) + : ZipArchive(zip_filename, result) { + if (result && (*result == UNZ_OK)) { + // look through the archive and locate the first file with a .dae extension + vector<ZipFileInfo> infolist; + GetInformationList(&infolist); + + bool dae_found = false; + for (int i = 0; i < infolist.size(); ++i) { + const char *name = infolist[i].name.c_str(); + int length = strlen(name); + + if (length > 4) { + const char *suffix = name + length - 4; + if (!base::strcasecmp(suffix, ".dae")) { + dae_pathname_ = name; + dae_directory_ = dae_pathname_; + RemoveLastPathComponent(&dae_directory_); + dae_found = true; + break; + } + } + } + + if (!dae_found && result) *result = -1; + } +} + +// Convert paths relative to the collada file to archive paths +char *ColladaZipArchive::GetColladaAssetData(const string &filename, + size_t *size) { + return GetRelativeFileData(filename, dae_directory_, size); +} +} // end namespace o3d diff --git a/o3d/import/cross/collada_zip_archive.h b/o3d/import/cross/collada_zip_archive.h new file mode 100644 index 0000000..018b3bf --- /dev/null +++ b/o3d/import/cross/collada_zip_archive.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + + +// A basic C++ wrapper for a collada zip file +// it looks for the first .dae file in the archive and is able to resolve +// partial pathnames (from the URI's in the collada file) to files in the +// archive + +#ifndef O3D_IMPORT_CROSS_COLLADA_ZIP_ARCHIVE_H_ +#define O3D_IMPORT_CROSS_COLLADA_ZIP_ARCHIVE_H_ + +#include "import/cross/zip_archive.h" + +#include <string> +#include <vector> + +namespace o3d { + +class ColladaZipArchive : public ZipArchive { + public: + ColladaZipArchive(const std::string &zip_filename, int *result); + + // |filename| is taken to be relative to the directory containing the + // first collada file found in the archive. It may contain relative path + // elements ("../"). These are the types of file references to images + // contained in the collada file. + // + // Extracts a single file and returns a pointer to the file's content. + // Returns NULL if |filename| doesn't match any in the archive + // or an error occurs. The caller must call free() on the returned pointer + // + virtual char *GetColladaAssetData(const std::string &filename, + size_t *size); + + const std::string& GetColladaPath() const { return dae_pathname_; } + const std::string& GetColladaDirectory() const { return dae_directory_; } + + protected: + std::string dae_pathname_; + std::string dae_directory_; +}; +} // end namespace o3d + +#endif // O3D_IMPORT_CROSS_COLLADA_ZIP_ARCHIVE_H_ diff --git a/o3d/import/cross/file_output_stream_processor.cc b/o3d/import/cross/file_output_stream_processor.cc new file mode 100644 index 0000000..3983faa --- /dev/null +++ b/o3d/import/cross/file_output_stream_processor.cc @@ -0,0 +1,52 @@ +/* + * 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 declaration of class FileOutputStreamProcessor. + +#include "base/logging.h" +#include "import/cross/file_output_stream_processor.h" + +namespace o3d { +FileOutputStreamProcessor::FileOutputStreamProcessor(FILE* file) + : file_(file) { + DCHECK(file != NULL); +} + +int FileOutputStreamProcessor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + size_t num_written = fwrite(stream->GetDirectMemoryPointer(), + 1, + bytes_to_process, + file_); + return num_written == bytes_to_process ? 0 : -1; +} +} // namespace o3d diff --git a/o3d/import/cross/file_output_stream_processor.h b/o3d/import/cross/file_output_stream_processor.h new file mode 100644 index 0000000..3c04562 --- /dev/null +++ b/o3d/import/cross/file_output_stream_processor.h @@ -0,0 +1,55 @@ +/* + * 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 declaration of class FileOutputStreamProcessor. + +#ifndef O3D_IMPORT_CROSS_FILE_OUTPUT_STREAM_PROCESSOR_H_ +#define O3D_IMPORT_CROSS_FILE_OUTPUT_STREAM_PROCESSOR_H_ + +#include "import/cross/memory_stream.h" + +namespace o3d { + +// A StringReader accepts binary data and writes it to a file. +class FileOutputStreamProcessor : public StreamProcessor { + public: + explicit FileOutputStreamProcessor(FILE* file); + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + private: + FILE* file_; + DISALLOW_COPY_AND_ASSIGN(FileOutputStreamProcessor); +}; +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_FILE_OUTPUT_STREAM_PROCESSOR_H_ diff --git a/o3d/import/cross/gz_compressor.cc b/o3d/import/cross/gz_compressor.cc new file mode 100644 index 0000000..2352405 --- /dev/null +++ b/o3d/import/cross/gz_compressor.cc @@ -0,0 +1,149 @@ +/* + * 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. + */ + +// +// GzCompressor compresses a byte stream using gzip compression +// calling the client's ProcessBytes() method with the compressed stream +// + +#include "import/cross/gz_compressor.h" + +#include <assert.h> +#include "import/cross/memory_buffer.h" +#include "import/cross/memory_stream.h" +#include "third_party/zlib/files/zutil.h" + +const size_t kChunkSize = 16384; + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +GzCompressor::GzCompressor(StreamProcessor *callback_client) + : callback_client_(callback_client), + stream_is_closed_(false) { + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // Store this, so we can later check if it's OK to start processing + init_result_ = deflateInit2( + &strm_, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + MAX_WBITS + 16, // 16 means write out gzip header/trailer + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void GzCompressor::Finalize() { + if (!stream_is_closed_) { + // Flush the compression stream + MemoryReadStream stream(NULL, 0); + CompressBytes(&stream, 0, true); + + // Deallocate resources + deflateEnd(&strm_); + stream_is_closed_ = true; + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +GzCompressor::~GzCompressor() { + // Finalize() turns out to be a "nop" if the user has already called it + Finalize(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int GzCompressor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // Basic sanity check + if (stream->GetDirectMemoryPointer() == NULL || bytes_to_process == 0) { + return -1; + } + + return CompressBytes(stream, bytes_to_process, false); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int GzCompressor::CompressBytes(MemoryReadStream *stream, + size_t bytes_to_process, + bool flush) { + // Don't even bother trying if we didn't get initialized properly + if (init_result_ != Z_OK) return init_result_; + + uint8 out[kChunkSize]; + int result = Z_OK; + + // Don't try to read more than our stream has + int remaining = stream->GetRemainingByteCount(); + if (bytes_to_process > remaining) { + return Z_STREAM_ERROR; + } + + // Use direct memory access on the MemoryStream object + const uint8 *input_data = stream->GetDirectMemoryPointer(); + stream->Skip(bytes_to_process); + + // Fill out the zlib z_stream struct + strm_.avail_in = bytes_to_process; + strm_.next_in = const_cast<uint8*>(input_data); + + // We need to flush the stream when we reach the end + int flush_code = flush ? Z_FINISH : Z_NO_FLUSH; + + // Run inflate() on input until output buffer not full + do { + strm_.avail_out = kChunkSize; + strm_.next_out = out; + + result = deflate(&strm_, flush_code); + + // error check here - return error codes if necessary + assert(result != Z_STREAM_ERROR); // state not clobbered + + size_t have = kChunkSize - strm_.avail_out; + + // Callback with the compressed byte stream + MemoryReadStream decompressed_stream(out, have); + if (have > 0 && callback_client_) { + int client_result = + callback_client_->ProcessBytes(&decompressed_stream, have); + if (client_result != 0) { + return client_result; // propagate callback errors + } + } + } while (strm_.avail_out == 0); + + return result; +} + +} // namespace o3d diff --git a/o3d/import/cross/gz_compressor.h b/o3d/import/cross/gz_compressor.h new file mode 100644 index 0000000..7c88657 --- /dev/null +++ b/o3d/import/cross/gz_compressor.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +// +// GzCompressor compresses a byte stream using gzip compression +// calling the client's ProcessBytes() method with the compressed stream +// + +#ifndef O3D_IMPORT_CROSS_GZ_COMPRESSOR_H_ +#define O3D_IMPORT_CROSS_GZ_COMPRESSOR_H_ + +#include "base/basictypes.h" +#include "third_party/zlib/files/zlib.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class GzCompressor : public StreamProcessor { + public: + explicit GzCompressor(StreamProcessor *callback_client); + virtual ~GzCompressor(); + + virtual int ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process); + + // Must call when all bytes to compress have been sent (with ProcessBytes) + void Finalize(); + + private: + int CompressBytes(MemoryReadStream *stream, + size_t bytes_to_process, + bool flush); + + z_stream strm_; // low-level zlib state + int init_result_; + bool stream_is_closed_; + StreamProcessor *callback_client_; + + DISALLOW_COPY_AND_ASSIGN(GzCompressor); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_GZ_COMPRESSOR_H_ diff --git a/o3d/import/cross/gz_compressor_test.cc b/o3d/import/cross/gz_compressor_test.cc new file mode 100644 index 0000000..787ab06 --- /dev/null +++ b/o3d/import/cross/gz_compressor_test.cc @@ -0,0 +1,161 @@ +/* + * 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. + */ + + +#include <sys/stat.h> +#include <string> + +#include "core/cross/client.h" +#include "import/cross/memory_stream.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/gz_decompressor.h" +#include "import/cross/gz_compressor.h" +#include "tests/common/win/testing_common.h" +#include "tests/common/cross/test_utils.h" + +namespace o3d { + +class GzCompressorTest : public testing::Test { +}; + +using o3d::MemoryReadStream; +using o3d::MemoryWriteStream; +using o3d::GzCompressor; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class for receiving the decompressed byte stream +class DecompressorClient : public o3d::StreamProcessor { + public: + explicit DecompressorClient(size_t uncompressed_byte_size) + : buffer_(uncompressed_byte_size), + write_stream_(buffer_, uncompressed_byte_size) {} + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // Make sure the output stream isn't full yet + EXPECT_TRUE(write_stream_.GetRemainingByteCount() >= bytes_to_process); + + // Buffer the decompressed bytes + const uint8 *p = stream->GetDirectMemoryPointer(); + stream->Skip(bytes_to_process); + write_stream_.Write(p, bytes_to_process); + + return 0; // return OK + } + + void VerifyDecompressedOutput(uint8 *original_data) { + // Make sure we filled up the output buffer + EXPECT_EQ(0, write_stream_.GetRemainingByteCount()); + + // Verify decompressed data with the original data we fed into the + // compressor + EXPECT_EQ(0, memcmp(original_data, buffer_, buffer_.GetLength())); + } + + private: + size_t uncompressed_byte_size_; + MemoryBuffer<uint8> buffer_; + MemoryWriteStream write_stream_; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class for receiving the compressed byte stream +class CompressorClient : public o3d::StreamProcessor { + public: + explicit CompressorClient(size_t uncompressed_byte_size) + : decompressor_client_(uncompressed_byte_size), + decompressor_(&decompressor_client_) { + }; + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // We're receiving compressed bytes here, so feed them back into + // the decompressor. Since we're making a compression / decompression + // round trip, we should end up with the same (initial) byte stream + // we can verify this at the end + int result = decompressor_.ProcessBytes(stream, bytes_to_process); + + // Verify the result code: + // zlib FAQ says Z_BUF_ERROR is OK + // and may occur in the middle of decompressing the stream + EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END || + result == Z_BUF_ERROR); + + return 0; // no error + } + + void VerifyDecompressedOutput(uint8 *original_data) { + // Send it on to the client, since its the one with the buffered data + decompressor_client_.VerifyDecompressedOutput(original_data); + } + + private: + DecompressorClient decompressor_client_; + o3d::GzDecompressor decompressor_; +}; + + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// int main (int argc, const char * argv[]) { +TEST_F(GzCompressorTest, RoundTripCompressionDecompression) { + // We'll compress this file + String filepath = *g_program_path + "/archive_files/keyboard.jpg"; + + size_t file_length; + uint8 *p = test_utils::ReadFile(filepath.c_str(), &file_length); + ASSERT_TRUE(p != NULL); + + MemoryReadStream input_file_stream(p, file_length); + + // Gets callbacks with compressed bytes + CompressorClient compressor_client(file_length); + + GzCompressor compressor(&compressor_client); + + // Compress the entire file contents. + // |compressor_client| will take the compressed byte stream and send it + // directly to a decompressor. + // + // Since we're making a compression / decompression + // round trip, we should end up with the same (initial) byte stream + // we can verify this at the end by calling VerifyDecompressedOutput() + int result = compressor.ProcessBytes(&input_file_stream, file_length); + EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END); + + compressor.Finalize(); + + compressor_client.VerifyDecompressedOutput(p); + + free(p); +} + +} // namespace o3d diff --git a/o3d/import/cross/gz_decompressor.cc b/o3d/import/cross/gz_decompressor.cc new file mode 100644 index 0000000..333a999 --- /dev/null +++ b/o3d/import/cross/gz_decompressor.cc @@ -0,0 +1,121 @@ +/* + * 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. + */ + +// +// GzDecompressor decompresses a gzip compressed byte stream +// calling the client's ProcessBytes() method with the uncompressed stream +// + +#include "import/cross/gz_decompressor.h" + +#include <assert.h> +#include "import/cross/memory_stream.h" + +const size_t kChunkSize = 16384; + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +GzDecompressor::GzDecompressor(StreamProcessor *callback_client) + : callback_client_(callback_client) { + // Initialize inflate state + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + strm_.avail_in = 0; + strm_.next_in = Z_NULL; + + // Store this, so we can later check if it's OK to start processing + init_result_ = inflateInit2(&strm_, 31); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +GzDecompressor::~GzDecompressor() { + inflateEnd(&strm_); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +int GzDecompressor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // Don't even bother trying if we didn't get initialized properly + if (init_result_ != Z_OK) return init_result_; + + uint8 out[kChunkSize]; + int result; + size_t have; + + // Don't try to read more than our stream has + int remaining = stream->GetRemainingByteCount(); + if (bytes_to_process > remaining) { + return Z_STREAM_ERROR; // could have our own error code, but good enough... + } + + // Use direct memory access on the MemoryStream object + strm_.avail_in = bytes_to_process; + strm_.next_in = const_cast<uint8*>(stream->GetDirectMemoryPointer()); + stream->Skip(bytes_to_process); + + // Run inflate() on input until output buffer not full + do { + strm_.avail_out = kChunkSize; + strm_.next_out = out; + + result = inflate(&strm_, Z_NO_FLUSH); + + // error check here - return error codes if necessary + assert(result != Z_STREAM_ERROR); /* state not clobbered */ + switch (result) { + case Z_NEED_DICT: + result = Z_DATA_ERROR; /* and fall through */ + + case Z_DATA_ERROR: + case Z_MEM_ERROR: + return result; + } + + have = kChunkSize - strm_.avail_out; + + // Callback with the decompressed byte stream + MemoryReadStream decompressed_stream(out, have); + if (callback_client_) { + int client_result = + callback_client_->ProcessBytes(&decompressed_stream, have); + if (client_result != 0) { + return client_result; // propagate callback errors + } + } + } while (strm_.avail_out == 0); + + return result; +} + +} // namespace o3d diff --git a/o3d/import/cross/gz_decompressor.h b/o3d/import/cross/gz_decompressor.h new file mode 100644 index 0000000..833a7b3 --- /dev/null +++ b/o3d/import/cross/gz_decompressor.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// +// GzDecompressor decompresses a gzip compressed byte stream +// calling the client's ProcessBytes() method with the uncompressed stream +// + +#ifndef O3D_IMPORT_CROSS_GZ_DECOMPRESSOR_H_ +#define O3D_IMPORT_CROSS_GZ_DECOMPRESSOR_H_ + +#include "base/basictypes.h" +#include "third_party/zlib/files/zlib.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class GzDecompressor : public StreamProcessor { + public: + explicit GzDecompressor(StreamProcessor *callback_client); + virtual ~GzDecompressor(); + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + private: + z_stream strm_; // low-level zlib state + int init_result_; + StreamProcessor *callback_client_; + + DISALLOW_COPY_AND_ASSIGN(GzDecompressor); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_GZ_DECOMPRESSOR_H_ diff --git a/o3d/import/cross/gz_decompressor_test.cc b/o3d/import/cross/gz_decompressor_test.cc new file mode 100644 index 0000000..af45ecd --- /dev/null +++ b/o3d/import/cross/gz_decompressor_test.cc @@ -0,0 +1,142 @@ +/* + * 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. + */ + + +#include <sys/stat.h> +#include <algorithm> + +#include "core/cross/client.h" +#include "import/cross/gz_decompressor.h" +#include "import/cross/memory_buffer.h" +#include "tests/common/win/testing_common.h" +#include "tests/common/cross/test_utils.h" + +using test_utils::ReadFile; + +namespace o3d { + +class GzDecompressorTest : public testing::Test { +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Receives bytes from the decompressor and buffers them +// +class GzTestClient : public StreamProcessor { + public: + explicit GzTestClient(size_t uncompressed_size) { + buffer_.Allocate(uncompressed_size); + stream_.Assign(buffer_, uncompressed_size); + } + + virtual int ProcessBytes(MemoryReadStream *input_stream, + size_t bytes_to_process) { + // Buffer the uncompressed bytes we're given + const uint8 *p = input_stream->GetDirectMemoryPointer(); + input_stream->Skip(bytes_to_process); + + size_t bytes_written = stream_.Write(p, bytes_to_process); + EXPECT_EQ(bytes_written, bytes_to_process); + + return Z_OK; + } + + // When we're done decompressing, we can check the results here + uint8 *GetResultBuffer() { return buffer_; } + size_t GetResultLength() const { return stream_.GetStreamPosition(); } + + private: + MemoryBuffer<uint8> buffer_; + MemoryWriteStream stream_; +}; + +// Loads a tar.gz file, runs it through the processor. +// In our callbacks, we verify that we receive three files with known contents +// +TEST_F(GzDecompressorTest, LoadGzFile) { + String compressed_file = *g_program_path + "/archive_files/keyboard.jpg.gz"; + String uncompressed_file = *g_program_path + "/archive_files/keyboard.jpg"; + + // Read the compressed and uncompressed files into memory. + // We can then run the decompressor and check it against the expected + // uncompressed data... + size_t compressed_size; + size_t uncompressed_size; + uint8 *compressed_data = ReadFile(compressed_file, &compressed_size); + uint8 *expected_uncompressed_data = + ReadFile(uncompressed_file, &uncompressed_size); + + ASSERT_TRUE(compressed_data != NULL); + ASSERT_TRUE(expected_uncompressed_data != NULL); + + // Gets callbacks for the uncompressed data + GzTestClient decompressor_client(uncompressed_size); + + // The class we're testing... + GzDecompressor decompressor(&decompressor_client); + + // Now that we've read the compressed file into memory, lets + // feed it, a chunk at a time, into the decompressor + const int kChunkSize = 512; + + MemoryReadStream compressed_stream(compressed_data, compressed_size); + size_t bytes_to_process = compressed_size; + + int result = Z_OK; + while (bytes_to_process > 0) { + size_t bytes_this_time = + bytes_to_process < kChunkSize ? bytes_to_process : kChunkSize; + + result = decompressor.ProcessBytes(&compressed_stream, bytes_this_time); + EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END); + + bytes_to_process -= bytes_this_time; + } + + // When the decompressor has finished it should return Z_STREAM_END + EXPECT_TRUE(result == Z_STREAM_END); + + // Now let's verify that what we just decompressed matches exactly + // what's in the reference file... + + // First check that the lengths match + EXPECT_EQ(decompressor_client.GetResultLength(), uncompressed_size); + + // Now check the data + result = memcmp(decompressor_client.GetResultBuffer(), + expected_uncompressed_data, + uncompressed_size); + EXPECT_EQ(0, result); + + free(compressed_data); + free(expected_uncompressed_data); +} + +} // namespace o3d diff --git a/o3d/import/cross/iarchive_generator.h b/o3d/import/cross/iarchive_generator.h new file mode 100644 index 0000000..4f662da --- /dev/null +++ b/o3d/import/cross/iarchive_generator.h @@ -0,0 +1,62 @@ +/* + * 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 declaration of class IArchiveGenerator. + +#ifndef O3D_IMPORT_CROSS_IARCHIVE_GENERATOR_H_ +#define O3D_IMPORT_CROSS_IARCHIVE_GENERATOR_H_ + +#include "core/cross/types.h" + +namespace o3d { + +class MemoryReadStream; + +class IArchiveGenerator { + public: + virtual ~IArchiveGenerator() { + } + + // Call AddFile() for each file entry, followed by calls to AddFileBytes() + // for the file's data + virtual void AddFile(const String& file_name, + size_t file_size) = 0; + + // Call with the file's data (after calling AddFile) + // may be called one time will all the file's data, or multiple times + // until all the data is provided + virtual int AddFileBytes(MemoryReadStream* stream, size_t n) = 0; +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_IARCHIVE_GENERATOR_H_ diff --git a/o3d/import/cross/memory_buffer.h b/o3d/import/cross/memory_buffer.h new file mode 100644 index 0000000..8d5640f --- /dev/null +++ b/o3d/import/cross/memory_buffer.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + + +// A MemoryBuffer object represents an array of type T. +// Please note that T is really intended to be used +// for integral types (char, int, float, double) +// It's very useful when used as a stack-based object or +// member variable, offering a cleaner, more direct alternative, +// in some cases, to using smart-pointers in conjunction with operator new +// +// Example usage: +// +// MemoryBuffer<int> buffer(1024); +// for (int i = 0; i < 1024; ++i) { +// buffer[i] = i; +// } + +#ifndef O3D_IMPORT_CROSS_MEMORY_BUFFER_H_ +#define O3D_IMPORT_CROSS_MEMORY_BUFFER_H_ + +#include <string.h> +#include <stdlib.h> +#include <vector> + +#include "base/basictypes.h" + +// Allocates an array of type T + +template <class T> +class MemoryBuffer { + public: + MemoryBuffer() {}; + explicit MemoryBuffer(size_t num_elems) : vector_(num_elems, 0) { } + + void Allocate(size_t num_elems) { AllocateClear(num_elems); } + void AllocateClear(size_t num_elems) { vector_.assign(num_elems, 0); } + void Deallocate() { vector_.clear(); } + void Clear() { AllocateClear(vector_.size()); } // sets to all zero values + void Resize(size_t n) { vector_.resize(n); } + size_t GetLength() { return vector_.size(); } + operator T *() { return &vector_[0]; } + + private: + std::vector<T> vector_; + + DISALLOW_COPY_AND_ASSIGN(MemoryBuffer); +}; + +#endif // O3D_IMPORT_CROSS_MEMORY_BUFFER_H_ diff --git a/o3d/import/cross/memory_buffer_test.cc b/o3d/import/cross/memory_buffer_test.cc new file mode 100644 index 0000000..59cb721 --- /dev/null +++ b/o3d/import/cross/memory_buffer_test.cc @@ -0,0 +1,98 @@ +/* + * 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. + */ + + +// Tests functionality of the MemoryBuffer class + +#include "core/cross/client.h" +#include "tests/common/win/testing_common.h" +#include "core/cross/error.h" +#include "import/cross/memory_buffer.h" + +namespace o3d { + +// Test fixture for RawData testing. +class MemoryBufferTest : public testing::Test { +}; + +// Test RawData +TEST_F(MemoryBufferTest, Basic) { + int i; + MemoryBuffer<int> buffer; + // Check that initially the buffer is not allocated + ASSERT_EQ(0, buffer.GetLength()); + + // Allocate and check the length is good + const int kBufferLength = 1024; + buffer.Allocate(kBufferLength); + ASSERT_EQ(kBufferLength, buffer.GetLength()); + + // Once allocated, the initial contents should be zero + // Check that the buffer contents are zeroed out + bool buffer_is_cleared = true; + for (i = 0; i < kBufferLength; ++i) { + if (buffer[i] != 0) { + buffer_is_cleared = false; + break; + } + } + ASSERT_TRUE(buffer_is_cleared); + + // Write some values and check that they're OK + for (i = 0; i < kBufferLength; ++i) { + buffer[i] = i; + } + bool buffer_values_good = true; + for (i = 0; i < kBufferLength; ++i) { + if (buffer[i] != i) { + buffer_values_good = false; + break; + } + } + ASSERT_TRUE(buffer_values_good); + + // Now, clear the buffer and check that it worked + buffer.Clear(); + buffer_is_cleared = true; + for (i = 0; i < kBufferLength; ++i) { + if (buffer[i] != 0) { + buffer_is_cleared = false; + break; + } + } + ASSERT_TRUE(buffer_is_cleared); + + // Deallocate the buffer and verify its length + buffer.Deallocate(); + ASSERT_EQ(0, buffer.GetLength()); +} + +} // namespace o3d diff --git a/o3d/import/cross/memory_stream.cc b/o3d/import/cross/memory_stream.cc new file mode 100644 index 0000000..9a4d6c5 --- /dev/null +++ b/o3d/import/cross/memory_stream.cc @@ -0,0 +1,401 @@ +/* + * 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. + */ + +// +// MemoryReadStream and MemoryWriteStream are simple stream wrappers around +// memory buffers. + +#include "import/cross/memory_stream.h" +#include "core/cross/types.h" + +#if !defined(IS_LITTLE_ENDIAN) && !defined(IS_BIG_ENDIAN) +#error "IS_LITTLE_ENDIAN or IS_BIG_ENDIAN must be defined!" +#endif + +namespace o3d { + +namespace { +union Int16Union { + uint8 c[2]; + int16 i; +}; + +union UInt16Union { + uint8 c[2]; + uint16 i; +}; + +union Int32Union { + uint8 c[4]; + int32 i; +}; + +union UInt32Union { + uint8 c[4]; + uint32 i; +}; + +union Int32FloatUnion { + int32 i; + float f; +}; + +union UInt32FloatUnion { + uint32 i; + float f; +}; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static int16 SwapInt16(const int16 *value) { + // endian-swap two bytes + uint8 p[2]; + const char *q = reinterpret_cast<const char*>(value); + p[0] = q[1]; + p[1] = q[0]; + + return *reinterpret_cast<int16*>(p); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static uint16 SwapUInt16(const uint16 *value) { + // endian-swap two bytes + uint8 p[2]; + const char *q = reinterpret_cast<const char*>(value); + p[0] = q[1]; + p[1] = q[0]; + + return *reinterpret_cast<const uint16*>(p); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static int32 SwapInt32(const int32 *value) { + // endian-swap four bytes + char p[4]; + const char *q = reinterpret_cast<const char*>(value); + p[0] = q[3]; + p[1] = q[2]; + p[2] = q[1]; + p[3] = q[0]; + + return *reinterpret_cast<const int32*>(p); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static uint32 SwapUInt32(const uint32 *value) { + // endian-swap four bytes + char p[4]; + const char *q = reinterpret_cast<const char*>(value); + p[0] = q[3]; + p[1] = q[2]; + p[2] = q[1]; + p[3] = q[0]; + + return *reinterpret_cast<const uint32*>(p); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int16 MemoryReadStream::ReadLittleEndianInt16() { + Int16Union u; + Read(u.c, 2); + +#ifdef IS_BIG_ENDIAN + return SwapInt16(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint16 MemoryReadStream::ReadLittleEndianUInt16() { + UInt16Union u; + Read(u.c, 2); + +#ifdef IS_BIG_ENDIAN + return SwapUint16(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int16 MemoryReadStream::ReadBigEndianInt16() { + Int16Union u; + Read(u.c, 2); + +#ifdef IS_LITTLE_ENDIAN + return SwapInt16(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint16 MemoryReadStream::ReadBigEndianUInt16() { + UInt16Union u; + Read(u.c, 2); + +#ifdef IS_LITTLE_ENDIAN + return SwapUInt16(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int32 MemoryReadStream::ReadLittleEndianInt32() { + Int32Union u; + Read(u.c, 4); + +#ifdef IS_BIG_ENDIAN + return SwapInt32(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint32 MemoryReadStream::ReadLittleEndianUInt32() { + UInt32Union u; + Read(u.c, 4); + +#ifdef IS_BIG_ENDIAN + return SwapUInt32(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int32 MemoryReadStream::ReadBigEndianInt32() { + Int32Union u; + Read(u.c, 4); + +#ifdef IS_LITTLE_ENDIAN + return SwapInt32(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint32 MemoryReadStream::ReadBigEndianUInt32() { + UInt32Union u; + Read(u.c, 4); + +#ifdef IS_LITTLE_ENDIAN + return SwapUInt32(&u.i); +#else + return u.i; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +float MemoryReadStream::ReadLittleEndianFloat32() { + // Read in as int32 then interpret as float32 + Int32FloatUnion u; + u.i = ReadLittleEndianInt32(); + return u.f; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +float MemoryReadStream::ReadBigEndianFloat32() { + // Read in as int32 then interpret as float32 + Int32FloatUnion u; + u.i = ReadBigEndianInt32(); + return u.f; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteLittleEndianInt16(int16 i) { + Int16Union u; + +#ifdef IS_BIG_ENDIAN + u.i = SwapInt16(&i); +#else + u.i = i; +#endif + + Write(u.c, 2); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteLittleEndianUInt16(uint16 i) { + UInt16Union u; + +#ifdef IS_BIG_ENDIAN + u.i = SwapUInt16(&i); +#else + u.i = i; +#endif + + Write(u.c, 2); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteBigEndianInt16(int16 i) { + Int16Union u; + +#ifdef IS_LITTLE_ENDIAN + u.i = SwapInt16(&i); +#else + u.i = i; +#endif + + Write(u.c, 2); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteBigEndianUInt16(uint16 i) { + UInt16Union u; + +#ifdef IS_LITTLE_ENDIAN + u.i = SwapUInt16(&i); +#else + u.i = i; +#endif + + Write(u.c, 2); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteLittleEndianInt32(int32 i) { + Int32Union u; + +#ifdef IS_BIG_ENDIAN + u.i = SwapInt32(&i); +#else + u.i = i; +#endif + + Write(u.c, 4); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteLittleEndianUInt32(uint32 i) { + UInt32Union u; + +#ifdef IS_BIG_ENDIAN + u.i = SwapUInt32(&i); +#else + u.i = i; +#endif + + Write(u.c, 4); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteBigEndianInt32(int32 i) { + Int32Union u; + +#ifdef IS_LITTLE_ENDIAN + u.i = SwapInt32(&i); +#else + u.i = i; +#endif + + Write(u.c, 4); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteBigEndianUInt32(uint32 i) { + UInt32Union u; + +#ifdef IS_LITTLE_ENDIAN + u.i = SwapUInt32(&i); +#else + u.i = i; +#endif + + Write(u.c, 4); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteLittleEndianFloat32(float f) { + // Interpret byte-pattern of f as int32 and write out + Int32FloatUnion u; + u.f = f; + WriteLittleEndianInt32(u.i); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MemoryWriteStream::WriteBigEndianFloat32(float f) { + // Interpret byte-pattern of f as int32 and write out + Int32FloatUnion u; + u.f = f; + WriteBigEndianInt32(u.i); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// A couple of useful utility functions + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Returns the bytes pointed to by |value| as little-endian 16 +int16 MemoryReadStream::GetLittleEndianInt16(const int16 *value) { +#ifdef IS_BIG_ENDIAN + return SwapInt16(value); +#else + return *value; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Returns the bytes pointed to by |value| as little-endian 16 +uint16 MemoryReadStream::GetLittleEndianUInt16(const uint16 *value) { +#ifdef IS_BIG_ENDIAN + return SwapUInt16(value); +#else + return *value; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Returns the bytes pointed to by |value| as little-endian 32 +int32 MemoryReadStream::GetLittleEndianInt32(const int32 *value) { +#ifdef IS_BIG_ENDIAN + return SwapInt32(value); +#else + return *value; +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Returns the bytes pointed to by |value| as little-endian 32 +uint32 MemoryReadStream::GetLittleEndianUInt32(const uint32 *value) { +#ifdef IS_BIG_ENDIAN + return SwapUint32(value); +#else + return *value; +#endif +} + +} // namespace o3d diff --git a/o3d/import/cross/memory_stream.h b/o3d/import/cross/memory_stream.h new file mode 100644 index 0000000..00585b6 --- /dev/null +++ b/o3d/import/cross/memory_stream.h @@ -0,0 +1,276 @@ +/* + * 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. + */ + +// +// MemoryReadStream and MemoryWriteStream are simple stream wrappers around +// memory buffers. Their constructers take a pointer to the memory and +// the length of the buffer. They are useful for pipeline based processing +// of byte-streams. +// +// MemoryReadStream maintains its stream position +// and can be read with the Read() method, returning the number of bytes +// read (similar to fread()). +// +// MemoryWriteStream maintains its stream position +// and can be written with the Write() method, returning the number of bytes +// actually written (similar to fwrite()). + +#ifndef O3D_IMPORT_CROSS_MEMORY_STREAM_H_ +#define O3D_IMPORT_CROSS_MEMORY_STREAM_H_ + +#include <string.h> +#include "base/basictypes.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class MemoryReadStream { + public: + MemoryReadStream(const uint8 *memory, size_t n) + : memory_(memory), length_(n), read_index_(0) {} + + // Explicit copy constructor + MemoryReadStream(const MemoryReadStream &stream) + : memory_(stream.memory_), + length_(stream.length_), + read_index_(stream.read_index_) {} + + virtual ~MemoryReadStream() {} + + // Similar to fread(), will try to copy |n| bytes to address |p| + // (copies fewer bytes if the stream doesn't have enough data) + // returns the number of bytes copied + virtual size_t Read(void *p, size_t n) { + size_t remaining = GetRemainingByteCount(); + size_t bytes_to_read = (n < remaining) ? n : remaining; + memcpy(p, memory_ + read_index_, bytes_to_read); + read_index_ += bytes_to_read; + return bytes_to_read; + } + + // Attempts to read a complete object of type T + // returns |true| on success + template<typename T> + bool ReadAs(T *p) { + size_t bytes_read = this->Read(reinterpret_cast<char*>(p), + sizeof(T)); + return (bytes_read == sizeof(T)); + } + + // Reads the next byte in the stream (if there are any left) + // If the stream has run dry (is empty) then returns 0 + // To check number of bytes available, call GetRemainingByteCount() + virtual uint8 ReadByte() { + if (read_index_ >= length_) return 0; + + uint8 byte = memory_[read_index_]; + read_index_++; + + return byte; + } + + // 16 and 32bit integer reading for both little and big endian + int16 ReadLittleEndianInt16(); + uint16 ReadLittleEndianUInt16(); + int16 ReadBigEndianInt16(); + uint16 ReadBigEndianUInt16(); + int32 ReadLittleEndianInt32(); + uint32 ReadLittleEndianUInt32(); + int32 ReadBigEndianInt32(); + uint32 ReadBigEndianUInt32(); + + // IEEE 32-bit float reading (little and big endian) + float ReadLittleEndianFloat32(); + float ReadBigEndianFloat32(); + + // Returns the number of bytes left in the stream (which can be read) + size_t GetRemainingByteCount() { + return length_ - read_index_; + }; + + // Returns |true| if the read position is at end-of-stream + // (no more bytes left to read) + bool EndOfStream() { return GetRemainingByteCount() == 0; } + + // Instead of calling Read(), this gives direct access to the data + // without a memcpy(). + // Calling GetRemainingByteCount() will give the number of remaining bytes + // starting at this address... + const uint8 *GetDirectMemoryPointer() { return memory_ + read_index_; } + + // Same as GetDirectMemoryPointer() but returns pointer of desired type + template <typename T> + T *GetDirectMemoryPointerAs() { + return reinterpret_cast<T*>(GetDirectMemoryPointer()); + }; + + // Advances the read position by |n| bytes + void Skip(size_t n) { + size_t remaining = GetRemainingByteCount(); + read_index_ += (n < remaining) ? n : remaining; + } + + // Changes the read position to the given byte offset. + // This allows random access into the stream similar to fseek() + bool Seek(size_t seek_pos) { + bool valid = (seek_pos >= 0 && seek_pos <= length_); + if (valid) { + read_index_ = seek_pos; + } + return valid; + } + + // Returns the total number of bytes in the stream + size_t GetTotalStreamLength() const { return length_; } + + + // Returns the byte position (offset from start of stream) + // this is effectively the number of bytes which have, so far, + // been read + size_t GetStreamPosition() const { return read_index_; } + + // utility methods (swaps the value if necessary) + static int16 GetLittleEndianInt16(const int16 *value); + static uint16 GetLittleEndianUInt16(const uint16 *value); + static int32 GetLittleEndianInt32(const int32 *value); + static uint32 GetLittleEndianUInt32(const uint32 *value); + + protected: + const uint8 *memory_; + size_t read_index_; + size_t length_; + + // Disallow assignment + void operator=(const MemoryReadStream&); +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class MemoryWriteStream { + public: + // May be completely initialized by calling Assign() + MemoryWriteStream() : memory_(NULL), write_index_(0), length_(0) {} + + MemoryWriteStream(uint8 *memory, size_t n) + : memory_(memory), length_(n), write_index_(0) {} + + // Explicit copy constructor + MemoryWriteStream(const MemoryWriteStream &stream) + : memory_(stream.memory_), + length_(stream.length_), + write_index_(stream.write_index_) {} + + virtual ~MemoryWriteStream() {} + + // In the case where the default constructor was used, this is useful + // to assign a pointer to memory and a length in bytes. + void Assign(uint8 *memory, size_t n) { + memory_ = memory; + length_ = n; + write_index_ = 0; + } + + // Changes the write position to the given byte offset. + // This allows random access into the stream similar to fseek() + bool Seek(size_t seek_pos) { + bool valid = (seek_pos >= 0 && seek_pos <= length_); + if (valid) { + write_index_ = seek_pos; + } + return valid; + } + + // Similar to fwrite(), will try to copy |n| bytes from address |p| + // (copies fewer bytes if the stream doesn't have enough data) + // returns the number of bytes copied + virtual size_t Write(const void *p, size_t n) { + size_t remaining = GetRemainingByteCount(); + size_t bytes_to_write = (n < remaining) ? n : remaining; + memcpy(memory_ + write_index_, p, bytes_to_write); + write_index_ += bytes_to_write; + return bytes_to_write; + } + + void WriteByte(uint8 byte) { + Write(&byte, 1); + } + + // 16 and 32bit integer writing for both little and big endian + void WriteLittleEndianInt16(int16 i); + void WriteLittleEndianUInt16(uint16 i); + void WriteBigEndianInt16(int16 i); + void WriteBigEndianUInt16(uint16 i); + void WriteLittleEndianInt32(int32 i); + void WriteLittleEndianUInt32(uint32 i); + void WriteBigEndianInt32(int32 i); + void WriteBigEndianUInt32(uint32 i); + + // IEEE 32-bit float writing (little and big endian) + void WriteLittleEndianFloat32(float f); + void WriteBigEndianFloat32(float f); + + // Returns the number of bytes left in the stream (which can be written) + size_t GetRemainingByteCount() { + return length_ - write_index_; + }; + + // Returns |true| if the read position is at end-of-stream + // (no more bytes left to read) + bool EndOfStream() { return GetRemainingByteCount() == 0; } + + // Returns the total number of bytes in the stream + size_t GetTotalStreamLength() const { return length_; } + + // Returns the byte position (offset from start of stream) + // this is effectively the number of bytes which have, so far, + // been written + size_t GetStreamPosition() const { return write_index_; } + + protected: + uint8 *memory_; + size_t write_index_; + size_t length_; + + // Disallow assignment + void operator=(const MemoryWriteStream&); +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Abstract interface to process a memory stream +class StreamProcessor { + public: + virtual ~StreamProcessor() {} + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) = 0; +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_MEMORY_STREAM_H_ diff --git a/o3d/import/cross/memory_stream_test.cc b/o3d/import/cross/memory_stream_test.cc new file mode 100644 index 0000000..a02fcca --- /dev/null +++ b/o3d/import/cross/memory_stream_test.cc @@ -0,0 +1,320 @@ +/* + * 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. + */ + + +// Tests functionality of the MemoryReadStream and MemoryWriteStream +// classes + +#include "core/cross/client.h" +#include "tests/common/win/testing_common.h" +#include "core/cross/error.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/memory_stream.h" +#include "core/cross/types.h" + +#if !defined(IS_LITTLE_ENDIAN) && !defined(IS_BIG_ENDIAN) +#error "IS_LITTLE_ENDIAN or IS_BIG_ENDIAN must be defined!" +#endif + +namespace o3d { + +static char *kTestString = + "Tests functionality of the MemoryReadStream and MemoryWriteStream classes"; + +// Test fixture for MemoryReadStream and MemoryWriteStream. +class MemoryStreamTest : public testing::Test { +}; + +// Test reading from a buffer using MemoryReadStream +TEST_F(MemoryStreamTest, Read) { + int i; + + // Put a test string in a memory buffer + const int kStringLength = strlen(kTestString); + MemoryBuffer<uint8> buffer(kStringLength); + + for (i = 0; i < kStringLength; ++i) { + buffer[i] = kTestString[i]; + } + + // Now, create a read stream on that buffer and verify that it reads + // correctly... + MemoryReadStream read_stream(buffer, buffer.GetLength()); + + EXPECT_EQ(buffer.GetLength(), read_stream.GetTotalStreamLength()); + EXPECT_EQ(kStringLength, read_stream.GetTotalStreamLength()); + + // Read one byte at a time and verify + uint8 c; + for (i = 0; i < kStringLength; ++i) { + c = read_stream.ReadByte(); + EXPECT_EQ(c, kTestString[i]); + } + // Read an extra byte and verify it's zero + c = read_stream.ReadByte(); + EXPECT_EQ(c, 0); + + // Now, create a 2nd read stream + MemoryReadStream read_stream2(buffer, buffer.GetLength()); + // Get direct memory access and check the pointer is correct + const uint8 *p = read_stream2.GetDirectMemoryPointer(); + EXPECT_EQ(const_cast<uint8*>(p), static_cast<uint8*>(buffer)); + + // Test the Read() method + + // First read 5 bytes + MemoryBuffer<uint8> read_buffer(kStringLength); + int bytes_read = read_stream2.Read(read_buffer, 5); + + // Verify bytes read, stream position and remaining byte count + EXPECT_EQ(5, bytes_read); + EXPECT_EQ(5, read_stream2.GetStreamPosition()); + EXPECT_EQ(kStringLength - 5, read_stream2.GetRemainingByteCount()); + + // Next read the remaining bytes + bytes_read = read_stream2.Read(read_buffer + 5, kStringLength - 5); + EXPECT_EQ(kStringLength - 5, bytes_read); + + // Make sure we read the correct data + EXPECT_EQ(0, memcmp(read_buffer, buffer, kStringLength)); + + // Try to read some more, even though we're at stream end + bytes_read = read_stream2.Read(read_buffer, 1000); + EXPECT_EQ(0, bytes_read); // make sure no bytes were read + + // Now, create a 3rd read stream + MemoryReadStream read_stream3(buffer, buffer.GetLength()); + + // Let's test the Skip() method + read_stream3.Skip(6); // skip over the first 6 bytes + + // Read three bytes in a row and verify + uint8 c1 = read_stream3.ReadByte(); + uint8 c2 = read_stream3.ReadByte(); + uint8 c3 = read_stream3.ReadByte(); + EXPECT_EQ('f', c1); + EXPECT_EQ('u', c2); + EXPECT_EQ('n', c3); +} + +// Test Writing to a buffer using MemoryWriteStream +TEST_F(MemoryStreamTest, Write) { + // Create a write stream without assigning it to memory yet + MemoryWriteStream empty_stream; + + // Verfify length is zero + EXPECT_EQ(0, empty_stream.GetTotalStreamLength()); + + // Now, assign it to the string (OK, we can't really write to + // this memory, but we're just checking the API here + const int kStringLength = strlen(kTestString); + empty_stream.Assign(reinterpret_cast<uint8*>(kTestString), kStringLength); + + // Sanity check on length, position, remaining + EXPECT_EQ(kStringLength, empty_stream.GetTotalStreamLength()); + EXPECT_EQ(0, empty_stream.GetStreamPosition()); + EXPECT_EQ(kStringLength, empty_stream.GetRemainingByteCount()); + + // Create a write stream on a buffer we can write to + MemoryBuffer<uint8> buffer(kStringLength); + MemoryWriteStream write_stream(buffer, buffer.GetLength()); + EXPECT_EQ(buffer.GetLength(), kStringLength); + + // Write 5 bytes + uint8 *p = reinterpret_cast<uint8*>(kTestString); + int bytes_written = write_stream.Write(p, 5); + EXPECT_EQ(5, bytes_written); + EXPECT_EQ(5, write_stream.GetStreamPosition()); + EXPECT_EQ(kStringLength - 5, write_stream.GetRemainingByteCount()); + + // Write the remaining bytes in the string + bytes_written = write_stream.Write(p + 5, kStringLength - 5); + EXPECT_EQ(kStringLength - 5, bytes_written); + EXPECT_EQ(kStringLength, write_stream.GetStreamPosition()); + EXPECT_EQ(0, write_stream.GetRemainingByteCount()); + + // Verify we wrote the correct data + EXPECT_EQ(0, memcmp(buffer, kTestString, kStringLength)); + + // Try to write some more even though the buffer is full + bytes_written = write_stream.Write(p, kStringLength); + EXPECT_EQ(0, bytes_written); +} + +// Basic sanity check for endian writing/reading +TEST_F(MemoryStreamTest, EndianSanity16) { + // Sanity check int16 + MemoryBuffer<int16> buffer16(2); + int16 *p = buffer16; + uint8 *p8 = reinterpret_cast<uint8*>(p); + MemoryWriteStream write_stream(p8, sizeof(int16) * 2); + + int16 value = 0x1234; + write_stream.WriteLittleEndianInt16(value); + write_stream.WriteBigEndianInt16(value); + + // Verify that the bytes are in the correct order + uint8 low_byte = value & 0xff; + uint8 high_byte = value >> 8; + + // validate little-endian + EXPECT_EQ(low_byte, p8[0]); + EXPECT_EQ(high_byte, p8[1]); + + // validate big-endian + EXPECT_EQ(high_byte, p8[2]); + EXPECT_EQ(low_byte, p8[3]); +} + +// Basic sanity check for endian writing/reading +TEST_F(MemoryStreamTest, EndianSanity32) { + // Sanity check int32 + MemoryBuffer<int32> buffer32(2); + int32 *p = buffer32; + uint8 *p8 = reinterpret_cast<uint8*>(p); + MemoryWriteStream write_stream(p8, sizeof(int32) * 2); + + int32 value = 0x12345678; + write_stream.WriteLittleEndianInt32(value); + write_stream.WriteBigEndianInt32(value); + + // Verify that the bytes are in the correct order + uint8 byte1 = value & 0xff; + uint8 byte2 = (value >> 8) & 0xff; + uint8 byte3 = (value >> 16) & 0xff; + uint8 byte4 = (value >> 24) & 0xff; + + // validate little-endian + EXPECT_EQ(byte1, p8[0]); + EXPECT_EQ(byte2, p8[1]); + EXPECT_EQ(byte3, p8[2]); + EXPECT_EQ(byte4, p8[3]); + + // validate big-endian + EXPECT_EQ(byte4, p8[4]); + EXPECT_EQ(byte3, p8[5]); + EXPECT_EQ(byte2, p8[6]); + EXPECT_EQ(byte1, p8[7]); +} + +// Basic sanity check for endian writing/reading +TEST_F(MemoryStreamTest, EndianSanityFloat32) { + // Sanity check int32 + MemoryBuffer<float> buffer32(2); + float *p = buffer32; + uint8 *p8 = reinterpret_cast<uint8*>(p); + MemoryWriteStream write_stream(p8, sizeof(int32) * 2); + + float value = 3.14159; + write_stream.WriteLittleEndianFloat32(value); + write_stream.WriteBigEndianFloat32(value); + + // Verify that the bytes are in the correct order + int32 ivalue = *reinterpret_cast<int32*>(&value); // interpret float as int32 + uint8 byte1 = ivalue & 0xff; + uint8 byte2 = (ivalue >> 8) & 0xff; + uint8 byte3 = (ivalue >> 16) & 0xff; + uint8 byte4 = (ivalue >> 24) & 0xff; + + // validate little-endian + EXPECT_EQ(byte1, p8[0]); + EXPECT_EQ(byte2, p8[1]); + EXPECT_EQ(byte3, p8[2]); + EXPECT_EQ(byte4, p8[3]); + + // validate big-endian + EXPECT_EQ(byte4, p8[4]); + EXPECT_EQ(byte3, p8[5]); + EXPECT_EQ(byte2, p8[6]); + EXPECT_EQ(byte1, p8[7]); +} + +// Write then read int16, int32, and float32 little/big endian values +TEST_F(MemoryStreamTest, Endian) { + const int16 kValue1 = 13243; + const int32 kValue2 = 2393043; + const float kValue3 = -0.039483; + const int16 kValue4 = -3984; + const float kValue5 = 1234.5678; + const uint8 kValue6 = 5; // write a single byte to make things interesting + const int32 kValue7 = -3920393; + + size_t total_size = sizeof(kValue1) + + sizeof(kValue2) + + sizeof(kValue3) + + sizeof(kValue4) + + sizeof(kValue5) + + sizeof(kValue6) + + sizeof(kValue7); + + MemoryBuffer<uint8> buffer(total_size); + + // write the values to the buffer + uint8 *p8 = buffer; + MemoryWriteStream write_stream(p8, total_size); + + write_stream.WriteLittleEndianInt16(kValue1); + write_stream.WriteBigEndianInt32(kValue2); + write_stream.WriteLittleEndianFloat32(kValue3); + write_stream.WriteBigEndianInt16(kValue4); + write_stream.WriteBigEndianFloat32(kValue5); + write_stream.Write(&kValue6, 1); + write_stream.WriteLittleEndianInt32(kValue7); + + // now read them back in and verify + int16 read_value1; + int32 read_value2; + float read_value3; + int16 read_value4; + float read_value5; + uint8 read_value6; + int32 read_value7; + MemoryReadStream read_stream(p8, total_size); + + read_value1 = read_stream.ReadLittleEndianInt16(); + read_value2 = read_stream.ReadBigEndianInt32(); + read_value3 = read_stream.ReadLittleEndianFloat32(); + read_value4 = read_stream.ReadBigEndianInt16(); + read_value5 = read_stream.ReadBigEndianFloat32(); + read_stream.Read(&read_value6, 1); + read_value7 = read_stream.ReadLittleEndianInt32(); + + // Validate + EXPECT_EQ(read_value1, kValue1); + EXPECT_EQ(read_value2, kValue2); + EXPECT_EQ(read_value3, kValue3); + EXPECT_EQ(read_value4, kValue4); + EXPECT_EQ(read_value5, kValue5); + EXPECT_EQ(read_value6, kValue6); + EXPECT_EQ(read_value7, kValue7); +} + +} // namespace o3d diff --git a/o3d/import/cross/precompile.cc b/o3d/import/cross/precompile.cc new file mode 100644 index 0000000..8a70235 --- /dev/null +++ b/o3d/import/cross/precompile.cc @@ -0,0 +1,33 @@ +/* + * 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. + */ + + +#include "import/cross/precompile.h" diff --git a/o3d/import/cross/precompile.h b/o3d/import/cross/precompile.h new file mode 100644 index 0000000..e1daa2b --- /dev/null +++ b/o3d/import/cross/precompile.h @@ -0,0 +1,99 @@ +/* + * 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. + */ + + +#ifndef O3D_IMPORT_CROSS_PRECOMPILE_H_ +#define O3D_IMPORT_CROSS_PRECOMPILE_H_ + +#ifdef OS_WIN +#include <windows.h> +#include <shellapi.h> +#include <stdlib.h> +#include <d3dx9.h> +#endif + +#ifdef __cplusplus + +#include <Cg/cg.h> +#include <Cg/cgGL.h> +#include <FCollada.h> +#include <FCDocument/FCDocument.h> +#include <FCDocument/FCDocumentTools.h> +#include <FCDocument/FCDAnimated.h> +#include <FCDocument/FCDAnimationCurve.h> +#include <FCDocument/FCDAnimationKey.h> +#include <FCDocument/FCDEffect.h> +#include <FCDocument/FCDEffectCode.h> +#include <FCDocument/FCDEffectParameter.h> +#include <FCDocument/FCDEffectParameterSurface.h> +#include <FCDocument/FCDEffectParameterSampler.h> +#include <FCDocument/FCDEffectPass.h> +#include <FCDocument/FCDEffectPassShader.h> +#include <FCDocument/FCDEffectPassState.h> +#include <FCDocument/FCDEffectProfile.h> +#include <FCDocument/FCDEffectProfileFX.h> +#include <FCDocument/FCDEffectStandard.h> +#include <FCDocument/FCDEffectTechnique.h> +#include <FCDocument/FCDExtra.h> +#include <FCDocument/FCDEntity.h> +#include <FCDocument/FCDImage.h> +#include <FCDocument/FCDMaterial.h> +#include <FCDocument/FCDMaterialInstance.h> +#include <FCDocument/FCDLibrary.h> +#include <FCDocument/FCDSceneNode.h> +#include <FCDocument/FCDCamera.h> +#include <FCDocument/FCDGeometry.h> +#include <FCDocument/FCDGeometryInstance.h> +#include <FCDocument/FCDGeometryMesh.h> +#include <FCDocument/FCDGeometryPolygons.h> +#include <FCDocument/FCDGeometryPolygonsInput.h> +#include <FCDocument/FCDGeometryPolygonsTools.h> +#include <FCDocument/FCDGeometrySource.h> +#include <FCDocument/FCDController.h> +#include <FCDocument/FCDControllerInstance.h> +#include <FCDocument/FCDSkinController.h> +#include <FCDocument/FCDTexture.h> +#include <FCDocument/FCDTransform.h> +#include <FUtils/FUFileManager.h> +#include <FUtils/FUUri.h> +#include <FUtils/FUXmlParser.h> + +#ifndef OS_WIN +#include <vector> +using std::min; +using std::max; +#endif + +#include <sstream> + +#endif // __cplusplus + +#endif // O3D_IMPORT_CROSS_PRECOMPILE_H_ diff --git a/o3d/import/cross/raw_data.cc b/o3d/import/cross/raw_data.cc new file mode 100644 index 0000000..067ff70 --- /dev/null +++ b/o3d/import/cross/raw_data.cc @@ -0,0 +1,343 @@ +/* + * 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 implementation for raw-data which may be used +// by the progressive streaming archive system + +#include "import/cross/raw_data.h" +#include "base/file_util.h" + +#ifdef OS_MACOSX +#include <CoreFoundation/CoreFoundation.h> +#endif + +#ifdef OS_WIN +#include <rpc.h> +#endif + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// RawData class +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +O3D_DEFN_CLASS(RawData, ParamObject); + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +RawData::RawData(ServiceLocator* service_locator, + const String &uri, + const void *data, + size_t length) + : ParamObject(service_locator), uri_(uri) { + // make private copy of data + data_.reset(new uint8[length]); + length_ = length; + memcpy(data_.get(), data, length); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +RawData::Ref RawData::Create(ServiceLocator* service_locator, + const String &uri, + const void *data, + size_t length) { + return RawData::Ref(new RawData(service_locator, uri, data, length)); +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +RawData::~RawData() { + Discard(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const uint8 *RawData::GetData() const { + // Return data immediately if we have it + if (data_.get()) { + return data_.get(); + } + + // We need to load the data from the cache file + if (temp_filepath_.empty()) { + DLOG(ERROR) << "cannot retrieve data object - it has been released"; + return NULL; + } + + FILE *tempfile = file_util::OpenFile(temp_filepath_, "rb"); + if (!tempfile) { + DLOG(ERROR) << "cached data file cannot be opened"; + return NULL; + } + + data_.reset(new uint8[length_]); + size_t bytes_read = fread(data_.get(), 1, length_, tempfile); + + if (bytes_read != length_) { + DLOG(ERROR) << "error reading cached data file"; + data_.reset(); + } + + file_util::CloseFile(tempfile); + + return data_.get(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +namespace { + +// Simple UTF8 validation. +// Params: +// data: RawData to validate. +// utf8_length: pointer to size_t to receieve length of UTF8 data. +// Returns: +// the start of the UTF-8 string or NULL if not valid UTF-8. +const char* GetValidUTF8(const RawData& data, size_t* utf8_length) { + DCHECK(utf8_length); + const uint8* s = data.GetDataAs<const uint8>(0); + if (!s) { + return NULL; + } + size_t length = data.GetLength(); + + // Check for BOM and skip it. + if (length >= 3 && s[0] == 0xEF && s[1] == 0xBB && s[2] == 0xBF) { + length -= 3; + s += 3; + } + + const uint8* start = s; + *utf8_length = length; + + while (length) { + uint8 c = *s++; + if (c >= 0x80) { + // It's a multi-byte character + if (c >= 0xC2 && c <= 0xF4) { + uint32 codepoint; + int remaining_code_length = 0; + if ((c & 0xE0) == 0xC0) { + codepoint = c & 0x1F; + remaining_code_length = 1; + } else if ((c & 0xF0) == 0xE0) { + codepoint = c & 0x0F; + remaining_code_length = 2; + } else if ((c & 0xF8) == 0xF0) { + codepoint = c & 0x07; + remaining_code_length = 3; + } + if (remaining_code_length == 0 || remaining_code_length > length) { + // Not valid UTF-8 + return NULL; + } + length -= remaining_code_length; + for (int cc = 0; cc < remaining_code_length; ++cc) { + c = *s++; + if ((c & 0xC0) != 0x80) { + // Not valid UTF-8 + return NULL; + } + codepoint = (codepoint << 6) | (c & 0x3F); + } + if (codepoint >= 0xD800 && codepoint < 0xDFFF) { + // Not valid UTF-8 + return NULL; + } + } else { + // Not valid UTF. + return NULL; + } + } else if (c == 0x00) { + // It's NULL, not UTF-8 + return NULL; + } + --length; + } + return reinterpret_cast<const char*>(start); +}; + +} // anonymous namespace + +String RawData::StringValue() const { + // NOTE: Originally it was thought to only allow certain extensions. + // Unfortunately it's not clear what list of extensions are valid. The list of + // extensions that might be useful to an application is nearly infinite (.txt, + // .json, .xml, .ini, .csv, .php, .js, .html, .css .xsl, .dae, etc.) So, + // instead we validate the string is valid UTF-8 AND that there are no NULLs + // in the string. + size_t length; + const char* utf8 = GetValidUTF8(*this, &length); + if (!utf8) { + O3D_ERROR(service_locator()) << "RawData is not valid UTF-8 string"; + } else { + return String (utf8, length); + } + return String(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void RawData::Flush() { + // Only create the temp file if it doesn't already exist + if (data_.get() && temp_filepath_.empty()) { + if (GetTempFilePathFromURI(uri_, &temp_filepath_)) { + FILE *tempfile = file_util::OpenFile(temp_filepath_, "wb"); + + if (tempfile) { + fwrite(data_.get(), 1, GetLength(), tempfile); + file_util::CloseFile(tempfile); + + // Now that the data is cached, free it + data_.reset(); + } else { + DLOG(ERROR) << "error creating cached data file"; + temp_filepath_ = FilePath(); + } + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const FilePath& RawData::GetTempFilePath() { + Flush(); // writes temp file if it's not already written + return temp_filepath_; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void RawData::Discard() { + data_.reset(); + DeleteTempFile(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool RawData::IsOffsetLengthValid(size_t offset, size_t length) const { + if (offset + length < offset) { + O3D_ERROR(service_locator()) << "overflow"; + return false; + } + if (offset + length > length_) { + O3D_ERROR(service_locator()) << "illegal data offset or size"; + return false; + } + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void RawData::DeleteTempFile() { + if (!temp_filepath_.empty()) { + file_util::Delete(temp_filepath_, false); + temp_filepath_ = FilePath(); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static String GetUUIDString() { +#ifdef OS_WIN + // now generate a GUID + UUID guid = {0}; + UuidCreate(&guid); + + // and format into a wide-string + char guid_string[37]; + snprintf( + guid_string, sizeof(guid_string) / sizeof(guid_string[0]), + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + + return guid_string; +#endif + +#ifdef OS_MACOSX + CFUUIDRef uuid = CFUUIDCreate(NULL); + CFStringRef uuid_string_ref = CFUUIDCreateString(NULL, uuid); + CFRelease(uuid); + + char uuid_string[64]; + uuid_string[0] = 0; // null-terminate, in case CFStringGetCString() fails + CFStringGetCString(uuid_string_ref, + uuid_string, + sizeof(uuid_string), + kCFStringEncodingUTF8); + CFRelease(uuid_string_ref); + + + return uuid_string; +#endif + +#ifdef OS_LINUX + static unsigned int index = 0; + char uuid[18] = {0}; + unsigned int pid = getpid(); + snprintf(uuid, 18, "%08x-%08x", pid, index++); + return String(uuid); +#endif +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool RawData::GetTempFilePathFromURI(const String &uri, + FilePath *temp_fullpath) { + if (!temp_fullpath) return false; + + // We use a UUID here to avoid any possible collisions with other tempfiles + // which have been or will be written sharing the same basic name + + FilePath temp_path; + if (!file_util::GetTempDir(&temp_path)) { + return false; + } + + String uuid_string = GetUUIDString(); + + // format the temp file basename + String filename; + + // try to retain the original file suffix (.jpg, etc.) + int dot_position = uri.rfind('.'); + if (dot_position != std::string::npos) { + filename = uuid_string + uri.substr(dot_position); + } else { + filename = uuid_string; + } + + // Construct the full pathname + FilePath fullpath = temp_path; + fullpath = fullpath.AppendASCII(filename); + + if (temp_fullpath) *temp_fullpath = fullpath; + + return true; +} + +} // namespace o3d diff --git a/o3d/import/cross/raw_data.h b/o3d/import/cross/raw_data.h new file mode 100644 index 0000000..8012efd --- /dev/null +++ b/o3d/import/cross/raw_data.h @@ -0,0 +1,119 @@ +/* + * 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 implementation for raw-data which is used +// by the progressive streaming archive system +// +// A RawData object is just a pointer to some memory of a given length. +// This data may represent string data, image data, audio data, etc.. + +#ifndef O3D_IMPORT_CROSS_RAW_DATA_H_ +#define O3D_IMPORT_CROSS_RAW_DATA_H_ + +#include "base/file_path.h" +#include "base/scoped_ptr.h" +#include "core/cross/error.h" +#include "core/cross/param_object.h" +#include "core/cross/param.h" +#include "core/cross/types.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class RawData : public ParamObject { + public: + typedef SmartPointer<RawData> Ref; + + static RawData::Ref Create(ServiceLocator* service_locator, + const String &uri, + const void *data, + size_t length); + + virtual ~RawData(); + + const uint8 *GetData() const; + + template <typename T> + const T* GetDataAs(size_t offset) const { + return reinterpret_cast<const T*>(GetData() + offset); + }; + + size_t GetLength() const { return length_; } + + String StringValue() const; + + const String& uri() const { return uri_; } + void set_uri(const String& uri) { uri_ = uri; } + + // If the data is still around + // (ie, Discard has not been called), then, if it has not been written + // to a temp file write it to a temp file + void Flush(); + + // calls Flush() if necessary and returns the path to the temp file + // if Discard() has already been called then returns an "empty" FilePath + const FilePath& GetTempFilePath(); + + // deletes the data which means IF the data is in memory it is + // freed. If there is a temp file it is deleted. + void Discard(); + + bool IsOffsetLengthValid(size_t offset, size_t length) const; + + private: + String uri_; + mutable scoped_array<uint8> data_; + size_t length_; + FilePath temp_filepath_; + + // Deletes temp file if it exists + void DeleteTempFile(); + + RawData(ServiceLocator* service_locator, + const String &uri, + const void *data, + size_t length); + + friend class IClassManager; + friend class Pack; + + // Returns |true| on success + bool GetTempFilePathFromURI(const String &uri, + FilePath *temp_fullpath); + + O3D_DECL_CLASS(RawData, ParamObject) + DISALLOW_COPY_AND_ASSIGN(RawData); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_RAW_DATA_H_ diff --git a/o3d/import/cross/raw_data_test.cc b/o3d/import/cross/raw_data_test.cc new file mode 100644 index 0000000..b1ccc5dd --- /dev/null +++ b/o3d/import/cross/raw_data_test.cc @@ -0,0 +1,196 @@ +/* + * 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. + */ + + +// Tests functionality of the RawData class + +#include "core/cross/client.h" +#include "tests/common/win/testing_common.h" +#include "core/cross/error.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/raw_data.h" + +namespace o3d { + +// Test fixture for RawData testing. +class RawDataTest : public testing::Test { +}; + +// Test RawData +TEST_F(RawDataTest, Basic) { + String uri("test_filename"); + const int kBufferLength = 1024; + + // Create a buffer and initialize it will some values + MemoryBuffer<uint8> buffer(kBufferLength); + for (int i = 0; i < kBufferLength; i++) { + buffer[i] = i % 256; + } + + + RawData::Ref ref = RawData::Create(g_service_locator, + uri, + buffer, + buffer.GetLength() ); + RawData *raw_data = ref; + + // Check that it got created. + ASSERT_TRUE(raw_data != NULL); + + // Now, let's make sure it got the data length correct + ASSERT_EQ(raw_data->GetLength(), buffer.GetLength()); + + // Check the contents are correct + ASSERT_EQ(0, memcmp(raw_data->GetData(), buffer, buffer.GetLength())); + + // Check that the uri is correct + ASSERT_EQ(raw_data->uri(), uri); +} + +namespace { + +struct TestData { + const uint8* data; + size_t length; + bool valid; + size_t offset; +}; + +} // anonymous namespace + +TEST_F(RawDataTest, StringValue) { + // A BOM in the front (valid) + static const uint8 data_0[] = { + 0xEF, + 0xBB, + 0xBF, + 0x65, + 0x66, + 0x65, + 0x67, + }; + + // A null in the string (invalid) + static const uint8 data_1[] = { + 0x65, + 0x66, + 0x00, + 0x67, + }; + + // A valid string + static const uint8 data_2[] = { + 0x65, + 0x66, + 0x65, + 0x67, + }; + + // Null at the end (invalid) + static const uint8 data_3[] = { + 0x65, + 0x66, + 0x65, + 0x67, + 0x00, + }; + + // A badly formed utf-8 like string (invalid) + static const uint8 data_4[] = { + 0xE9, + 0xBE, + 0xE0, + }; + + // A badly formed utf-8 like string (invalid) + static const uint8 data_5[] = { + 0xC1, + 0x65, + 0x66, + }; + + // A badly formed utf-8 like string (invalid) + static const uint8 data_6[] = { + 0x65, + 0x66, + 0xF4, + }; + + // A valid multi-byte utf-8 string (valid) + static const uint8 data_7[] = { + 0xE9, + 0xBE, + 0x8D, + 0xF0, + 0x90, + 0x90, + 0x92, + }; + + // A UTF-8 but in D800-DFFF range + static const uint8 data_8[] = { + 0xED, + 0xA0, + 0xA1, + }; + + TestData test_datas[] = { + { &data_0[0], arraysize(data_0), true, 3, }, + { &data_1[0], arraysize(data_1), false, }, + { &data_2[0], arraysize(data_2), true, }, + { &data_3[0], arraysize(data_3), false, }, + { &data_4[0], arraysize(data_4), false, }, + { &data_5[0], arraysize(data_5), false, }, + { &data_6[0], arraysize(data_6), false, }, + { &data_7[0], arraysize(data_7), true, }, + { &data_8[0], arraysize(data_8), false, }, + }; + + for (unsigned ii = 0; ii < arraysize(test_datas); ++ii) { + TestData& test_data = test_datas[ii]; + RawData::Ref raw_data = RawData::Create(g_service_locator, + "test", + test_data.data, + test_data.length); + String str(raw_data->StringValue()); + if (test_data.valid) { + size_t test_length = test_data.length - test_data.offset; + const char* test_string = reinterpret_cast<const char*>(test_data.data + + test_data.offset); + EXPECT_EQ(str.size(), test_length); + EXPECT_EQ(str.compare(0, test_length, test_string, test_length), 0); + } else { + EXPECT_TRUE(str.empty()); + } + } +} + +} // namespace o3d diff --git a/o3d/import/cross/tar_generator.cc b/o3d/import/cross/tar_generator.cc new file mode 100644 index 0000000..019ec0e --- /dev/null +++ b/o3d/import/cross/tar_generator.cc @@ -0,0 +1,195 @@ +/* + * 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. + */ + + +#include "import/cross/memory_buffer.h" +#include "import/cross/tar_generator.h" + +using std::string; + +namespace o3d { + +const int kMaxFilenameSize = 100; + +const int kFileNameOffset = 0; +const int kFileModeOffset = 100; +const int kUserIDOffset = 108; +const int kGroupIDOffset = 116; +const int kFileSizeOffset = 124; +const int kModifyTimeOffset = 136; +const int kHeaderCheckSumOffset = 148; +const int kLinkFlagOffset = 156; +const int kMagicOffset = 257; +const int kUserNameOffset = 265; +const int kGroupNameOffset = 297; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::AddFile(const String &file_name, size_t file_size) { + AddDirectoryEntryIfNeeded(file_name); + AddEntry(file_name, file_size, false); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::AddDirectory(const String &file_name) { + AddEntry(file_name, 0, true); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// We keep a map so we add a particular directory entry only once +void TarGenerator::AddDirectoryEntryIfNeeded(const String &file_name) { + string::size_type index = file_name.find_last_of('/'); + + if (index != string::npos) { + String dir_name = file_name.substr(0, index + 1); // keep the '/' at end + if (!directory_map_[dir_name]) { + directory_map_[dir_name] = true; + AddDirectory(dir_name); + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::AddEntry(const String &file_name, + size_t file_size, + bool is_directory) { + // first write out last data block from last file (if any) + FlushDataBuffer(true); + + // next fill out a tar header + MemoryBuffer<uint8> header(TAR_HEADER_SIZE); // initializes header to zeroes + + // leave room for NULL-termination + uint8 *h = header; + char *p = reinterpret_cast<char*>(h); + + // File name + strncpy(p, file_name.c_str(), kMaxFilenameSize - 1); + + // File mode + snprintf(p + kFileModeOffset, 8, "%07o", is_directory ? 0755 : 0644); + + // UserID + snprintf(p + kUserIDOffset, 8, "%07o", 0765); + + // GroupID + snprintf(p + kGroupIDOffset, 8, "%07o", 0204); + + // File size + snprintf(p + kFileSizeOffset, 12, "%011o", file_size); + + // Modification time + // TODO: write the correct current time here... + snprintf(p + kModifyTimeOffset, 12, "%07o", 011131753141); + + // Initialize Header checksum so check sum can be computed + // by ComputeCheckSum() which will fill in the value here + memset(p + kHeaderCheckSumOffset, 32, 8); + + // We only support ordinary files and directories, which is fine + // for our use case + int link_flag = is_directory ? '5' : '0'; + p[kLinkFlagOffset] = link_flag; + + // Magic offset + snprintf(p + kMagicOffset, 8, "ustar "); + + // User name + snprintf(p + kUserNameOffset, 32, "guest"); + + // Group name + snprintf(p + kGroupNameOffset, 32, "staff"); + + + // This has to be done at the end + ComputeCheckSum(header); + + if (callback_client_) { + MemoryReadStream stream(header, TAR_HEADER_SIZE); + callback_client_->ProcessBytes(&stream, TAR_HEADER_SIZE); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::ComputeCheckSum(uint8 *header) { + unsigned int checksum = 0; + for (int i = 0; i < TAR_HEADER_SIZE; ++i) { + checksum += header[i]; + } + snprintf(reinterpret_cast<char*>(header + kHeaderCheckSumOffset), + 8, "%06o\0\0", checksum); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int TarGenerator::AddFileBytes(MemoryReadStream *stream, size_t n) { + if (callback_client_) { + FlushDataBuffer(false); // flush any old data sitting around + + // we'll directly write as much of the data as we can, writing full blocks + int nblocks = n / TAR_BLOCK_SIZE; + size_t direct_bytes_to_write = nblocks * TAR_BLOCK_SIZE; + if (direct_bytes_to_write > 0) { + callback_client_->ProcessBytes(stream, direct_bytes_to_write); + } + + // now, buffer the remainder (less than TAR_BLOCK_SIZE) + size_t remainder = n - direct_bytes_to_write; + + const uint8 *p = stream->GetDirectMemoryPointer(); + data_buffer_stream_.Write(p, remainder); + stream->Skip(remainder); + } + + return 0; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::FlushDataBuffer(bool flush_padding_zeroes) { + size_t buffered_bytes = data_buffer_stream_.GetStreamPosition(); + if (buffered_bytes > 0 && callback_client_) { + // write out the complete data block (which may be zero padded at the end) + size_t bytes_to_flush = + flush_padding_zeroes ? TAR_BLOCK_SIZE : buffered_bytes; + + MemoryReadStream stream(data_block_buffer_, bytes_to_flush); + callback_client_->ProcessBytes(&stream, bytes_to_flush); + + // get ready to start buffering next data block + data_block_buffer_.Clear(); + data_buffer_stream_.Seek(0); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGenerator::Finalize() { + FlushDataBuffer(true); +} + +} // namespace o3d diff --git a/o3d/import/cross/tar_generator.h b/o3d/import/cross/tar_generator.h new file mode 100644 index 0000000..1468849 --- /dev/null +++ b/o3d/import/cross/tar_generator.h @@ -0,0 +1,125 @@ +/* + * 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. + */ + +// +// TarGenerator generates a tar byte stream (uncompressed). +// +// A tar byte stream consists of a series of file headers, each followed by +// the actual file data. Each file header starts on a block-aligned offset +// with the blocksize 512. The start of data for each file is also +// block-aligned. Zero-padding is added at the end of the file's data, +// if necessary to block-align... +// +// The normal usage is to call the AddFile() method for each file to add to the +// archive, then make one or more calls to AddFileBytes() to give the file's +// data. Then repeat this sequence for each file to be added. When done, +// call Finalize(). + +#ifndef O3D_IMPORT_CROSS_TAR_GENERATOR_H_ +#define O3D_IMPORT_CROSS_TAR_GENERATOR_H_ + +#include <map> +#include <string> +#include "base/basictypes.h" +#include "core/cross/types.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarGenerator { + public: + explicit TarGenerator(StreamProcessor *callback_client) + : callback_client_(callback_client), + data_block_buffer_(TAR_BLOCK_SIZE), // initialized to zeroes + data_buffer_stream_(data_block_buffer_, TAR_BLOCK_SIZE) {} + + virtual ~TarGenerator() { Finalize(); } + + // Call AddFile() for each file entry, followed by calls to AddFileBytes() + // for the file's data + virtual void AddFile(const String &file_name, + size_t file_size); + + // Call to "push" bytes to be processed - our client will get called back + // with the byte stream, with files rounded up to the nearest block size + // (with zero padding) + virtual int AddFileBytes(MemoryReadStream *stream, size_t n); + + // Must call this after all files and file data have been written + virtual void Finalize(); + + private: + void AddEntry(const String &file_name, + size_t file_size, + bool is_directory); + + void AddDirectory(const String &file_name); + void AddDirectoryEntryIfNeeded(const String &file_name); + + // Checksum for each header + void ComputeCheckSum(uint8 *header); + + // flushes buffered file data to the client callback + // if |flush_padding_zeroes| is |true| then flush a complete block + // with zero padding even if less was buffered + void FlushDataBuffer(bool flush_padding_zeroes); + + enum {TAR_HEADER_SIZE = 512}; + enum {TAR_BLOCK_SIZE = 512}; + + StreamProcessor *callback_client_; + + // Buffers file data here - file data is in multiples of TAR_BLOCK_SIZE + MemoryBuffer<uint8> data_block_buffer_; + MemoryWriteStream data_buffer_stream_; + + // We use DirectoryMap to keep track of which directories we've already + // written out headers for. The client doesn't need to explicitly + // add the directory entries. Instead, the files will be stripped to their + // base path and entries added for the directories as needed... + struct StrCmp { + bool operator()(const std::string &s1, const std::string &s2) const { + return strcmp(s1.c_str(), s2.c_str()) < 0; + } + }; + + typedef std::map<const std::string, bool, StrCmp> DirectoryMap; + + DirectoryMap directory_map_; + + DISALLOW_COPY_AND_ASSIGN(TarGenerator); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_TAR_GENERATOR_H_ diff --git a/o3d/import/cross/tar_generator_test.cc b/o3d/import/cross/tar_generator_test.cc new file mode 100644 index 0000000..0e45b8d --- /dev/null +++ b/o3d/import/cross/tar_generator_test.cc @@ -0,0 +1,386 @@ +/* + * 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. + */ + + +#include "core/cross/client.h" +#include "import/cross/memory_buffer.h" +#include "import/cross/memory_stream.h" +#include "import/cross/tar_generator.h" +#include "tests/common/win/testing_common.h" +#include "tests/common/cross/test_utils.h" + +namespace o3d { + +using o3d::StreamProcessor; +using o3d::MemoryReadStream; +using o3d::MemoryWriteStream; +using o3d::TarGenerator; + +class TarGeneratorTest : public testing::Test { +}; + +const int kBlockSize = 512; + +const int kMaxFilenameSize = 100; + +const int kFileNameOffset = 0; +const int kFileModeOffset = 100; +const int kUserIDOffset = 108; +const int kGroupIDOffset = 116; +const int kFileSizeOffset = 124; +const int kModifyTimeOffset = 136; +const int kHeaderCheckSumOffset = 148; +const int kLinkFlagOffset = 156; +const int kMagicOffset = 257; +const int kUserNameOffset = 265; +const int kGroupNameOffset = 297; + +const char *kDirName1 = "test/apples/"; +const char *kDirName2 = "test/oranges/"; +const char *kFileName1 = "test/apples/file1"; +const char *kFileName2 = "test/apples/file2"; +const char *kFileName3 = "test/oranges/file3"; + +// The first file is less than one block in size +const char *kFileContents1 = + "The cellphone is the world’s most ubiquitous computer.\n" + "The four billion cellphones in use around the globe carry personal\n" + "information, provide access to the Web and are being used more and more\n" + "to navigate the real world. And as cellphones change how we live,\n" + "computer scientists say, they are also changing\n" + "how we think about information\n"; + +// The 2nd file takes two blocks +const char *kFileContents2 = + "From Hong Kong to eastern Europe to Wall Street, financial gloom was\n" + "everywhere on Tuesday.\n" + "Stock markets around the world staggered lower. In New York,\n" + "the Dow fell more than 3 percent, coming within sight of its worst\n" + "levels since the credit crisis erupted. Financial shares were battered.\n" + "And rattled investors clamored to buy rainy-day investments like gold\n" + "and Treasury debt. It was a global wave of selling spurred by rising\n" + "worries about how banks, automakers — entire countries — would fare\n" + "in a deepening global downturn.\n" + "'Nobody believes it’s going get better yet,' said Howard Silverblatt,\n" + "senior index analyst at Standard & Poor’s. 'Do you see that light at\n" + "the end of the tunnel? Any kind of light? Right now, it’s not there'\n" + "yet.\n"; + +// The 3rd file takes one block +const char *kFileContents3 = "nothing much here...\n"; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Receives the tar bytestream from the TarGenerator. +// We validate the bytestream as it comes in... +// +class CallbackClient : public StreamProcessor { + public: + // states for state machine, with each state representing one + // received block of the tar stream. + // The blocks can be either headers (for directories and files) + // or data blocks (zero padded at the end to make a full block) + enum ValidationState { + VALIDATE_DIRECTORY_HEADER1, // header 1 is directory so no file data + VALIDATE_FILE_HEADER1, + VALIDATE_FILE_DATA1, // 1st file takes one block + VALIDATE_FILE_HEADER2, + VALIDATE_FILE_DATA2_BLOCK1, // 2nd file takes two blocks + VALIDATE_FILE_DATA2_BLOCK2, + VALIDATE_DIRECTORY_HEADER2, // 3rd file is in another directory + VALIDATE_FILE_HEADER3, + VALIDATE_FILE_DATA3, + FINISHED + }; + + CallbackClient() + : state_(VALIDATE_DIRECTORY_HEADER1), + total_bytes_received_(0), + memory_block_(kBlockSize), + write_stream_(memory_block_, kBlockSize) { + } + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + size_t GetTotalBytesReceived() { return total_bytes_received_; } + int GetState() { return state_; } + + private: + bool IsOctalDigit(uint8 c); + bool IsOctalString(uint8 *p); + unsigned int ComputeCheckSum(uint8 *header); + + void ValidateData(uint8 *header, const char *file_contents); + + void ValidateHeader(uint8 *header, + const char *file_name, + size_t file_length); + + // For debugging + void DumpMemoryBlock(uint8 *block); + + int state_; + size_t total_bytes_received_; + MemoryBuffer<uint8> memory_block_; + MemoryWriteStream write_stream_; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int CallbackClient::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + total_bytes_received_ += bytes_to_process; + + while (bytes_to_process > 0) { + size_t remaining = write_stream_.GetRemainingByteCount(); + + size_t bytes_this_time = + bytes_to_process < remaining ? bytes_to_process : remaining; + + const uint8 *p = stream->GetDirectMemoryPointer(); + stream->Skip(bytes_this_time); + + write_stream_.Write(p, bytes_this_time); + + // our block buffer is full, so validate it according to our state + // machine + if (write_stream_.GetRemainingByteCount() == 0) { + // DumpMemoryBlock(memory_block_); // uncomment for debugging + + switch (state_) { + case VALIDATE_DIRECTORY_HEADER1: + ValidateHeader(memory_block_, kDirName1, 0); + break; + + case VALIDATE_FILE_HEADER1: + ValidateHeader(memory_block_, kFileName1, strlen(kFileContents1)); + break; + + case VALIDATE_FILE_DATA1: + ValidateData(memory_block_, kFileContents1); + break; + + case VALIDATE_FILE_HEADER2: + ValidateHeader(memory_block_, kFileName2, strlen(kFileContents2)); + break; + + case VALIDATE_FILE_DATA2_BLOCK1: + // file2 data is larger than one one block, so we'll verify + // the two blocks are correct + ValidateData(memory_block_, kFileContents2); + break; + + case VALIDATE_FILE_DATA2_BLOCK2: + ValidateData(memory_block_, kFileContents2 + kBlockSize); + break; + + case VALIDATE_DIRECTORY_HEADER2: + ValidateHeader(memory_block_, kDirName2, 0); + break; + + case VALIDATE_FILE_HEADER3: + ValidateHeader(memory_block_, kFileName3, strlen(kFileContents3)); + break; + + case VALIDATE_FILE_DATA3: + ValidateData(memory_block_, kFileContents3); + break; + + case FINISHED: + break; + } + + // Advance to the next state + ++state_; + + // So next time we write, we start at beginning of buffer + write_stream_.Seek(0); + } + + bytes_to_process -= bytes_this_time; + } + + return 0; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool CallbackClient::IsOctalDigit(uint8 c) { + return (c >= '0' && c <= '7'); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool CallbackClient::IsOctalString(uint8 *p) { + while (IsOctalDigit(*p)) ++p; + return *p == 0; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +unsigned int CallbackClient::ComputeCheckSum(uint8 *header) { + unsigned int checksum = 0; + for (int i = 0; i < kBlockSize; ++i) { + uint8 value = header[i]; + if (i >= kHeaderCheckSumOffset && i < kHeaderCheckSumOffset + 8) { + value = 32; // treat checksum itself as ' ' + } + checksum += value; + } + return checksum; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// for now |file_contents| must be less than a block size... +void CallbackClient::ValidateData(uint8 *header, + const char *file_contents) { + // only check up to one block size worth of data + int zero_pad_start_index = strlen(file_contents); + if (zero_pad_start_index > kBlockSize) zero_pad_start_index = kBlockSize; + + if (zero_pad_start_index <= kBlockSize) { + // file data must match + uint8 *p = memory_block_; + EXPECT_EQ(0, strncmp(file_contents, (const char*)p, kBlockSize)); + + // check that zero padding is there + bool zero_padding_good = true; + for (int i = zero_pad_start_index; i < kBlockSize; ++i) { + if (memory_block_[i] != 0) { + zero_padding_good = false; + break; + } + } + EXPECT_TRUE(zero_padding_good); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void CallbackClient::ValidateHeader(uint8 *header, + const char *file_name, + size_t file_length) { + // Validate file name + EXPECT_EQ(0, + strcmp(reinterpret_cast<char*>(header) + kFileNameOffset, + file_name)); + + // Validate length + int length_in_header; + sscanf((const char*)header + kFileSizeOffset, "%o", &length_in_header); + EXPECT_EQ(file_length, length_in_header); + + + EXPECT_EQ(0, header[kMaxFilenameSize - 1]); + + EXPECT_TRUE(IsOctalString(header + kFileModeOffset)); + EXPECT_EQ(0, header[kFileModeOffset + 7]); + + EXPECT_TRUE(IsOctalString(header + kUserIDOffset)); + EXPECT_EQ(0, header[kUserIDOffset + 7]); + + EXPECT_TRUE(IsOctalString(header + kGroupIDOffset)); + EXPECT_EQ(0, header[kGroupIDOffset + 7]); + + EXPECT_TRUE(IsOctalString(header + kFileSizeOffset)); + EXPECT_EQ(0, header[kFileSizeOffset + 11]); + + EXPECT_TRUE(IsOctalString(header + kModifyTimeOffset)); + EXPECT_EQ(0, header[kModifyTimeOffset + 11]); + + EXPECT_TRUE(IsOctalString(header + kHeaderCheckSumOffset)); + EXPECT_EQ(0, header[kHeaderCheckSumOffset + 6]); + + // For now we only have directories '5' or normal files '0' + int link_flag = header[kLinkFlagOffset]; + EXPECT_TRUE(link_flag == '0' || link_flag == '5'); + + EXPECT_EQ(0, strcmp((const char*)header + kMagicOffset, "ustar ")); + + EXPECT_EQ(0, header[kUserNameOffset + 31]); + EXPECT_EQ(0, header[kGroupNameOffset + 31]); + + // Validate checksum + int checksum = ComputeCheckSum(header); + int header_checksum; + sscanf((const char*)header + kHeaderCheckSumOffset, "%o", &header_checksum); + + EXPECT_EQ(checksum, header_checksum); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// For debugging purposes +void CallbackClient::DumpMemoryBlock(uint8 *block) { + for (int i = 0; i < kBlockSize; ++i) { + if ((i % 16) == 0) printf("\n"); + char c = block[i]; + if (c == 0) { + c = '.'; + } + printf("%c", c); + } + printf("\n"); +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Creates a tar file with three files in two directories +// +TEST_F(TarGeneratorTest, CreateSimpleArchive) { + CallbackClient client; + TarGenerator generator(&client); + + const int kFileLength1 = strlen(kFileContents1); + const int kFileLength2 = strlen(kFileContents2); + const int kFileLength3 = strlen(kFileContents3); + + generator.AddFile(kFileName1, kFileLength1); + MemoryReadStream file1_stream(reinterpret_cast<const uint8*>(kFileContents1), + kFileLength1); + generator.AddFileBytes(&file1_stream, kFileLength1); + + generator.AddFile(kFileName2, kFileLength2); + MemoryReadStream file2_stream(reinterpret_cast<const uint8*>(kFileContents2), + kFileLength2); + generator.AddFileBytes(&file2_stream, kFileLength2); + + generator.AddFile(kFileName3, kFileLength3); + MemoryReadStream file3_stream(reinterpret_cast<const uint8*>(kFileContents3), + kFileLength3); + generator.AddFileBytes(&file3_stream, kFileLength3); + + generator.Finalize(); + + // Verify that the tar byte stream produced is exactly divisible by + // the block size + size_t bytes_received = client.GetTotalBytesReceived(); + EXPECT_EQ(0, bytes_received % kBlockSize); + + // Make sure the state machine is in the expected state + EXPECT_EQ(CallbackClient::FINISHED, client.GetState()); +} + +} // namespace diff --git a/o3d/import/cross/tar_processor.cc b/o3d/import/cross/tar_processor.cc new file mode 100644 index 0000000..bb1d9b8 --- /dev/null +++ b/o3d/import/cross/tar_processor.cc @@ -0,0 +1,144 @@ +/* + * 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. + */ + +// +// TarProcessor processes a tar byte stream (uncompressed). + +#include "base/logging.h" +#include "import/cross/tar_processor.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { + // Keep processing the byte-stream until we've consumed all we're given + // + size_t bytes_to_consume = n; + + while (bytes_to_consume > 0) { + // First see if we have any more header bytes to read + if (header_bytes_read_ < TAR_HEADER_SIZE) { + // Read header bytes + size_t header_bytes_remaining = TAR_HEADER_SIZE - header_bytes_read_; + size_t bytes_to_read = std::min(bytes_to_consume, header_bytes_remaining); + size_t bytes_read = + stream->Read(reinterpret_cast<uint8*>(header_ + header_bytes_read_), + bytes_to_read); + if (bytes_read != bytes_to_read) { + return -1; + } + + header_bytes_read_ += bytes_to_read; + bytes_to_consume -= bytes_to_read; + + if (header_bytes_read_ == TAR_HEADER_SIZE) { + const char *filename = (const char *)header_; + + // The tar format stupidly represents size_teger values as + // octal strings!! + size_t file_size = 0; + sscanf(header_ + 124, "%o", &file_size); + + // Only callback client if this is a "real" header + // (filename is not NULL) + // Extra zero-padding can be added by the gzip compression + // (at end of archive), so ignore these ones. + // + // Also, ignore entries for directories (which have zero size) + if (header_[0] != 0 && file_size > 0) { + ArchiveFileInfo info(filename, file_size); + callback_client_->ReceiveFileHeader(info); + } else if (header_[0] == 0) { + // If filename is NULL due to zero-padding then file size + // should also be NULL + assert(file_size == 0); + } + + // Round filesize up to nearest block size + file_bytes_to_read_ = + (file_size + TAR_BLOCK_SIZE - 1) & ~(TAR_BLOCK_SIZE - 1); + + // Our client doesn't want to be bothered with the block padding, + // so only send him the actual file bytes + client_file_bytes_to_read_ = file_size; + } + } + + if (bytes_to_consume > 0) { + // Looks like we have some left-over bytes past the header + // -- size_terpret as file bytes + if (client_file_bytes_to_read_ > 0) { + // Callback to client with some file data + + // Use a copy of the stream in case the client doesn't read + // the amount they're supposed to + MemoryReadStream client_read_stream(*stream); + + size_t client_bytes_this_time = + std::min(bytes_to_consume, client_file_bytes_to_read_); + + if (!callback_client_->ReceiveFileData(&client_read_stream, + client_bytes_this_time)) { + return -1; + } + + client_file_bytes_to_read_ -= client_bytes_this_time; + file_bytes_to_read_ -= client_bytes_this_time; + + // Advance stream the amount the client should have consumed + stream->Skip(client_bytes_this_time); + + bytes_to_consume -= client_bytes_this_time; + } + + // Now, check if we have any padding bytes (up to block size) + // past the file data + if (bytes_to_consume > 0) { + size_t bytes_to_skip = std::min(bytes_to_consume, file_bytes_to_read_); + stream->Skip(bytes_to_skip); + file_bytes_to_read_ -= bytes_to_skip; + bytes_to_consume -= bytes_to_skip; + } + + if (file_bytes_to_read_ == 0) { + // We've read all of the file data, + // so now expect the next file header + + // setting to 0 makes us want more header bytes + header_bytes_read_ = 0; + } + } + } + + return 0; +} + +} // namespace o3d diff --git a/o3d/import/cross/tar_processor.h b/o3d/import/cross/tar_processor.h new file mode 100644 index 0000000..e680e47 --- /dev/null +++ b/o3d/import/cross/tar_processor.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +// +// TarProcessor processes a tar byte stream (uncompressed). +// +// A tar byte stream consists of a series of file headers, each followed by +// the actual file data. Each file header starts on a block-aligned offset +// with the blocksize 512. The start of data for each file is also +// block-aligned. +// +// As a TarProcessor receives bytes, it calls the client +// callback method ReceiveFileHeader() when each complete file header has been +// received. Then the client's ReceiveFileData() will be called (possibly +// repeatedly) as the file's data is received. This is repeated until all of +// the files in the archive have been processed. + +#ifndef O3D_IMPORT_CROSS_TAR_PROCESSOR_H_ +#define O3D_IMPORT_CROSS_TAR_PROCESSOR_H_ + +#include "base/basictypes.h" +#include "import/cross/memory_stream.h" +#include "import/cross/archive_processor.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarProcessor : public StreamProcessor { + public: + explicit TarProcessor(ArchiveCallbackClient *callback_client) + : callback_client_(callback_client), + header_bytes_read_(0), + file_bytes_to_read_(0) {} + + virtual ~TarProcessor() {} + + // Call to "push" bytes to be processed - the appropriate callback will get + // called when we have enough data + virtual int ProcessBytes(MemoryReadStream *stream, size_t n); + + private: + enum {TAR_HEADER_SIZE = 512}; + enum {TAR_BLOCK_SIZE = 512}; + + ArchiveCallbackClient *callback_client_; + size_t header_bytes_read_; + char header_[TAR_HEADER_SIZE]; + + // Initialized to total number of file bytes, + // including zero padding up to block size + // We read this many bytes to get to the next header + size_t file_bytes_to_read_; + + // Initialized to the actual file size (not counting zero-padding) - keeps + // track of number of bytes the client needs to read for the current file + size_t client_file_bytes_to_read_; + + DISALLOW_COPY_AND_ASSIGN(TarProcessor); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_TAR_PROCESSOR_H_ diff --git a/o3d/import/cross/tar_processor_test.cc b/o3d/import/cross/tar_processor_test.cc new file mode 100644 index 0000000..120aef3 --- /dev/null +++ b/o3d/import/cross/tar_processor_test.cc @@ -0,0 +1,151 @@ +/* + * 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. + */ + + +#include "core/cross/client.h" +#include "import/cross/targz_processor.h" +#include "tests/common/win/testing_common.h" +#include "tests/common/cross/test_utils.h" + +namespace o3d { + +class TarProcessorTest : public testing::Test { +}; + +// We verify that the tar file contains exactly these filenames +static const char *kFilename1 = "test/file1"; +static const char *kFilename2 = "test/file2"; +static const char *kFilename3 = "test/file3"; + +// With each file having these exact contents +#define kFileContents1 "the cat in the hat\n" +#define kFileContents2 "abracadabra\n" +#define kFileContents3 "I think therefore I am\n" + +// we should receive these (and exactly these bytes in this order) +static const char *kConcatenatedContents = + kFileContents1 kFileContents2 kFileContents3; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarTestClient : public ArchiveCallbackClient { + public: + explicit TarTestClient() : file_count_(0), index_(0) {} + // ArchiveCallbackClient methods + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); + virtual bool ReceiveFileData(MemoryReadStream *stream, int nbytes); + + int GetFileCount() const { return file_count_; } + size_t GetNumTotalBytesReceived() const { return index_; } + + private: + int file_count_; + int index_; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarTestClient::ReceiveFileHeader(const ArchiveFileInfo &file_info) { + // We get called one time for each file in the archive + + // Check that the filenames match our expectation + switch (file_count_) { + case 0: + EXPECT_TRUE(!strcmp(kFilename1, file_info.GetFileName().c_str())); + break; + case 1: + EXPECT_TRUE(!strcmp(kFilename2, file_info.GetFileName().c_str())); + break; + case 2: + EXPECT_TRUE(!strcmp(kFilename3, file_info.GetFileName().c_str())); + break; + } + + file_count_++; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool TarTestClient::ReceiveFileData(MemoryReadStream *stream, int nbytes) { + const char *p = reinterpret_cast<const char*>( + stream->GetDirectMemoryPointer()); + + // Note: ReceiveFileData() may be called multiple times for each file, until + // the complete file contents have been given... + + // We're receiving three file, one after the other + // The bytes we receive are the concatenated contents of the three files + // (with calls to ReceiveFileHeader() separating each one) + // + EXPECT_TRUE(index_ + nbytes <= strlen(kConcatenatedContents)); + EXPECT_TRUE(!strncmp(kConcatenatedContents + index_, p, nbytes)); + + index_ += nbytes; + + return true; +} + +// Loads a tar file, runs it through the processor. +// In our callbacks, we verify that we receive three files with known contents +// +TEST_F(TarProcessorTest, LoadTarFile) { + String filepath = *g_program_path + "/archive_files/test1.tar"; + + // Read the test tar file into memory + size_t file_size; + uint8 *tar_data = test_utils::ReadFile(filepath, &file_size); + ASSERT_TRUE(tar_data != NULL); + + // Gets header and file data callbacks + TarTestClient callback_client; + + // The class we're testing... + TarProcessor tar_processor(&callback_client); + + // Now that we've read the compressed file into memory, lets + // feed it, a chunk at a time, into the tar_processor + const int kChunkSize = 32; + + MemoryReadStream tar_stream(tar_data, file_size); + size_t bytes_to_process = file_size; + + int result = Z_OK; + while (bytes_to_process > 0) { + size_t bytes_this_time = + bytes_to_process < kChunkSize ? bytes_to_process : kChunkSize; + + result = tar_processor.ProcessBytes(&tar_stream, bytes_this_time); + EXPECT_TRUE(result == Z_OK); + + bytes_to_process -= bytes_this_time; + } + + free(tar_data); +} + +} // namespace diff --git a/o3d/import/cross/targz_generator.cc b/o3d/import/cross/targz_generator.cc new file mode 100644 index 0000000..02cb5b3 --- /dev/null +++ b/o3d/import/cross/targz_generator.cc @@ -0,0 +1,72 @@ +/* + * 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. + */ + + +#include "import/cross/memory_buffer.h" +#include "import/cross/targz_generator.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +TarGzGenerator::TarGzGenerator(StreamProcessor *callback_client) + : gz_compressor_(callback_client), tar_generator_(this) { +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGzGenerator::AddFile(const String &file_name, size_t file_size) { + tar_generator_.AddFile(file_name, file_size); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int TarGzGenerator::AddFileBytes(MemoryReadStream *stream, size_t n) { + return tar_generator_.AddFileBytes(stream, n); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int TarGzGenerator::AddFileBytes(const uint8 *data, size_t n) { + MemoryReadStream stream(data, n); + return AddFileBytes(&stream, n); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void TarGzGenerator::Finalize() { + tar_generator_.Finalize(); + gz_compressor_.Finalize(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int TarGzGenerator::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // pass bytestream from tar generator to the compressor + return gz_compressor_.ProcessBytes(stream, bytes_to_process); +} + +} // namespace o3d diff --git a/o3d/import/cross/targz_generator.h b/o3d/import/cross/targz_generator.h new file mode 100644 index 0000000..fd58c86 --- /dev/null +++ b/o3d/import/cross/targz_generator.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +// +// TarGzGenerator generates a "tar.gz" byte stream. +// In other words, given a series of files, it will first create a tar archive +// from them, then apply gzip compression. +// This would be equivalent to using the "tar cf" command followed by "gzip" +// +// The normal usage is to call the AddFile() method for each file to add to the +// archive, then make one or more calls to AddFileBytes() to give the file's +// data. Then repeat this sequence for each file to be added. When done, +// call Finalize(). + +#ifndef O3D_IMPORT_CROSS_TARGZ_GENERATOR_H_ +#define O3D_IMPORT_CROSS_TARGZ_GENERATOR_H_ + +#include <string> +#include "base/basictypes.h" +#include "core/cross/types.h" +#include "import/cross/gz_compressor.h" +#include "import/cross/iarchive_generator.h" +#include "import/cross/memory_stream.h" +#include "import/cross/tar_generator.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarGzGenerator : public StreamProcessor, public IArchiveGenerator { + public: + // |callback_client| receives the tar.gz byte stream + explicit TarGzGenerator(StreamProcessor *callback_client); + + // Call AddFile() for each file entry, followed by calls to AddFileBytes() + // for the file's data + void AddFile(const String &file_name, + size_t file_size); + + // Call with the file's data (after calling AddFile) + // may be called one time with all the file's data, or multiple times + // until all the data is provided + int AddFileBytes(MemoryReadStream *stream, size_t n); + int AddFileBytes(const uint8 *data, size_t n); + + // Must call this after all files and file data have been written + virtual void Finalize(); + + private: + // StreamProcessor method: + // Receives the tar bytestream from the TarGenerator. + // It then feeds this into the GzCompressor + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + GzCompressor gz_compressor_; + TarGenerator tar_generator_; + + DISALLOW_COPY_AND_ASSIGN(TarGzGenerator); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_TARGZ_GENERATOR_H_ diff --git a/o3d/import/cross/targz_generator_test.cc b/o3d/import/cross/targz_generator_test.cc new file mode 100644 index 0000000..8715933 --- /dev/null +++ b/o3d/import/cross/targz_generator_test.cc @@ -0,0 +1,166 @@ +/* + * 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. + */ + + +#include <string> + +#include "base/file_util.h" +#include "core/cross/client.h" +#include "import/cross/memory_stream.h" +#include "import/cross/targz_generator.h" +#include "tests/common/cross/test_utils.h" +#include "tests/common/win/testing_common.h" +#include "utils/cross/file_path_utils.h" + +// Define to generate a new golden file for the archive test. Don't +// leave this enabled, as it may affect the results of the test. +#undef GENERATE_GOLDEN + +namespace o3d { + +class TarGzGeneratorTest : public testing::Test { +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarGzTestClient : public StreamProcessor { + public: + explicit TarGzTestClient(size_t reference_size) + : compressed_data_(reference_size), + write_stream_(compressed_data_, reference_size) { + }; + + virtual int ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + // Simply buffer up the tar.gz bytes + // When we've gotten them all our Validate() method will be called + const uint8 *p = stream->GetDirectMemoryPointer(); + stream->Skip(bytes_to_process); + + size_t remaining = write_stream_.GetRemainingByteCount(); + EXPECT_TRUE(bytes_to_process <= remaining); + + write_stream_.Write(p, bytes_to_process); + + return 0; + } + + // Checks that the data from the reference tar.gz file matches the tar.gz + // stream we just ge5nerated + void Validate(uint8 *reference_data) { + uint8 *received_data = compressed_data_; + + // on Windows the platform field is different than our reference file + received_data[9] = 3; // Force platform in header to 'UNIX'. + + EXPECT_EQ(0, memcmp(reference_data, + received_data, + compressed_data_.GetLength())); + } + +#if defined(GENERATE_GOLDEN) + char* received_data() { + uint8 *data = compressed_data_; + data[9] = 3; // Force platform in header to 'UNIX'. + return reinterpret_cast<char*>(data); + } + + int received_data_length() { + return compressed_data_.GetLength(); + } +#endif + + private: + MemoryBuffer<uint8> compressed_data_; + MemoryWriteStream write_stream_; + + DISALLOW_COPY_AND_ASSIGN(TarGzTestClient); +}; + +// Generates a tar.gz archive (in memory) containing three files (image, audio, +// shader) in three separate directories. The generated archive is then +// compared with a reference tar.gz file which is known to be correct. +// +TEST_F(TarGzGeneratorTest, GenerateTarGz) { + String targz_reference_file = *g_program_path + "/archive_files/test2.tar.gz"; + String image_file = *g_program_path + "/archive_files/keyboard.jpg"; + String audio_file = *g_program_path + "/archive_files/perc.aif"; + String shader_file = *g_program_path + "/archive_files/BumpReflect.fx"; + + size_t targz_reference_size; + size_t image_size; + size_t audio_size; + size_t shader_size; + + // Read the test files into memory + uint8 *targz_data = + test_utils::ReadFile(targz_reference_file, &targz_reference_size); + uint8 *image_data = test_utils::ReadFile(image_file, &image_size); + uint8 *audio_data = test_utils::ReadFile(audio_file, &audio_size); + uint8 *shader_data = test_utils::ReadFile(shader_file, &shader_size); + ASSERT_TRUE(targz_data != NULL); + ASSERT_TRUE(image_data != NULL); + ASSERT_TRUE(audio_data != NULL); + ASSERT_TRUE(shader_data != NULL); + + ASSERT_NE(0, targz_reference_size); + + TarGzTestClient test_client(targz_reference_size); + TarGzGenerator targz_generator(&test_client); + + targz_generator.AddFile("test/images/keyboard.jpg", image_size); + targz_generator.AddFileBytes(image_data, image_size); + + targz_generator.AddFile("test/audio/perc.aif", audio_size); + targz_generator.AddFileBytes(audio_data, audio_size); + + targz_generator.AddFile("test/shaders/BumpReflect.fx", shader_size); + targz_generator.AddFileBytes(shader_data, shader_size); + + targz_generator.Finalize(); + +#if defined(GENERATE_GOLDEN) + std::string new_golden_file = *g_program_path + + "/archive_files/new_golden_test2.tar.gz"; + file_util::WriteFile(UTF8ToFilePath(new_golden_file), + test_client.received_data(), + test_client.received_data_length()); + +#endif + + test_client.Validate(targz_data); + + free(targz_data); + free(image_data); + free(audio_data); + free(shader_data); +} + +} // namespace o3d diff --git a/o3d/import/cross/targz_processor.cc b/o3d/import/cross/targz_processor.cc new file mode 100644 index 0000000..a2a4ba8 --- /dev/null +++ b/o3d/import/cross/targz_processor.cc @@ -0,0 +1,66 @@ +/* + * 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. + */ + +// +// TarGzProcessor processes a gzipped tar stream (tar.gz) +// compressed byte stream +// + +#include "import/cross/targz_processor.h" + +#include <assert.h> +#include "import/cross/memory_stream.h" + +#define CHUNK 16384 + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// The |gz_decompressor_| processes a compressed byte stream, calling back +// into |tar_processor_| with the decompressed byte stream +// Finally, |tar_processor_| calls back to |callback_client| with file header +// and file data callbacks... +// +TarGzProcessor::TarGzProcessor(ArchiveCallbackClient *callback_client) + : ArchiveProcessor(callback_client), + tar_processor_(callback_client), + gz_decompressor_(&tar_processor_) { +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +int TarGzProcessor::ProcessCompressedBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + int result = gz_decompressor_.ProcessBytes(stream, bytes_to_process); + return result; +} + +} // namespace o3d diff --git a/o3d/import/cross/targz_processor.h b/o3d/import/cross/targz_processor.h new file mode 100644 index 0000000..56b5427 --- /dev/null +++ b/o3d/import/cross/targz_processor.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +// +// TarGzProcessor processes a gzipped tar stream (tar.gz) +// compressed byte stream +// + +#ifndef O3D_IMPORT_CROSS_TARGZ_PROCESSOR_H_ +#define O3D_IMPORT_CROSS_TARGZ_PROCESSOR_H_ + +#include "base/basictypes.h" +#include "import/cross/archive_processor.h" +#include "import/cross/gz_decompressor.h" +#include "import/cross/tar_processor.h" + +#include "third_party/zlib/files/zlib.h" + +namespace o3d { + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TarGzProcessor : public ArchiveProcessor { + public: + explicit TarGzProcessor(ArchiveCallbackClient *callback_client); + + virtual int ProcessCompressedBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + private: + TarProcessor tar_processor_; + GzDecompressor gz_decompressor_; + + DISALLOW_COPY_AND_ASSIGN(TarGzProcessor); +}; + +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_TARGZ_PROCESSOR_H_ diff --git a/o3d/import/cross/targz_processor_test.cc b/o3d/import/cross/targz_processor_test.cc new file mode 100644 index 0000000..fd98f27 --- /dev/null +++ b/o3d/import/cross/targz_processor_test.cc @@ -0,0 +1,142 @@ +/* + * 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. + */ + + +#include "core/cross/client.h" +#include "import/cross/targz_processor.h" +#include "tests/common/win/testing_common.h" + +namespace o3d { + +class TarGzProcessorTest : public testing::Test { +}; + +// We verify that the tar.gz file contains exactly these filenames +const char *kFilename1 = "test/file1"; +const char *kFilename2 = "test/file2"; +const char *kFilename3 = "test/file3"; + +// With each file having these exact contents +#define kFileContents1 "the cat in the hat\n" +#define kFileContents2 "abracadabra\n" +#define kFileContents3 "I think therefore I am\n" + +// we should receive these (and exactly these bytes in this order) +const char *kConcatenatedContents = + kFileContents1 kFileContents2 kFileContents3; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class ArchiveTestClient : public ArchiveCallbackClient { + public: + explicit ArchiveTestClient() : file_count_(0), index_(0) {} + // ArchiveCallbackClient methods + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); + virtual bool ReceiveFileData(MemoryReadStream *stream, int nbytes); + + int GetFileCount() const { return file_count_; } + size_t GetNumTotalBytesReceived() const { return index_; } + + private: + int file_count_; + int index_; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void ArchiveTestClient::ReceiveFileHeader(const ArchiveFileInfo &file_info) { + // We get called one time for each file in the archive + + // Check that the filenames match our expectation + switch (file_count_) { + case 0: + EXPECT_TRUE(!strcmp(kFilename1, file_info.GetFileName().c_str())); + break; + case 1: + EXPECT_TRUE(!strcmp(kFilename2, file_info.GetFileName().c_str())); + break; + case 2: + EXPECT_TRUE(!strcmp(kFilename3, file_info.GetFileName().c_str())); + break; + } + + file_count_++; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool ArchiveTestClient::ReceiveFileData(MemoryReadStream *stream, int nbytes) { + const char *p = reinterpret_cast<const char*>( + stream->GetDirectMemoryPointer()); + + // Note: ReceiveFileData() may be called multiple times for each file, until + // the complete file contents have been given... + + // We're receiving three file, one after the other + // The bytes we receive are the concatenated contents of the three files + // (with calls to ReceiveFileHeader() separating each one) + // + EXPECT_TRUE(index_ + nbytes <= strlen(kConcatenatedContents)); + EXPECT_TRUE(!strncmp(kConcatenatedContents + index_, p, nbytes)); + + index_ += nbytes; + + return true; +} + +// Loads a tar.gz file, runs it through the processor. +// In our callbacks, we verify that we receive three files with known contents +// +TEST_F(TarGzProcessorTest, LoadTarGzFile) { + String filepath = *g_program_path + "/archive_files/test1.tar.gz"; + + ArchiveTestClient test_callback_client; + + TarGzProcessor processor(&test_callback_client); + int result = processor.ProcessFile(filepath.c_str()); + EXPECT_EQ(Z_OK, result); + + EXPECT_EQ(3, test_callback_client.GetFileCount()); + EXPECT_EQ(strlen(kConcatenatedContents), + test_callback_client.GetNumTotalBytesReceived()); +} + +// Tries to load something with a tar.gz extension, but which isn't +// really a tar.gz and contains random text +// +TEST_F(TarGzProcessorTest, LoadBogusTarGzFile) { + String filepath = *g_program_path + "/archive_files/bogus.tar.gz"; + + ArchiveTestClient test_callback_client; + + TarGzProcessor processor(&test_callback_client); + int result = processor.ProcessFile(filepath.c_str()); + EXPECT_TRUE(result != Z_OK); +} + +} // namespace diff --git a/o3d/import/cross/zip_archive.cc b/o3d/import/cross/zip_archive.cc new file mode 100644 index 0000000..275722f --- /dev/null +++ b/o3d/import/cross/zip_archive.cc @@ -0,0 +1,876 @@ +/* + * 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. + */ + + +// A basic C++ wrapper for a zip file +// Adapted from miniunz.c from minizip open source code by Gilles Vollant. +// Copyright (C) 1998-2005 Gilles Vollant + +#include "import/cross/zip_archive.h" + +#include "base/basictypes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <fcntl.h> + +#ifdef OS_POSIX +#include <unistd.h> +#include <utime.h> +#include <sys/stat.h> +#else +#include <direct.h> +#include <io.h> +#include <rpc.h> +#endif + +using std::vector; +using std::string; + +#ifndef ALLOW_USER_QUERY +#define ALLOW_USER_QUERY 0 // 1: ask before overwriting files +#endif + +#ifndef DEBUGLOG_DESTINATION +#define DEBUGLOG_DESTINATION 0 // 0: no debug output 1: output to stdout +#endif + +#if DEBUGLOG_DESTINATION == 1 +#define DEBUGLOG(...) printf(__VA_ARGS__) +#else +#define DEBUGLOG(...) +#endif + + +#define CASESENSITIVITY (1) // activate case sensitivity +#define WRITEBUFFERSIZE (8192) +#define MAXFILENAME (1024) + +// Creates a basic C++ wrapper for a zip file +ZipArchive::ZipArchive(const std::string &zip_filename, int *result) + : zip_filename_(zip_filename), zip_file_ref_(0) { + const char *zip_filename_c = zip_filename_.c_str(); + char filename_try[MAXFILENAME + 16] = ""; + strncpy(filename_try, zip_filename_c, MAXFILENAME - 1); + + // strncpy doesn't append the trailing NULL if the string is too long. + filename_try[MAXFILENAME] = '\0'; + + zip_file_ref_ = unzOpen(zip_filename_c); + + // try appending .zip if |zip_filename| as given wasn't found + if (zip_file_ref_ == NULL) { + strcat(filename_try, ".zip"); + zip_file_ref_ = unzOpen(filename_try); + } + + if (zip_file_ref_ == NULL) { + DEBUGLOG("Cannot open %s or %s.zip\n", zip_filename_c, zip_filename_c); + if (result) { + *result = 1; + return; + } + } + DEBUGLOG("%s opened\n", filename_try); + + *result = UNZ_OK; +} + +ZipArchive::~ZipArchive() { + unzClose(zip_file_ref_); + zip_file_ref_ = 0; +} + +// The returned filenames should adhere to the zip archive spec +// (UTF8 with '/' as the path separator) +// If the zip file is badly constructed then this assumption may be invalid. +// The filenames will contain a leading '/', with '/' indicating the "root" +// of the zip archive (as if the zip archive were a filesystem) +// +int ZipArchive::GetInformationList(vector<ZipFileInfo> *infolist) { + if (!infolist) return -1; + + unz_global_info gi; + int result = unzGetGlobalInfo(zip_file_ref_, &gi); + + if (result == UNZ_OK) { + unzGoToFirstFile(zip_file_ref_); + + for (uLong i = 0; i < gi.number_entry; ++i) { + // get the info for this entry + char filename_inzip[MAXFILENAME]; // MAX_PATH + ZipFileInfo file_info; + result = unzGetCurrentFileInfo(zip_file_ref_, + &file_info, + filename_inzip, + sizeof(filename_inzip), + NULL, + 0, + NULL, + 0); + + file_info.name = "/" + string(filename_inzip); + infolist->push_back(file_info); + + if ((i + 1) < gi.number_entry) { + result = unzGoToNextFile(zip_file_ref_); + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzGoToNextFile\n", result); + break; + } + } + } + } + + return result; +} + +// Returns information for |filename| in |*info| +// returns 0 if successful +// |filename| is expected to have a leading '/' (as is returned by +// GetInformationList() ) +// +int ZipArchive::GetFileInfo(const string &filename, ZipFileInfo *info) { + if (!info) return -1; + + unzGoToFirstFile(zip_file_ref_); + unzFile uf = zip_file_ref_; + + string actual_filename; + GetActualFilename(filename, &actual_filename); + + if (unzLocateFile(uf, actual_filename.c_str(), CASESENSITIVITY) != UNZ_OK) { + DEBUGLOG("file %s not found in the zipfile\n", actual_filename.c_str()); + return 2; + } + + // get the info for this entry + char filename_inzip[MAXFILENAME]; + int result = unzGetCurrentFileInfo(uf, + info, + filename_inzip, + sizeof(filename_inzip), + NULL, + 0, + NULL, + 0); + + info->name = "/" + string(filename_inzip); + + return result; +} + +// Extracts the entire archive to disk +int ZipArchive::Extract() { + const char *filename_to_extract = NULL; + const char *password = NULL; + + int opt_do_extract = 1; + int opt_do_extract_withoutpath = 0; + int opt_overwrite = 0; + int opt_extractdir = 0; + const char *dirname = NULL; + + if (opt_do_extract == 1) { + if (opt_extractdir && chdir(dirname)) { + DEBUGLOG("Error changing into %s, aborting\n", dirname); + exit(-1); + } + + if (filename_to_extract == NULL) { + return DoExtract(opt_do_extract_withoutpath, + opt_overwrite, + password); + } else { + return ExtractOneFile(filename_to_extract, + opt_do_extract_withoutpath, + opt_overwrite, + password); + } + } + + unzCloseCurrentFile(zip_file_ref_); +} + +// Extracts a single file to disk +int ZipArchive::ExtractOneFile(const string &filename, + int opt_extract_without_path, + int opt_overwrite, + const char *password) { + string actual_filename; + GetActualFilename(filename, &actual_filename); + + unzGoToFirstFile(zip_file_ref_); + if (unzLocateFile(zip_file_ref_, + actual_filename.c_str(), + CASESENSITIVITY) != UNZ_OK) { + DEBUGLOG("file %s not found in the zipfile\n", actual_filename.c_str()); + return 2; + } + + if (ExtractCurrentFile(&opt_extract_without_path, + &opt_overwrite, + password) == UNZ_OK) { + return UNZ_OK; + } else { + return 1; + } +} + + +// Extracts a single file and returns a pointer to the file's content. +// Returns NULL if |filename| doesn't match any in the archive +// or an error occurs. The caller must call free() on the returned pointer +char *ZipArchive::GetFileData(const string &filename, size_t *size) { + string actual_filename; + GetActualFilename(filename, &actual_filename); + + unzFile uf = zip_file_ref_; + + unzGoToFirstFile(uf); + if (unzLocateFile(uf, actual_filename.c_str(), CASESENSITIVITY) != UNZ_OK) { + DEBUGLOG("file %s not found in the zipfile\n", actual_filename.c_str()); + return NULL; + } + + // determine the size of the uncompressed file + unz_file_info file_info; + char filename_inzip[MAXFILENAME]; + int result = unzGetCurrentFileInfo(uf, + &file_info, + filename_inzip, + sizeof(filename_inzip), + NULL, + 0, + NULL, + 0); + + if (result != UNZ_OK) return NULL; + + if (size) *size = file_info.uncompressed_size; + + result = unzOpenCurrentFilePassword(uf, NULL); + + char *buffer; + + if (result == UNZ_OK) { + const int kBufferChunkSize = 32768; + void *temp_buffer = malloc(kBufferChunkSize); + + // allocate one extra byte so we can NULL terminate + // but don't report this extra byte in the |size| we return + // NULL terminating is useful if the data retrieved is to be interpreted + // as string data and doesn't harm anything else + buffer = reinterpret_cast<char*>(malloc(file_info.uncompressed_size + 1)); + buffer[file_info.uncompressed_size] = 0; + int buffer_index = 0; + + int nbytes; + do { + nbytes = unzReadCurrentFile(uf, temp_buffer, kBufferChunkSize); + if (nbytes < 0) { + DEBUGLOG("error %d with zipfile in unzReadCurrentFile\n", result); + result = -1; + break; + } + if (nbytes > 0) { + // check that we're not exceeding the expected uncompressed size! + if (buffer_index + nbytes > file_info.uncompressed_size) { + result = -2; + break; + } + memcpy(buffer + buffer_index, temp_buffer, nbytes); + buffer_index += nbytes; + } + } while (nbytes > 0); + + free(temp_buffer); + + if (result == UNZ_OK) { + result = unzCloseCurrentFile(uf); + } + } + + if (result != UNZ_OK) { + free(buffer); + buffer = NULL; + } + + return buffer; +} + +// Convert paths relative to |root_path| to archive paths +// The |root_path| should start with '/' and should be an actual +// directory in the zip archive. +// +char *ZipArchive::GetRelativeFileData(const string &relative_path, + const string &root_path, + size_t *size) { + string converted_filename(relative_path); + ConvertRelativeToAbsolutePath(&converted_filename, root_path); + + return GetFileData(converted_filename, size); +} + +// private/protected methods +// +#ifdef OS_MACOSX +#pragma mark - +#endif + +int ZipArchive::ExtractCurrentFile(const int *popt_extract_without_path, + int *popt_overwrite, + const char *password) { + int result = UNZ_OK; + + unz_file_info file_info; + char filename_inzip[MAXFILENAME]; + result = unzGetCurrentFileInfo(zip_file_ref_, + &file_info, + filename_inzip, + sizeof(filename_inzip), + NULL, + 0, + NULL, + 0); + + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzGetCurrentFileInfo\n", result); + return result; + } + + DEBUGLOG("ExtractCurrentFile: %s\n", filename_inzip); + + uInt size_buf = WRITEBUFFERSIZE; + void *buf = malloc(size_buf); + + char *filename_withoutpath; + char *p; + p = filename_withoutpath = filename_inzip; + while ((*p) != '\0') { + if (((*p) == '/') || ((*p) == '\\')) + filename_withoutpath = p + 1; + p++; + } + + if ((*filename_withoutpath) == '\0') { + if ((*popt_extract_without_path) == 0) { + DEBUGLOG("creating directory: %s\n", filename_inzip); + MyMkDir(filename_inzip); + } + } else { + char *write_filename; + int skip = 0; + + if ((*popt_extract_without_path) == 0) { + write_filename = filename_inzip; + } else { + write_filename = filename_withoutpath; + } + + result = unzOpenCurrentFilePassword(zip_file_ref_, password); + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzOpenCurrentFilePassword\n", result); + } + +#if ALLOW_USER_QUERY + if (((*popt_overwrite) == 0) && (result == UNZ_OK)) { + char rep = 0; + FILE* ftestexist; + ftestexist = fopen(write_filename, "rb"); + + if (ftestexist != NULL) { + fclose(ftestexist); + + do { + char answer[128]; + int ret; + + DEBUGLOG("The file %s exists. Overwrite ? [y]es, [n]o, [A]ll: ", + write_filename); + + ret = scanf("%1s", answer); + if (ret != 1) { + exit(EXIT_FAILURE); + } + rep = answer[0]; + if ((rep >= 'a') && (rep <= 'z')) + rep -= 0x20; + } + while ((rep != 'Y') && (rep != 'N') && (rep != 'A')); + } + + if (rep == 'N') + skip = 1; + + if (rep == 'A') + *popt_overwrite = 1; + } +#endif // ALLOW_USER_QUERY + + FILE *fout = NULL; + if ((skip == 0) && (result == UNZ_OK)) { + DEBUGLOG("fopen: %s\n", write_filename); + + fout = fopen(write_filename, "wb"); + + // some zipfiles don't contain directory alone before file + if ((fout == NULL) && ((*popt_extract_without_path) == 0) && + (filename_withoutpath != reinterpret_cast<char*>(filename_inzip))) { + char c = *(filename_withoutpath - 1); + *(filename_withoutpath-1) = '\0'; + MakeDir(write_filename); + *(filename_withoutpath - 1) = c; + fout = fopen(write_filename, "wb"); + } + + if (fout == NULL) { + DEBUGLOG("error opening %s\n", write_filename); + } + } + + if (fout != NULL) { + DEBUGLOG(" extracting: %s\n", write_filename); + + do { + result = unzReadCurrentFile(zip_file_ref_, buf, size_buf); + + if (result < 0) { + DEBUGLOG("error %d with zipfile in unzReadCurrentFile\n", result); + break; + } + + if (result > 0) + if (fwrite(buf, result, 1, fout) != 1) { + DEBUGLOG("error in writing extracted file\n"); + result = UNZ_ERRNO; + break; + } + } while (result > 0); + + if (fout) { + fclose(fout); + } + + if (result == 0) { + ChangeFileDate(write_filename, + file_info.dosDate, + file_info.tmu_date); + } + } + + if (result == UNZ_OK) { + result = unzCloseCurrentFile(zip_file_ref_); + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzCloseCurrentFile\n", result); + } + } else { + unzCloseCurrentFile(zip_file_ref_); // don't lose the error + } + } + + free(buf); + return result; +} + +int ZipArchive::DoExtract(int opt_extract_without_path, + int opt_overwrite, + const char *password) { + unz_global_info gi; + int result = unzGetGlobalInfo(zip_file_ref_, &gi); + + if (result != UNZ_OK) + DEBUGLOG("error %d with zipfile in unzGetGlobalInfo \n", result); + + for (uLong i = 0; i < gi.number_entry; ++i) { + if (ExtractCurrentFile(&opt_extract_without_path, + &opt_overwrite, + password) != UNZ_OK) + break; + + if ((i + 1) < gi.number_entry) { + result = unzGoToNextFile(zip_file_ref_); + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzGoToNextFile\n", result); + break; + } + } + } + + return UNZ_OK; +} + +// ChangeFileDate : change the date/time of a file +// filename : the filename of the file where date/time must be modified +// dosdate : the new date at the MSDos format (4 bytes) +// tmu_date : the SAME new date at the tm_unz format +void ZipArchive::ChangeFileDate(const char *filename, + uLong dosdate, + tm_unz tmu_date) { +#if 0 // don't need or want this for now +#ifdef WIN32 + HANDLE hFile; + FILETIME ftm, ftLocal, ftCreate, ftLastAcc, ftLastWrite; + + hFile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + GetFileTime(hFile, &ftCreate, &ftLastAcc, &ftLastWrite); + DosDateTimeToFileTime((WORD)(dosdate>>16), (WORD)dosdate, &ftLocal); + LocalFileTimeToFileTime(&ftLocal, &ftm); + SetFileTime(hFile, &ftm, &ftLastAcc, &ftm); + CloseHandle(hFile); +#else +#ifdef unix + struct utimbuf ut; + struct tm newdate; + newdate.tm_sec = tmu_date.tm_sec; + newdate.tm_min = tmu_date.tm_min; + newdate.tm_hour = tmu_date.tm_hour; + newdate.tm_mday = tmu_date.tm_mday; + newdate.tm_mon = tmu_date.tm_mon; + + if (tmu_date.tm_year > 1900) + newdate.tm_year = tmu_date.tm_year - 1900; + else + newdate.tm_year = tmu_date.tm_year; + + newdate.tm_isdst = -1; + + ut.actime = ut.modtime = mktime(&newdate); + utime(filename, &ut); +#endif +#endif +#endif +} + + +int ZipArchive::MyMkDir(const char *dirname) { + int ret = 0; +#ifdef WIN32 + ret = mkdir(dirname); +#else +#ifdef unix + ret = mkdir(dirname, 0775); +#endif +#endif + return ret; +} + +int ZipArchive::MakeDir(const char *newdir) { + int len = static_cast<int>(strlen(newdir)); + + if (len <= 0) + return 0; + + char *buffer = reinterpret_cast<char*>(malloc(len + 1)); + strcpy(buffer, newdir); + + if (buffer[len - 1] == '/') { + buffer[len - 1] = '\0'; + } + + if (MyMkDir(buffer) == 0) { + free(buffer); + return 1; + } + + char *p = buffer + 1; + while (1) { + while (*p && *p != '\\' && *p != '/') + p++; + + char hold = *p; + *p = 0; + + if ((MyMkDir(buffer) == -1) && (errno == ENOENT)) { + DEBUGLOG("couldn't create directory %s\n", buffer); + free(buffer); + return 0; + } + + if (hold == 0) + break; + + *p++ = hold; + } + + free(buffer); + return 1; +} + + +// Prints information about the given zip archive +int ZipArchive::Print() { + unzGoToFirstFile(zip_file_ref_); + + unz_global_info gi; + int result = unzGetGlobalInfo(zip_file_ref_, &gi); + if (result != UNZ_OK) { + printf("error %d with zipfile in unzGetGlobalInfo \n", result); + } else { + printf(" Length Method Size Ratio Date Time CRC-32 Name\n"); + printf(" ------ ------ ---- ----- ---- ---- ------ ----\n"); + + for (int i = 0; i < gi.number_entry; ++i) { + char filename_inzip[MAXFILENAME]; + ZipFileInfo file_info; + result = unzGetCurrentFileInfo(zip_file_ref_, + &file_info, + filename_inzip, + sizeof(filename_inzip), + NULL, + 0, + NULL, + 0); + + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzGetCurrentFileInfo\n", result); + break; + } + + file_info.name = filename_inzip; + file_info.Print(false); + + if ((i + 1) < gi.number_entry) { + result = unzGoToNextFile(zip_file_ref_); + if (result != UNZ_OK) { + DEBUGLOG("error %d with zipfile in unzGoToNextFile\n", result); + break; + } + } + } + + DEBUGLOG("\n"); + } + + return result; +} + +// Prints information about the given file +void ZipFileInfo::Print(bool print_header) { + if (print_header) { + printf(" Length Method Size Ratio Date Time CRC-32 Name\n"); + printf(" ------ ------ ---- ----- ---- ---- ------ ----\n"); + } + + uLong ratio = 0; + if (uncompressed_size > 0) { + ratio = (compressed_size * 100) / uncompressed_size; + } + + // display a '*' if the file is encrypted + char charCrypt = ' '; + if ((flag & 1) != 0) { + charCrypt = '*'; + } + + const char *string_method; + if (compression_method == 0) { + string_method = "Stored"; + } else { + if (compression_method == Z_DEFLATED) { + uInt iLevel = (uInt)((flag & 0x6) / 2); + if (iLevel == 0) { + string_method = "Defl:N"; + } else if (iLevel == 1) { + string_method = "Defl:X"; + } else if ((iLevel == 2) || (iLevel == 3)) { + string_method = "Defl:F"; // 2:fast , 3 : extra fast + } + } else { + string_method = "Unkn. "; + } + } + + printf("%7lu %6s%c%7lu %3lu%% %2.2lu-%2.2lu-%2.2lu " + "%2.2lu:%2.2lu %8.8lx %s\n", + uncompressed_size, + string_method, + charCrypt, + compressed_size, + ratio, + (uLong)tmu_date.tm_mon + 1, + (uLong)tmu_date.tm_mday, + (uLong)tmu_date.tm_year % 100, + (uLong)tmu_date.tm_hour, + (uLong)tmu_date.tm_min, + (uLong)crc, name.c_str()); +} + +bool ZipArchive::IsZipFile(const std::string& filename) { + int result; + // If we can open it, it's a zip file. + ZipArchive archive(filename, &result); + return result == UNZ_OK; +} + +#ifdef OS_MACOSX +#pragma mark - +#endif + +// assumes |path| is UTF8 with '/' as the path separator +void ZipArchive::RemoveLastPathComponent(string *path) { + // This gets rid of trailing slashes, if any. + int length = path->size(); + while ((*path)[length - 1] == '/' && length > 0) { + path->resize(length - 1); + length = path->size(); + } + + string::size_type index = path->find_last_of('/'); + + if (index == string::npos) { + *path = ""; + } else { + path->resize(index + 1); // keep a trailing '/' + } +} + +// This assumes |path| is UTF8 with '/' as the path separator normally +// it should be a relative IETF URI path +void ZipArchive::ConvertRelativeToAbsolutePath(string *rel_path, + const string &root_path) { + string base_path(root_path); + string path(*rel_path); + + if (!path.empty() && path[0] == '/') { + // Path is already absolute. + return; + } else { + while (path.size() >= 2 && path[0] == '.' && path[1] == '/') { + path = path.substr(2); // strip off leading ./'s + } + + while (path.find("../") == 0) { + path = path.substr(3); // strip off a leading ../ + RemoveLastPathComponent(&base_path); // strip off a base dir element. + } + + *rel_path = base_path + path; + } +} + +// This removes leading '/' which is the form that the minizip library +// likes. The public ZipArchive API expects pathnames to have the +// leading '/' treating the zip archive as a file-system rooted at '/' +void ZipArchive::GetActualFilename(const string &filename, + string *actual_filename) { + if (filename.find('/') == 0) { + *actual_filename = filename.substr(1); + } else { + *actual_filename = filename; + } +} + +bool ZipArchive::GetTempFileFromFile(const string &filename, + string *temp_filename) { + if (!temp_filename) return false; + + size_t data_size; + char *data = GetFileData(filename, &data_size); + + if (data) { +#ifdef OS_WIN + // get the temp directory + char temp_path[MAX_PATH]; + if (!GetTempPathA(MAX_PATH, temp_path)) { + return false; + } + + // now generate a GUID + UUID guid = {0}; + UuidCreate(&guid); + + // and format into a wide-string + char guid_string[37]; + snprintf( + guid_string, sizeof(guid_string) / sizeof(guid_string[0]), + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + + // format a complete file path for the temp file + char fullpath[MAX_PATH]; + + int dot_position = filename.rfind('.'); + if (dot_position != string::npos) { + // try to retain the original file suffix (.jpg, etc.) + snprintf(fullpath, MAX_PATH, "%s%s%s", + temp_path, + guid_string, + filename.substr(dot_position).c_str()); + } else { + snprintf(fullpath, MAX_PATH, "%s\\%s", + temp_path, + guid_string); + } + + FILE *tempfile = fopen(fullpath, "wb"); + + if (tempfile) { + fwrite(data, 1, data_size, tempfile); + fclose(tempfile); + *temp_filename = fullpath; + } else { + return false; + } + +#else + // get just the final path component + int pos = filename.rfind('/'); + if (pos != string::npos) { + // TODO : need to get "proper" temp dir for user + // TODO : need to append GUID to filename + std::string tmp = "/tmp/" + filename.substr(pos + 1); + FILE *fp = fopen(tmp.c_str(), "w"); + + if (fp) { + fwrite(data, 1, data_size, fp); + fclose(fp); + *temp_filename = tmp; + } else { + return false; + } + } +#endif + } + + return true; +} + +void ZipArchive::DeleteFile(const string &filename) { + unlink(filename.c_str()); +} diff --git a/o3d/import/cross/zip_archive.h b/o3d/import/cross/zip_archive.h new file mode 100644 index 0000000..4c55539 --- /dev/null +++ b/o3d/import/cross/zip_archive.h @@ -0,0 +1,216 @@ +/* + * 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. + */ + + +// A basic C++ wrapper for a zip file +// Adapted from miniunz.c from minizip open source code by Gilles Vollant. + +/* + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow creates .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + For uncompress .zip file, look at unzip.h + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.html for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + + +/* Examples Usages: + + int result; + ZipArchive archive("test.zip", &result); + + if (result == UNZ_OK) { + archive.Print(); + + vector<ZipFileInfo> infolist; + archive.GetInformationList(&infolist); + + // get all file information and print all file names + for (int i = 0; i < infolist.size(); ++i) { + printf("[%d] %s\n", i + 1, infolist[i].name.c_str() ); + } + + // can get information based on file name in the archive + // print information about the first file + if (infolist.size() > 0) { + ZipFileInfo info; + result = archive.GetFileInfo(infolist[0].name, &info); + + if (result == UNZ_OK) { + info.Print(true); + } + } + + + size_t data_size; + char *data = archive.GetFileData(infolist[0].name, &data_size); + printf("data = %p : data_size = %d\n", data, data_size); +*/ + +#ifndef O3D_IMPORT_CROSS_ZIP_ARCHIVE_H_ +#define O3D_IMPORT_CROSS_ZIP_ARCHIVE_H_ + +#include <string> +#include <vector> + +#ifdef OS_MACOSX +#define unix +#endif + +#include "third_party/zlib/files/contrib/minizip/unzip.h" + +// structure containing the unz_file_info information plus the file name +struct ZipFileInfo : public unz_file_info { + std::string name; + + void Print(bool print_header); // prints info to stdout +}; + +class ZipArchive { + public: + // Returns UNZ_OK in |result| on success + ZipArchive(const std::string &zip_filename, int *result); + virtual ~ZipArchive(); + + // The returned filenames should adhere to the zip archive spec + // (UTF8 with '/' as the path separator) + // If the zip file is badly constructed then this assumption may be invalid. + // returns UNZ_OK on success + int GetInformationList(std::vector<ZipFileInfo> *infolist); + + // Returns information for |filename| in |*info| + // returns UNZ_OK if successful + virtual int GetFileInfo(const std::string &filename, ZipFileInfo *info); + + // Extracts a single file and returns a pointer to the file's content. + // Returns NULL if |filename| doesn't match any in the archive + // or an error occurs. The caller must call free() on the returned pointer + // the buffer allocated will actually be one byte greater then reported in + // |size|. And this extra byte is set to zero. This way the data may + // be interpreted as a string, and doesn't harm anything else. + // + virtual char *GetFileData(const std::string &filename, size_t *size); + + // |relative_path| is taken to be relative to |root_path| + // It may contain relative path elements ("../") + // + // Extracts a single file and returns a pointer to the file's content. + // Returns NULL if |filename| doesn't match any in the archive + // or an error occurs. The caller must call free() on the returned pointer + // + virtual char *GetRelativeFileData(const std::string &relative_path, + const std::string &root_path, + size_t *size); + + // Extracts the entire archive to disk (relative to current working dir) + // returns UNZ_OK on success + int Extract(); + + // Extracts a single file to disk (relative to current working dir) + // returns UNZ_OK on success + int ExtractOneFile(const std::string &filename, + int opt_extract_without_path, + int opt_overwrite, + const char *password); + + // Extracts |filename| from the archive, saves to a temp file, and sets + // the path to the temp file in |temp_filename| + // returns |true| on success + // + bool GetTempFileFromFile(const std::string &filename, + std::string *temp_filename); + + // So we can delete the temp file we made + static void DeleteFile(const std::string &filename); + + // Lists content of archive to stdout + // returns UNZ_OK on success + int Print(); + + // Tests the given file to see if it is a zip file. + // (Added by Google) + static bool IsZipFile(const std::string& filename); + + protected: + void ChangeFileDate(const char *filename, uLong dosdate, tm_unz tmu_date); + + int MyMkDir(const char *dirname); + + int MakeDir(const char *newdir); + + int ExtractCurrentFile(const int *popt_extract_without_path, + int *popt_overwrite, + const char *password); + + int DoExtract(int opt_extract_without_path, + int opt_overwrite, + const char *password); + + void RemoveLastPathComponent(std::string *string); + + void ConvertRelativeToAbsolutePath(std::string *path, + const std::string &root_path); + + void GetActualFilename(const std::string &filename, + std::string *actual_filename); + + std::string zip_filename_; + unzFile zip_file_ref_; +}; + +#endif // O3D_IMPORT_CROSS_ZIP_ARCHIVE_H_ diff --git a/o3d/import/fcollada.scons b/o3d/import/fcollada.scons new file mode 100644 index 0000000..0849f9f --- /dev/null +++ b/o3d/import/fcollada.scons @@ -0,0 +1,267 @@ +# 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. + + +Import('env') + +c_sources = [ + 'LibXML/DOCBparser', + 'LibXML/HTMLparser', + 'LibXML/HTMLtree', + 'LibXML/SAX', + 'LibXML/SAX2', + 'LibXML/c14n', + 'LibXML/catalog', + 'LibXML/chvalid', + 'LibXML/debugXML', + 'LibXML/dict', + 'LibXML/encoding', + 'LibXML/entities', + 'LibXML/error', + 'LibXML/globals', + 'LibXML/hash', + 'LibXML/legacy', + 'LibXML/list', + 'LibXML/nanoftp', + 'LibXML/nanohttp', + 'LibXML/parser', + 'LibXML/parserInternals', + 'LibXML/pattern', + 'LibXML/relaxng', + 'LibXML/threads', + 'LibXML/tree', + 'LibXML/uri', + 'LibXML/valid', + 'LibXML/xinclude', + 'LibXML/xlink', + 'LibXML/xmlIO', + 'LibXML/xmlcatalog', + 'LibXML/xmlmemory', + 'LibXML/xmlmodule', + 'LibXML/xmlreader', + 'LibXML/xmlregexp', + 'LibXML/xmlsave', + 'LibXML/xmlstring', + 'LibXML/xmlunicode', + 'LibXML/xmlwriter', +] + +cpp_sources = [ + 'DLLEntry', + 'FCollada', + 'FColladaPlugin', + 'StdAfx', + + 'FArchiveXML/FAXAnimationExport', + 'FArchiveXML/FAXAnimationImport', + 'FArchiveXML/FAXCameraExport', + 'FArchiveXML/FAXCameraImport', + 'FArchiveXML/FAXColladaParser', + 'FArchiveXML/FAXColladaWriter', + 'FArchiveXML/FAXControllerExport', + 'FArchiveXML/FAXControllerImport', + 'FArchiveXML/FAXEmitterExport', + 'FArchiveXML/FAXEmitterImport', + 'FArchiveXML/FAXEntityExport', + 'FArchiveXML/FAXEntityImport', + 'FArchiveXML/FAXForceFieldExport', + 'FArchiveXML/FAXForceFieldImport', + 'FArchiveXML/FAXGeometryExport', + 'FArchiveXML/FAXGeometryImport', + 'FArchiveXML/FAXImportLinking', + 'FArchiveXML/FAXInstanceExport', + 'FArchiveXML/FAXInstanceImport', + 'FArchiveXML/FAXLightExport', + 'FArchiveXML/FAXLightImport', + 'FArchiveXML/FAXMaterialExport', + 'FArchiveXML/FAXMaterialImport', + 'FArchiveXML/FAXPhysicsExport', + 'FArchiveXML/FAXPhysicsImport', + 'FArchiveXML/FAXSceneExport', + 'FArchiveXML/FAXSceneImport', + 'FArchiveXML/FArchiveXML', + 'FArchiveXML/StdAfx', + + 'FCDocument/FCDAnimated', + 'FCDocument/FCDAnimation', + 'FCDocument/FCDAnimationChannel', + 'FCDocument/FCDAnimationClip', + 'FCDocument/FCDAnimationClipTools', + 'FCDocument/FCDAnimationCurve', + 'FCDocument/FCDAnimationCurveTools', + 'FCDocument/FCDAnimationKey', + 'FCDocument/FCDAnimationMultiCurve', + 'FCDocument/FCDAsset', + 'FCDocument/FCDCamera', + 'FCDocument/FCDController', + 'FCDocument/FCDControllerInstance', + 'FCDocument/FCDControllerTools', + 'FCDocument/FCDEffect', + 'FCDocument/FCDEffectCode', + 'FCDocument/FCDEffectParameter', + 'FCDocument/FCDEffectParameterFactory', + 'FCDocument/FCDEffectParameterSampler', + 'FCDocument/FCDEffectParameterSurface', + 'FCDocument/FCDEffectPass', + 'FCDocument/FCDEffectPassShader', + 'FCDocument/FCDEffectPassState', + 'FCDocument/FCDEffectProfile', + 'FCDocument/FCDEffectProfileFX', + 'FCDocument/FCDEffectStandard', + 'FCDocument/FCDEffectTechnique', + 'FCDocument/FCDEffectTools', + 'FCDocument/FCDEmitter', + 'FCDocument/FCDEmitterInstance', + 'FCDocument/FCDEmitterObject', + 'FCDocument/FCDEmitterParticle', + 'FCDocument/FCDEntity', + 'FCDocument/FCDEntityInstance', + 'FCDocument/FCDEntityReference', + 'FCDocument/FCDExternalReferenceManager', + 'FCDocument/FCDExtra', + 'FCDocument/FCDForceDeflector', + 'FCDocument/FCDForceDrag', + 'FCDocument/FCDForceField', + 'FCDocument/FCDForceGravity', + 'FCDocument/FCDForcePBomb', + 'FCDocument/FCDForceWind', + 'FCDocument/FCDGeometry', + 'FCDocument/FCDGeometryInstance', + 'FCDocument/FCDGeometryMesh', + 'FCDocument/FCDGeometryNURBSSurface', + 'FCDocument/FCDGeometryPolygons', + 'FCDocument/FCDGeometryPolygonsInput', + 'FCDocument/FCDGeometryPolygonsTools', + 'FCDocument/FCDGeometrySource', + 'FCDocument/FCDGeometrySpline', + 'FCDocument/FCDImage', + 'FCDocument/FCDLibrary', + 'FCDocument/FCDLight', + 'FCDocument/FCDLightTools', + 'FCDocument/FCDMaterial', + 'FCDocument/FCDMaterialInstance', + 'FCDocument/FCDMorphController', + 'FCDocument/FCDObject', + 'FCDocument/FCDObjectWithId', + 'FCDocument/FCDParameterAnimatable', + 'FCDocument/FCDParticleModifier', + 'FCDocument/FCDPhysicsAnalyticalGeometry', + 'FCDocument/FCDPhysicsForceFieldInstance', + 'FCDocument/FCDPhysicsMaterial', + 'FCDocument/FCDPhysicsModel', + 'FCDocument/FCDPhysicsModelInstance', + 'FCDocument/FCDPhysicsRigidBody', + 'FCDocument/FCDPhysicsRigidBodyInstance', + 'FCDocument/FCDPhysicsRigidBodyParameters', + 'FCDocument/FCDPhysicsRigidConstraint', + 'FCDocument/FCDPhysicsRigidConstraintInstance', + 'FCDocument/FCDPhysicsScene', + 'FCDocument/FCDPhysicsShape', + 'FCDocument/FCDPlaceHolder', + 'FCDocument/FCDSceneNode', + 'FCDocument/FCDSceneNodeIterator', + 'FCDocument/FCDSceneNodeTools', + 'FCDocument/FCDSkinController', + 'FCDocument/FCDTargetedEntity', + 'FCDocument/FCDTexture', + 'FCDocument/FCDTransform', + 'FCDocument/FCDVersion', + 'FCDocument/FCDocument', + 'FCDocument/FCDocumentTools', + + 'FMath/FMAllocator', + 'FMath/FMAngleAxis', + 'FMath/FMColor', + 'FMath/FMInterpolation', + 'FMath/FMLookAt', + 'FMath/FMMatrix33', + 'FMath/FMMatrix44', + 'FMath/FMQuaternion', + 'FMath/FMRandom', + 'FMath/FMSkew', + 'FMath/FMVector3', + 'FMath/FMVolume', + 'FMath/StdAfx', + + 'FUtils/FUAssert', + 'FUtils/FUBase64', + 'FUtils/FUBoundingBox', + 'FUtils/FUBoundingSphere', + 'FUtils/FUCrc32', + 'FUtils/FUCriticalSection', + 'FUtils/FUDaeEnum', + 'FUtils/FUDateTime', + 'FUtils/FUDebug', + 'FUtils/FUError', + 'FUtils/FUErrorLog', + 'FUtils/FUFile', + 'FUtils/FUFileManager', + 'FUtils/FULogFile', + 'FUtils/FUObject', + 'FUtils/FUObjectType', + 'FUtils/FUParameter', + 'FUtils/FUParameterizable', + 'FUtils/FUPluginManager', + 'FUtils/FUSemaphore', + 'FUtils/FUStringBuilder', + 'FUtils/FUStringConversion', + 'FUtils/FUSynchronizableObject', + 'FUtils/FUThread', + 'FUtils/FUTracker', + 'FUtils/FUUniqueStringMap', + 'FUtils/FUUniqueStringMapTest', + 'FUtils/FUUri', + 'FUtils/FUXmlDocument', + 'FUtils/FUXmlParser', + 'FUtils/FUXmlWriter', + 'FUtils/StdAfx', +] + +env.Append(CPPPATH=['$COLLADA_DIR/LibXML/include', '$COLLADA_DIR'], + # Always set RETAIL so that we don't link in tests. + CPPDEFINES=['RETAIL']) + +# TESTING is added on our test- targets but messes up fcollada +env.FilterOut(CPPDEFINES=['TESTING']) + +if env.Bit('mac'): + # LibXML needs this define on Mac. + env.Append(CPPDEFINES = ['OS_MACINTOSH']) + +if env.Bit('linux'): + # On linux, -O3 seems to cause problems with template instantiation, -O2 is + # fine. + if not env['DEBUG']: + env.FilterOut(CCFLAGS=['-O3']) + env.Append(CCFLAGS=['-O2']) + +inputs = env.MakeObjects(c_sources, '$COLLADA_DIR', 'c') +inputs += env.MakeObjects(cpp_sources, '$COLLADA_DIR', 'cpp') + +env.ComponentLibrary('FColladaU', inputs) diff --git a/o3d/import/linux/collada_conditioner_linux.cc b/o3d/import/linux/collada_conditioner_linux.cc new file mode 100644 index 0000000..640c134 --- /dev/null +++ b/o3d/import/linux/collada_conditioner_linux.cc @@ -0,0 +1,120 @@ +/* + * 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 defintions of the functions in the +// conditioner namespace, which do the bulk of the work of +// conditioning a Collada file for use in O3D. + +#include "import/cross/precompile.h" + +#include <sys/wait.h> +#include <vector> + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "core/cross/error.h" +#include "core/cross/service_locator.h" +#include "import/cross/collada.h" +#include "import/cross/collada_conditioner.h" + +namespace o3d { + +bool ColladaConditioner::CompileHLSL(const String& shader_source, + const String& vs_entry, + const String& ps_entry) { + // The HLSL compiler isn't available on Linux. + return true; +} + +// Find cgc executable, and run it on the input so that we can get a +// preprocessed version out. +bool ColladaConditioner::PreprocessShaderFile(const FilePath& in_filename, + const FilePath& out_filename) { + FilePath executable_path; + if (!PathService::Get(base::DIR_EXE, &executable_path)) { + O3D_ERROR(service_locator_) << "Couldn't get executable path."; + return false; + } + executable_path = executable_path.Append(FILE_PATH_LITERAL("cgc")); + + std::vector<const char*> args; + args.push_back(executable_path.value().c_str()); + args.push_back("-E"); + args.push_back(in_filename.value().c_str()); + args.push_back("-o"); + args.push_back(out_filename.value().c_str()); + args.push_back(NULL); + char** args_array = const_cast<char**>(&*args.begin()); + + pid_t pid = ::fork(); + if (pid < 0) { + return false; + } + if (pid == 0) { + DLOG(INFO) << "Now invoking '" + << args_array[0] << " " + << args_array[1] << " " + << args_array[2] << " " + << args_array[3] << " " + << args_array[4] << "'"; + char *environ[] = {NULL}; + ::execve(args[0], args_array, environ); + NOTREACHED() << "Problems invoking " + << args_array[0] << " " + << args_array[1] << " " + << args_array[2] << " " + << args_array[3] << " " + << args_array[4]; + } + + int status; + ::wait(&status); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return true; + } else { + if (WIFEXITED(status)) { + DLOG(ERROR) << "Cgc terminated with status: " << WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + DLOG(ERROR) << "Cgc received a signal: " << WTERMSIG(status); + if (WCOREDUMP(status)) { + DLOG(ERROR) << "and Cgc dumped a core file."; + } + } + if (WIFSTOPPED(status)) { + DLOG(ERROR) << "Cgc is stopped on a signal: " << WSTOPSIG(status); + } + } + return false; +} +} // end namespace o3d diff --git a/o3d/import/mac/collada_conditioner_mac.mm b/o3d/import/mac/collada_conditioner_mac.mm new file mode 100644 index 0000000..97f98fa --- /dev/null +++ b/o3d/import/mac/collada_conditioner_mac.mm @@ -0,0 +1,105 @@ +/* + * 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 defintions of the functions in the +// conditioner namespace, which do the bulk of the work of +// conditioning a Collada file for use in O3D. + +#include "import/cross/precompile.h" + +#include <vector> +#import <Cocoa/Cocoa.h> + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "core/cross/error.h" +#include "import/cross/collada.h" +#include "import/cross/collada_conditioner.h" +#include "utils/cross/file_path_utils.h" + +namespace o3d { +bool ColladaConditioner::CompileHLSL(const String& shader_source, + const String& vs_entry, + const String& ps_entry) { + // The HLSL compiler isn't available on Mac. + return true; +} + +// Find cgc executable, and run it on the input so that we can get a +// preprocessed version out. +bool ColladaConditioner::PreprocessShaderFile(const FilePath& in_filename, + const FilePath& out_filename) { + FilePath executable_path; + if (!PathService::Get(base::DIR_EXE, &executable_path)) { + O3D_ERROR(service_locator_) << "Couldn't get executable path."; + return false; + } + executable_path = executable_path.Append(FILE_PATH_LITERAL("cgc")); + + // Have to convert the file paths to UTF-8 + std::string executable_path_utf8 = FilePathToUTF8(executable_path); + std::string in_filename_utf8 = FilePathToUTF8(in_filename); + std::string out_filename_utf8 = FilePathToUTF8(out_filename); + + DLOG(INFO) << "Now invoking '" + << executable_path_utf8 << " -E " + << in_filename_utf8 << " -o " + << out_filename_utf8 << "'"; + + // Now we have to convert the UTF-8 file paths to NSStrings. + NSString *executable_path_ns = + [[[NSString alloc] initWithUTF8String:executable_path_utf8.c_str()] + autorelease]; + NSString *in_filename_ns = + [[[NSString alloc] initWithUTF8String:in_filename_utf8.c_str()] + autorelease]; + NSString *out_filename_ns = + [[[NSString alloc] initWithUTF8String:out_filename_utf8.c_str()] + autorelease]; + + NSTask *task = [[[NSTask alloc] init] autorelease]; + [task setLaunchPath:executable_path_ns]; + [task setArguments:[NSArray arrayWithObjects:@"-E", in_filename_ns, + @"-o", out_filename_ns, nil]]; + + // We send the output to a pipe, even though the tool doesn't really + // output anything. This is so that the invocation of the command + // will be synchronous, and we can be sure that the output file has + // been written by the time we exit this function. + NSPipe *pipe = [NSPipe pipe]; + [task setStandardOutput:pipe]; + [task launch]; + NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile]; + return true; +} +} // end namespace o3d diff --git a/o3d/import/test_data/crate.dae b/o3d/import/test_data/crate.dae new file mode 100644 index 0000000..36565ffd --- /dev/null +++ b/o3d/import/test_data/crate.dae @@ -0,0 +1,239 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +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. +--> + +<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1"> + <asset> + <contributor> + <authoring_tool>Maya2008 | ColladaMaya v3.05B</authoring_tool> + <comments>ColladaMaya export options: bakeTransforms=0;exportPolygonMeshes=1;bakeLighting=0;isSampling=0; +curveConstrainSampling=0;removeStaticCurves=1;exportCameraAsLookat=0; +exportLights=1;exportCameras=1;exportJointsAndSkin=1; +exportAnimations=1;exportTriangles=0;exportInvisibleNodes=0; +exportNormals=1;exportTexCoords=1; +exportVertexColors=1;exportVertexColorsAnimation=0;exportTangents=0; +exportTexTangents=0;exportConstraints=1;exportPhysics=1;exportXRefs=0; +dereferenceXRefs=1;cameraXFov=0;cameraYFov=1</comments> + </contributor> + <created>2008-03-05T22:10:14Z</created> + <modified>2008-03-05T22:10:14Z</modified> + <unit meter="0.01" name="centimeter"/> + <up_axis>Y_UP</up_axis> + </asset> + <library_physics_scenes> + <physics_scene id="MayaNativePhysicsScene"> + <technique_common> + <gravity>0 -980 0</gravity> + <time_step>0.083</time_step> + </technique_common> + </physics_scene> + </library_physics_scenes> + <library_images> + <image id="file1" name="file1"> + <init_from>rock01.tga</init_from> + <extra> + <technique profile="MAYA"> + <dgnode_type>kFile</dgnode_type> + <image_sequence>0</image_sequence> + </technique> + </extra> + </image> + </library_images> + <library_materials> + <material id="phong1" name="phong1"> + <instance_effect url="#phong1-fx"/> + </material> + </library_materials> + <library_effects> + <effect id="phong1-fx"> + <profile_COMMON> + <newparam sid="file1-surface"> + <surface type="2D"> + <init_from>file1</init_from> + <format>A8R8G8B8</format> + </surface> + </newparam> + <newparam sid="file1-sampler"> + <sampler2D> + <source>file1-surface</source> + <wrap_s>WRAP</wrap_s> + <wrap_t>WRAP</wrap_t> + <minfilter>NONE</minfilter> + <magfilter>NONE</magfilter> + <mipfilter>NONE</mipfilter> + </sampler2D> + </newparam> + <technique sid="common"> + <phong> + <emission> + <color>0 0 0 1</color> + </emission> + <ambient> + <color>0 0 0 1</color> + </ambient> + <diffuse> + <texture texture="file1-sampler" texcoord="TEX0"> + <extra> + <technique profile="MAYA"> + <wrapU>1</wrapU> + <wrapV>1</wrapV> + <mirrorU>0</mirrorU> + <mirrorV>0</mirrorV> + <coverageU>1</coverageU> + <coverageV>1</coverageV> + <translateFrameU>0</translateFrameU> + <translateFrameV>0</translateFrameV> + <rotateFrame>0</rotateFrame> + <stagger>0</stagger> + <fast>0</fast> + <repeatU>1</repeatU> + <repeatV>1</repeatV> + <offsetU>0</offsetU> + <offsetV>0</offsetV> + <rotateUV>0</rotateUV> + <noiseU>0</noiseU> + <noiseV>0</noiseV> + <blend_mode>NONE</blend_mode> + </technique> + </extra> + </texture> + </diffuse> + <specular> + <color>0.5 0.5 0.5 1</color> + </specular> + <shininess> + <float>20</float> + </shininess> + <reflective> + <color>0 0 0 1</color> + </reflective> + <reflectivity> + <float>0.5</float> + </reflectivity> + <transparent opaque="RGB_ZERO"> + <color>0 0 0 1</color> + </transparent> + <transparency> + <float>1</float> + </transparency> + </phong> + <extra> + <technique profile="FCOLLADA"/> + </extra> + </technique> + </profile_COMMON> + </effect> + </library_effects> + <library_geometries> + <geometry id="cubeShape" name="cubeShape"> + <mesh> + <source id="cubeShape-positions" name="position"> + <float_array id="cubeShape-positions-array" count="24">-0.5 -0.5 0.5 0.5 -0.5 0.5 -0.5 0.5 0.5 0.5 0.5 0.5 -0.5 0.5 -0.5 0.5 0.5 -0.5 -0.5 -0.5 -0.5 0.5 -0.5 -0.5</float_array> + <technique_common> + <accessor source="#cubeShape-positions-array" count="8" stride="3"> + <param name="X" type="float"/> + <param name="Y" type="float"/> + <param name="Z" type="float"/> + </accessor> + </technique_common> + </source> + <source id="cubeShape-normals" name="normal"> + <float_array id="cubeShape-normals-array" count="72">0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 1 0 0 1 0 0 1 0 0 1 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0</float_array> + <technique_common> + <accessor source="#cubeShape-normals-array" count="24" stride="3"> + <param name="X" type="float"/> + <param name="Y" type="float"/> + <param name="Z" type="float"/> + </accessor> + </technique_common> + </source> + <source id="cubeShape-map1" name="map1"> + <float_array id="cubeShape-map1-array" count="48">1 0 1 0 0 0 0 1 0 0 1 1 0 0 1 1 0 1 1 1 1 0 1 1 0 0 0 1 0 0 1 1 0 0 1 1 1 0 0 1 1 0 1 0 0 1 0 1</float_array> + <technique_common> + <accessor source="#cubeShape-map1-array" count="24" stride="2"> + <param name="S" type="float"/> + <param name="T" type="float"/> + </accessor> + </technique_common> + </source> + <vertices id="cubeShape-vertices"> + <input semantic="POSITION" source="#cubeShape-positions"/> + </vertices> + <polylist material="phong1SG" count="6"> + <input semantic="VERTEX" source="#cubeShape-vertices" offset="0"/> + <input semantic="NORMAL" source="#cubeShape-normals" offset="1"/> + <input semantic="TEXCOORD" source="#cubeShape-map1" offset="2" set="0"/> + <vcount>4 4 4 4 4 4</vcount> + <p>0 0 14 1 1 1 3 2 17 2 3 19 2 4 2 3 5 18 5 6 5 4 7 22 4 8 4 5 9 20 7 10 7 6 11 23 6 12 6 7 13 21 1 14 9 0 15 8 1 16 16 7 17 10 5 18 11 3 19 3 6 20 12 0 21 0 2 22 15 4 23 13</p> + </polylist> + </mesh> + <extra> + <technique profile="MAYA"> + <double_sided>1</double_sided> + <dynamic_attributes> + <miShadingSamplesOverride short_name="mso" type="bool">0</miShadingSamplesOverride> + <miShadingSamples short_name="msh" type="float">0</miShadingSamples> + <miMaxDisplaceOverride short_name="mdo" type="bool">0</miMaxDisplaceOverride> + <miMaxDisplace short_name="mmd" type="float">0</miMaxDisplace> + </dynamic_attributes> + </technique> + </extra> + </geometry> + </library_geometries> + <library_visual_scenes> + <visual_scene id="VisualSceneNode" name="crate"> + <node id="cube" name="cube" type="NODE"> + <rotate sid="rotateZ">0 0 1 0</rotate> + <rotate sid="rotateY">0 1 0 0</rotate> + <rotate sid="rotateX">1 0 0 0</rotate> + <instance_geometry url="#cubeShape"> + <bind_material> + <technique_common> + <instance_material symbol="phong1SG" target="#phong1"> + <bind_vertex_input semantic="TEX0" input_semantic="TEXCOORD" input_set="0"/> + </instance_material> + </technique_common> + </bind_material> + </instance_geometry> + </node> + <extra> + <technique profile="FCOLLADA"> + <start_time>0.041666</start_time> + <end_time>2</end_time> + </technique> + </extra> + </visual_scene> + </library_visual_scenes> + <scene> + <instance_physics_scene url="#MayaNativePhysicsScene"/> + <instance_visual_scene url="#VisualSceneNode"/> + </scene> +</COLLADA> diff --git a/o3d/import/test_data/crate.jpg b/o3d/import/test_data/crate.jpg Binary files differnew file mode 100644 index 0000000..19d3033 --- /dev/null +++ b/o3d/import/test_data/crate.jpg diff --git a/o3d/import/test_data/rock01.tga b/o3d/import/test_data/rock01.tga Binary files differnew file mode 100644 index 0000000..666741d --- /dev/null +++ b/o3d/import/test_data/rock01.tga diff --git a/o3d/import/test_data/rock02.tga b/o3d/import/test_data/rock02.tga Binary files differnew file mode 100644 index 0000000..481f657 --- /dev/null +++ b/o3d/import/test_data/rock02.tga diff --git a/o3d/import/win/collada_conditioner_win.cc b/o3d/import/win/collada_conditioner_win.cc new file mode 100644 index 0000000..e9d3ebb --- /dev/null +++ b/o3d/import/win/collada_conditioner_win.cc @@ -0,0 +1,154 @@ +/* + * 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 defines platform-specific implementations of certain +// methods of ColladaConditioner. + +#include "import/cross/precompile.h" + +#include <d3dx9effect.h> + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "core/cross/error.h" +#include "core/cross/service_locator.h" +#include "import/cross/collada.h" +#include "import/cross/collada_conditioner.h" + +namespace o3d { + +const int kTimeoutInSeconds = 30; + +bool ColladaConditioner::CompileHLSL(const String& shader_source, + const String& vs_entry, + const String& ps_entry) { + bool retval = false; + String shader_source_hlsl = shader_source; + shader_source_hlsl += + "technique t {\n" + " pass p {\n" + " VertexShader = compile vs_2_0 " + vs_entry + "();\n" + " PixelShader = compile ps_2_0 " + ps_entry + "();\n" + " }\n" + "};\n"; + + // Create an effect compiler from the FX file. + ID3DXEffectCompiler* compiler = NULL; + ID3DXBuffer* parse_errors = NULL; + HRESULT hr = ::D3DXCreateEffectCompiler(shader_source_hlsl.c_str(), + static_cast<UINT>( + shader_source_hlsl.size()), + NULL, + NULL, + 0, + &compiler, + &parse_errors); + + if (hr != D3D_OK) { + const char* error_str = reinterpret_cast<const char*>( + parse_errors->GetBufferPointer()); + O3D_ERROR(service_locator_) << error_str; + } else { + ID3DXBuffer* effect_buffer; + ID3DXBuffer* error_buffer; + hr = compiler->CompileEffect(0, &effect_buffer, &error_buffer); + if (hr != S_OK) { + const char* compile_errors = reinterpret_cast<const char*>( + error_buffer->GetBufferPointer()); + O3D_ERROR(service_locator_) << compile_errors; + } else { + retval = true; + if (effect_buffer) effect_buffer->Release(); + if (error_buffer) error_buffer->Release(); + } + if (compiler) compiler->Release(); + if (parse_errors) parse_errors->Release(); + } + return retval; +} + +// Find cgc executable, and run it on the input so that we can get a +// preprocessed version out. +bool ColladaConditioner::PreprocessShaderFile(const FilePath& in_filename, + const FilePath& out_filename) { + FilePath executable_path; + if (!PathService::Get(base::DIR_EXE, &executable_path)) { + O3D_ERROR(service_locator_) << "Couldn't get executable path."; + return false; + } + executable_path = executable_path.Append(FILE_PATH_LITERAL("cgc.exe")); + std::wstring cmd; + cmd = L"\"" + executable_path.value() + L"\" "; + cmd += L"-E \"" + in_filename.value() + L"\" "; + cmd += L"-o \"" + out_filename.value() + L"\""; + + // Have to dup the string because CreateProcessW might modify the + // contents. + wchar_t* buf = ::wcsdup(cmd.c_str()); + + // Initialize structure to zero with default array initializer. + PROCESS_INFORMATION process_info = {0}; + // Initialize structure to it's size, followed by zeros. + STARTUPINFO startup_info = {sizeof(STARTUPINFO)}; + + BOOL success = ::CreateProcessW(NULL, + buf, + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &startup_info, + &process_info); + + if (success) { + success = FALSE; + int exit_code = ::WaitForSingleObject(process_info.hProcess, + kTimeoutInSeconds * 1000); + if (exit_code == WAIT_OBJECT_0) { + success = TRUE; + } else if (exit_code == WAIT_TIMEOUT) { + O3D_ERROR(service_locator_) << "Timed out waiting for cg compiler!"; + } else { + O3D_ERROR(service_locator_) << "Couldn't start cg compiler (cgc.exe)."; + } + } else { + O3D_ERROR(service_locator_) << "Couldn't start cg compiler (cgc.exe)."; + } + ::CloseHandle(process_info.hProcess); + ::CloseHandle(process_info.hThread); + ::free(buf); + return success == TRUE; +} +} // end namespace o3d |