summaryrefslogtreecommitdiffstats
path: root/o3d/import
diff options
context:
space:
mode:
authorgspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-27 23:15:42 +0000
committergspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-27 23:15:42 +0000
commit05b47f7a8c5451f858dc220df0e3a97542edace6 (patch)
treea2273d619f0625c9d44d40842845ccce2eac1045 /o3d/import
parent5cdc8bdb4c847cefe7f4542bd10c9880c2c557a0 (diff)
downloadchromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.zip
chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.gz
chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.bz2
This is the O3D source tree's initial commit to the Chromium tree. It
is not built or referenced at all by the chrome build yet, and doesn't yet build in it's new home. We'll change that shortly. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17035 0039d316-1c4b-4281-b951-d872f2087c98
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