summaryrefslogtreecommitdiffstats
path: root/o3d/import
diff options
context:
space:
mode:
Diffstat (limited to 'o3d/import')
-rw-r--r--o3d/import/build.scons144
-rw-r--r--o3d/import/cross/archive_processor.cc137
-rw-r--r--o3d/import/cross/archive_processor.h107
-rw-r--r--o3d/import/cross/archive_request.cc231
-rw-r--r--o3d/import/cross/archive_request.h203
-rw-r--r--o3d/import/cross/collada.cc2713
-rw-r--r--o3d/import/cross/collada.h438
-rw-r--r--o3d/import/cross/collada_conditioner.cc578
-rw-r--r--o3d/import/cross/collada_conditioner.h136
-rw-r--r--o3d/import/cross/collada_conditioner_stub.cc90
-rw-r--r--o3d/import/cross/collada_zip_archive.cc80
-rw-r--r--o3d/import/cross/collada_zip_archive.h73
-rw-r--r--o3d/import/cross/file_output_stream_processor.cc52
-rw-r--r--o3d/import/cross/file_output_stream_processor.h55
-rw-r--r--o3d/import/cross/gz_compressor.cc149
-rw-r--r--o3d/import/cross/gz_compressor.h72
-rw-r--r--o3d/import/cross/gz_compressor_test.cc161
-rw-r--r--o3d/import/cross/gz_decompressor.cc121
-rw-r--r--o3d/import/cross/gz_decompressor.h65
-rw-r--r--o3d/import/cross/gz_decompressor_test.cc142
-rw-r--r--o3d/import/cross/iarchive_generator.h62
-rw-r--r--o3d/import/cross/memory_buffer.h78
-rw-r--r--o3d/import/cross/memory_buffer_test.cc98
-rw-r--r--o3d/import/cross/memory_stream.cc401
-rw-r--r--o3d/import/cross/memory_stream.h276
-rw-r--r--o3d/import/cross/memory_stream_test.cc320
-rw-r--r--o3d/import/cross/precompile.cc33
-rw-r--r--o3d/import/cross/precompile.h99
-rw-r--r--o3d/import/cross/raw_data.cc343
-rw-r--r--o3d/import/cross/raw_data.h119
-rw-r--r--o3d/import/cross/raw_data_test.cc196
-rw-r--r--o3d/import/cross/tar_generator.cc195
-rw-r--r--o3d/import/cross/tar_generator.h125
-rw-r--r--o3d/import/cross/tar_generator_test.cc386
-rw-r--r--o3d/import/cross/tar_processor.cc144
-rw-r--r--o3d/import/cross/tar_processor.h91
-rw-r--r--o3d/import/cross/tar_processor_test.cc151
-rw-r--r--o3d/import/cross/targz_generator.cc72
-rw-r--r--o3d/import/cross/targz_generator.h91
-rw-r--r--o3d/import/cross/targz_generator_test.cc166
-rw-r--r--o3d/import/cross/targz_processor.cc66
-rw-r--r--o3d/import/cross/targz_processor.h66
-rw-r--r--o3d/import/cross/targz_processor_test.cc142
-rw-r--r--o3d/import/cross/zip_archive.cc876
-rw-r--r--o3d/import/cross/zip_archive.h216
-rw-r--r--o3d/import/fcollada.scons267
-rw-r--r--o3d/import/linux/collada_conditioner_linux.cc120
-rw-r--r--o3d/import/mac/collada_conditioner_mac.mm105
-rw-r--r--o3d/import/test_data/crate.dae239
-rw-r--r--o3d/import/test_data/crate.jpgbin0 -> 15135 bytes
-rw-r--r--o3d/import/test_data/rock01.tgabin0 -> 262188 bytes
-rw-r--r--o3d/import/test_data/rock02.tgabin0 -> 196652 bytes
-rw-r--r--o3d/import/win/collada_conditioner_win.cc154
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 &param_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 &param_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
new file mode 100644
index 0000000..19d3033
--- /dev/null
+++ b/o3d/import/test_data/crate.jpg
Binary files differ
diff --git a/o3d/import/test_data/rock01.tga b/o3d/import/test_data/rock01.tga
new file mode 100644
index 0000000..666741d
--- /dev/null
+++ b/o3d/import/test_data/rock01.tga
Binary files differ
diff --git a/o3d/import/test_data/rock02.tga b/o3d/import/test_data/rock02.tga
new file mode 100644
index 0000000..481f657
--- /dev/null
+++ b/o3d/import/test_data/rock02.tga
Binary files differ
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