diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c7ff3c..7b2df97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: run: yarn prepare build-android: + if: false runs-on: ubuntu-latest env: TURBO_CACHE_DIR: .turbo/android @@ -113,6 +114,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + submodules: recursive - name: Setup uses: ./.github/actions/setup @@ -125,6 +128,18 @@ jobs: restore-keys: | ${{ runner.os }}-turborepo-ios- + - name: Build draco + run: | + yarn run build-draco + + - name: Cache draco + uses: actions/cache@v3 + with: + path: libs/** + key: ${{ runner.os }}-draco-build-${{ hashFiles('example/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-draco- + - name: Check turborepo cache for iOS run: | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") diff --git a/cpp/draco-helpers.h b/cpp/draco-helpers.h new file mode 100644 index 0000000..328e808 --- /dev/null +++ b/cpp/draco-helpers.h @@ -0,0 +1,99 @@ +#include "draco/core/decoder_buffer.h" +#pragma once + +#include + +#include "draco/attributes/attribute_transform_type.h" +#include "draco/attributes/point_attribute.h" +#include "draco/compression/config/compression_shared.h" +#include "draco/compression/decode.h" +#include "draco/core/decoder_buffer.h" +#include "draco/mesh/mesh.h" + +#define HOSTFN(name, size, capture) \ +jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name), size, \ +capture(jsi::Runtime &rt, const jsi::Value &thisValue, \ +const jsi::Value *arguments, size_t count) \ +->jsi::Value + +namespace facebook::react { +template +std::shared_ptr tryGetDracoObject(jsi::Runtime& rt, jsi::Object& obj) { + if (!obj.hasNativeState(rt)) { + return nullptr; + } + + return std::dynamic_pointer_cast(obj.getNativeState(rt)); +} + +template +bool GetTrianglesArray(const draco::Mesh &m, const int out_size, + T *out_values) { + const uint32_t num_faces = m.num_faces(); + if (num_faces * 3 * sizeof(T) != out_size) { + return false; + } + + for (uint32_t face_id = 0; face_id < num_faces; ++face_id) { + const draco::Mesh::Face &face = m.face(draco::FaceIndex(face_id)); + out_values[face_id * 3 + 0] = static_cast(face[0].value()); + out_values[face_id * 3 + 1] = static_cast(face[1].value()); + out_values[face_id * 3 + 2] = static_cast(face[2].value()); + } + return true; +} + +template +static bool GetAttributeDataArrayForAllPointsHelper(const draco::PointCloud &pc, + const draco::PointAttribute &pa, + const draco::DataType type, + int out_size, + void *out_values) { + const int components = pa.num_components(); + const int num_points = pc.num_points(); + const size_t data_size = num_points * components * sizeof(T); + if (data_size != out_size) { + return false; + } + const bool requested_type_matches = pa.data_type() == type; + if (requested_type_matches && pa.is_mapping_identity()) { + // Copy values directly to the output vector. + const uint8_t *ptr = pa.GetAddress(draco::AttributeValueIndex(0)); + ::memcpy(out_values, ptr, data_size); + return true; + } + + return false; +}; + +bool GetAttributeFloatArrayForAllPoints(const draco::PointCloud &pc, + const draco::PointAttribute &pa, + int out_size, + void *out_values) { + const int components = pa.num_components(); + const int num_points = pc.num_points(); + const int data_size = num_points * components * sizeof(float); + if (data_size != out_size) { + return false; + } + const bool requested_type_is_float = pa.data_type() == draco::DT_FLOAT32; + std::vector values(components, -2.f); + int entry_id = 0; + float *const floats = reinterpret_cast(out_values); + + for (draco::PointIndex i(0); i < num_points; ++i) { + const draco::AttributeValueIndex val_index = pa.mapped_index(i); + if (requested_type_is_float) { + pa.GetValue(val_index, &values[0]); + } else { + if (!pa.ConvertValue(val_index, &values[0])) { + return false; + } + } + for (int j = 0; j < components; ++j) { + floats[entry_id++] = values[j]; + } + } + return true; +} +} diff --git a/cpp/draco-state.h b/cpp/draco-state.h new file mode 100644 index 0000000..bc918f2 --- /dev/null +++ b/cpp/draco-state.h @@ -0,0 +1,57 @@ +#pragma once + +#include "draco/core/decoder_buffer.h" +#include + +#include "draco/attributes/point_attribute.h" +#include "draco/core/decoder_buffer.h" +#include "draco/mesh/mesh.h" + +namespace facebook::react { +class DracoDecoder: public jsi::NativeState { +public: + DracoDecoder(): decoder_(draco::Decoder()), last_status_(draco::Status()) {} + virtual ~DracoDecoder() {} + + draco::Decoder decoder_; + draco::Status last_status_; +}; + +class DracoDecoderBuffer : public jsi::NativeState { +public: + DracoDecoderBuffer() : buffer_(draco::DecoderBuffer()) {} + virtual ~DracoDecoderBuffer() {} + + draco::DecoderBuffer buffer_; +}; + +class DracoPointCloud: public jsi::NativeState { +public: + DracoPointCloud(): pointCloud_(draco::PointCloud()) {} + virtual ~DracoPointCloud() {} + + draco::PointCloud pointCloud_; +}; + +class DracoPointAttribute: public jsi::NativeState { +public: + DracoPointAttribute(): pointAttribute_(draco::PointAttribute()) {} + virtual ~DracoPointAttribute() {} + + explicit DracoPointAttribute(const draco::PointAttribute &attribute): pointAttribute_(draco::PointAttribute()) { + pointAttribute_.CopyFrom(attribute); + } + + explicit DracoPointAttribute(draco::PointAttribute &&attribute): pointAttribute_(std::move(attribute)) {} + + draco::PointAttribute pointAttribute_; +}; + +class DracoMesh: public jsi::NativeState { +public: + DracoMesh(): mesh_(draco::Mesh()) {} + virtual ~DracoMesh() {} + + draco::Mesh mesh_; +}; +} diff --git a/cpp/react-native-draco.cpp b/cpp/react-native-draco.cpp index d74171e..ee97f52 100644 --- a/cpp/react-native-draco.cpp +++ b/cpp/react-native-draco.cpp @@ -1,14 +1,335 @@ #include "react-native-draco.h" #include "draco/core/decoder_buffer.h" +#include "draco-state.h" +#include "draco-helpers.h" namespace facebook::react { ReactNativeDraco::ReactNativeDraco(std::shared_ptr jsInvoker) - : NativeDracoCxxSpec(std::move(jsInvoker)) {} +: NativeDracoCxxSpec(std::move(jsInvoker)) {} -double ReactNativeDraco::multiply(jsi::Runtime &rt, double a, double b) { - draco::DecoderBuffer buffer {}; - return a + b; -}; +jsi::Object ReactNativeDraco::createDecoderModule(jsi::Runtime &rt) { + jsi::Object dracoEncoder{rt}; + dracoEncoder.setNativeState(rt, std::make_shared()); + return dracoEncoder; +} + +jsi::Object ReactNativeDraco::createEncoderModule(jsi::Runtime &rt) { + return jsi::Object(rt); +} + +void ReactNativeDraco::attachPointCloudHandle(jsi::Runtime &rt, jsi::Object handle) { + handle.setNativeState(rt, std::make_shared()); +} + +void ReactNativeDraco::attachMeshHandle(jsi::Runtime &rt, jsi::Object handle) { + handle.setNativeState(rt, std::make_shared()); +} + +void ReactNativeDraco::attachPointAttributeHandle(jsi::Runtime &rt, jsi::Object handle) { + handle.setNativeState(rt, std::make_shared()); +} + +void ReactNativeDraco::installMeshMethods(jsi::Runtime &rt, jsi::Object handle) { + _installMeshMethods(rt, handle); +} + +void ReactNativeDraco::_installMeshMethods(jsi::Runtime &rt, jsi::Object& handle) { + auto num_faces = HOSTFN("num_faces", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto faces = mesh->mesh_.num_faces(); + return jsi::Value((int)faces); + }); + + auto num_attributes = HOSTFN("num_attributes", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto attributes = mesh->mesh_.num_attributes(); + return jsi::Value((int)attributes); + }); + + auto num_points = HOSTFN("num_points", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto points = mesh->mesh_.num_points(); + return jsi::Value((int)points); + }); + + handle.setProperty(rt, "num_faces", std::move(num_faces)); + handle.setProperty(rt, "num_points", std::move(num_points)); + handle.setProperty(rt, "num_attributes", std::move(num_attributes)); +} + +void ReactNativeDraco::installPointCloudMethods(jsi::Runtime &rt, jsi::Object handle) { + _installPointCloudMethods(rt, handle); +} + +void ReactNativeDraco::_installPointCloudMethods(jsi::Runtime &rt, jsi::Object& handle) { + auto num_points = HOSTFN("num_points", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto points = mesh->pointCloud_.num_points(); + return jsi::Value((int)points); + }); + + auto num_attributes = HOSTFN("num_attributes", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto attributes = mesh->pointCloud_.num_attributes(); + return jsi::Value((int)attributes); + }); + + handle.setProperty(rt, "num_points", std::move(num_points)); + handle.setProperty(rt, "num_attributes", std::move(num_attributes)); +} + +void ReactNativeDraco::installPointAttributeMethods(jsi::Runtime &rt, jsi::Object handle) { + _installPointAttributeMethods(rt, handle); +} + +void ReactNativeDraco::_installPointAttributeMethods(jsi::Runtime &rt, jsi::Object& handle) { + auto size = HOSTFN("size", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.size(); + return jsi::Value((int)value); + }); + + auto attribute_type = HOSTFN("attribute_type", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.attribute_type(); + return jsi::Value((int)value); + }); + + auto data_type = HOSTFN("data_type", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.data_type(); + return jsi::Value((int)value); + }); + + auto normalized = HOSTFN("normalized", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.normalized(); + return jsi::Value((bool)value); + }); + + auto byte_stride = HOSTFN("byte_stride", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.byte_stride(); + return jsi::Value((int)value); + }); + + auto byte_offset = HOSTFN("byte_offset", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.byte_offset(); + return jsi::Value((int)value); + }); + + auto unique_id = HOSTFN("unique_id", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.unique_id(); + return jsi::Value((int)value); + }); + + auto num_components = HOSTFN("num_components", 0, []) { + auto thisHandle = thisValue.getObject(rt); + auto mesh = tryGetDracoObject(rt, thisHandle); + auto value = mesh->pointAttribute_.num_components(); + return jsi::Value((int)value); + }); + + handle.setProperty(rt, "size", std::move(size)); + handle.setProperty(rt, "attribute_type", std::move(attribute_type)); + handle.setProperty(rt, "data_type", std::move(data_type)); + handle.setProperty(rt, "normalized", std::move(normalized)); + handle.setProperty(rt, "byte_stride", std::move(byte_stride)); + handle.setProperty(rt, "byte_offset", std::move(byte_offset)); + handle.setProperty(rt, "unique_id", std::move(unique_id)); + handle.setProperty(rt, "num_components", std::move(num_components)); +} + +void ReactNativeDraco::SkipAttributeTransform(jsi::Runtime &rt, jsi::Object decoderHandle, NativeDracoGeometryAttribute attributeType) { + auto decoder = tryGetDracoObject(rt, decoderHandle); + decoder->decoder_.SetSkipAttributeTransform(draco::GeometryAttribute::Type((int)attributeType)); +} + +void ReactNativeDraco::initBuffer(jsi::Runtime &rt, jsi::Object bufferHandle, jsi::Object data, int length) { + auto dracoBuffer = std::make_shared(); + + if (!data.isArrayBuffer(rt)) { + throw jsi::JSError(rt, "Data needs to be an array buffer"); + } + + auto arrayBuffer = data.getArrayBuffer(rt); + auto bufferData = arrayBuffer.data(rt); + + + if (bufferData == nullptr) { + throw jsi::JSError(rt, "Buffer data is null"); + } + + dracoBuffer->buffer_.Init((const char*)bufferData, length); + bufferHandle.setNativeState(rt, dracoBuffer); +} + +jsi::Object ReactNativeDraco::GetAttributeByUniqueId(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, int uniqueId) { + auto pointCloud = tryGetDracoObject(rt, pointCloudHandle); + auto mesh = tryGetDracoObject(rt, pointCloudHandle); + + const draco::PointAttribute *pointAttr = nullptr; + + try { + auto &pc = (pointCloud != nullptr) ? pointCloud->pointCloud_ : mesh->mesh_; + pointAttr = pc.GetAttributeByUniqueId(uniqueId); + } catch (std::exception& e) { + throw jsi::JSError(rt, "Operation failed"); + } + + if (pointAttr == nullptr) { + throw jsi::JSError(rt, "Failed to get attribute by unique id"); + } + + auto object = jsi::Object{rt}; + object.setNativeState(rt, std::make_shared(*pointAttr)); + _installPointAttributeMethods(rt, object); + + return object; +} + +jsi::Object ReactNativeDraco::GetAttribute(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, int attributeId) { + + auto pointCloud = tryGetDracoObject(rt, pointCloudHandle); + auto mesh = tryGetDracoObject(rt, pointCloudHandle); + + const draco::PointAttribute *pointAttr = nullptr; + + try { + auto &pc = (pointCloud != nullptr) ? pointCloud->pointCloud_ : mesh->mesh_; + pointAttr = pc.attribute(attributeId); + } catch (std::exception& e) { + throw jsi::JSError(rt, "Operation failed"); + } + + if (pointAttr == nullptr) { + throw jsi::JSError(rt, "Failed to get attribute by unique id"); + } + + auto object = jsi::Object{rt}; + object.setNativeState(rt, std::make_shared(*pointAttr)); + _installPointAttributeMethods(rt, object); + + return object; +} + +int ReactNativeDraco::GetAttributeId(jsi::Runtime &rt, jsi::Object pointCloudHandle, NativeDracoGeometryAttribute attributeType) { + auto pointCloud = tryGetDracoObject(rt, pointCloudHandle); + auto mesh = tryGetDracoObject(rt, pointCloudHandle); + auto geometryAttr = draco::GeometryAttribute::Type((int)attributeType); + + auto &object = (pointCloud != nullptr) ? pointCloud->pointCloud_ : mesh->mesh_; + + return object.GetNamedAttributeId(geometryAttr); +} + +bool ReactNativeDraco::GetTrianglesUInt32Array(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object meshHandle, int outSize, jsi::Object outValues) { + auto mesh = tryGetDracoObject(rt, meshHandle); + + if (!outValues.isArrayBuffer(rt)) { + throw jsi::JSError(rt, "Data needs to be an array buffer"); + } + + auto arrayBuffer = outValues.getArrayBuffer(rt); + auto bufferData = arrayBuffer.data(rt); + + return GetTrianglesArray(mesh->mesh_, outSize, + reinterpret_cast(bufferData)); + return false; +} + +bool ReactNativeDraco::GetAttributeDataArrayForAllPoints(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, jsi::Object pointAttributeHandle, NativeDracoDataType dataType, int outSize, jsi::Object outValues) { + auto pc = tryGetDracoObject(rt, pointCloudHandle); + auto mesh = tryGetDracoObject(rt, pointCloudHandle); + auto pa = tryGetDracoObject(rt, pointAttributeHandle); + + if (!outValues.isArrayBuffer(rt)) { + throw jsi::JSError(rt, "Data needs to be an array buffer"); + } + + auto arrayBuffer = outValues.getArrayBuffer(rt); + auto bufferData = arrayBuffer.data(rt); + + try { + auto &object = (pc != nullptr) ? pc->pointCloud_ : mesh->mesh_; + switch (dataType) { + case NativeDracoDataType::DT_INT8: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_INT16: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_INT32: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_UINT8: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_UINT16: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_UINT32: + return GetAttributeDataArrayForAllPointsHelper(object, pa->pointAttribute_, draco::DT_INT8, + outSize, bufferData); + case NativeDracoDataType::DT_FLOAT32: + return GetAttributeFloatArrayForAllPoints(object, pa->pointAttribute_, outSize, bufferData); + default: + return false; + } + } catch (std::exception e) { + throw jsi::JSError(rt, "Operation failed"); + } + + return false; +} + +NativeDracoStatus ReactNativeDraco::DecodeBufferToPointCloud(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object bufferHandle, jsi::Object pointCloudHandle) { + auto decoder = tryGetDracoObject(rt, decoderHandle); + auto buffer = tryGetDracoObject(rt, bufferHandle); + auto pointCloud = tryGetDracoObject(rt, pointCloudHandle); + + auto status = decoder->decoder_.DecodeBufferToGeometry(&buffer->buffer_, &pointCloud->pointCloud_); + decoder->last_status_ = draco::Status(std::move(status)); + + return Bridging::fromJs(rt, jsi::Value(status.code())); +} + +NativeDracoStatus ReactNativeDraco::DecodeBufferToMesh(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object bufferHandle, jsi::Object meshHandle) { + auto decoder = tryGetDracoObject(rt, decoderHandle); + auto buffer = tryGetDracoObject(rt, bufferHandle); + auto mesh = tryGetDracoObject(rt, meshHandle); + + auto status = decoder->decoder_.DecodeBufferToGeometry(&buffer->buffer_, &mesh->mesh_); + decoder->last_status_ = draco::Status(std::move(status)); + + return Bridging::fromJs(rt, jsi::Value(status.code())); +} + + +NativeDracoEncodedGeometryType ReactNativeDraco::GetEncodedGeometryType_Deprecated(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object inBuffer) { + auto decoderBuffer = tryGetDracoObject(rt, inBuffer); + + if (decoderBuffer == nullptr) { + return NativeDracoEncodedGeometryType::INVALID_GEOMETRY_TYPE; + } + + auto value = draco::Decoder::GetEncodedGeometryType(&decoderBuffer->buffer_).value(); + return Bridging::fromJs(rt, jsi::Value(value)); +} } // namespace facebook::react diff --git a/cpp/react-native-draco.h b/cpp/react-native-draco.h index 8d98c1e..0150706 100644 --- a/cpp/react-native-draco.h +++ b/cpp/react-native-draco.h @@ -1,13 +1,46 @@ #pragma once #include +#include + +#include "draco/attributes/attribute_transform_type.h" +#include "draco/attributes/point_attribute.h" +#include "draco/compression/config/compression_shared.h" +#include "draco/compression/decode.h" +#include "draco/core/decoder_buffer.h" +#include "draco/mesh/mesh.h" namespace facebook::react { + class ReactNativeDraco: public NativeDracoCxxSpec { public: ReactNativeDraco(std::shared_ptr jsInvoker); - - double multiply(jsi::Runtime &rt, double a, double b); + + NativeDracoEncodedGeometryType GetEncodedGeometryType_Deprecated(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object inBuffer); + + jsi::Object createDecoderModule(jsi::Runtime &rt); + jsi::Object createEncoderModule(jsi::Runtime &rt); + + void installMeshMethods(jsi::Runtime &rt, jsi::Object handle); + void installPointCloudMethods(jsi::Runtime &rt, jsi::Object handle); + void installPointAttributeMethods(jsi::Runtime &rt, jsi::Object handle); + void attachPointCloudHandle(jsi::Runtime &rt, jsi::Object handle); + void attachPointAttributeHandle(jsi::Runtime &rt, jsi::Object handle); + void attachMeshHandle(jsi::Runtime &rt, jsi::Object handle); + bool GetAttributeDataArrayForAllPoints(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, jsi::Object pointAttributeHandle, NativeDracoDataType dataType, int outSize, jsi::Object outValues); + void SkipAttributeTransform(jsi::Runtime &rt, jsi::Object decoderHandle, NativeDracoGeometryAttribute attributeType); + jsi::Object GetAttributeByUniqueId(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, int uniqueId); + jsi::Object GetAttribute(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object pointCloudHandle, int attributeId); + bool GetTrianglesUInt32Array(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object meshHandle, int outSize, jsi::Object outValues); + NativeDracoStatus DecodeBufferToPointCloud(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object bufferHandle, jsi::Object pointCloudHandle); + NativeDracoStatus DecodeBufferToMesh(jsi::Runtime &rt, jsi::Object decoderHandle, jsi::Object bufferHandle, jsi::Object meshHandle); + int GetAttributeId(jsi::Runtime &rt, jsi::Object pointCloudHandle, NativeDracoGeometryAttribute attributeType); + void initBuffer(jsi::Runtime &rt, jsi::Object bufferHandle, jsi::Object data, int length); + +private: + void _installMeshMethods(jsi::Runtime &rt, jsi::Object& handle); + void _installPointCloudMethods(jsi::Runtime &rt, jsi::Object& handle); + void _installPointAttributeMethods(jsi::Runtime &rt, jsi::Object& handle); }; } diff --git a/example/ios/Podfile b/example/ios/Podfile index 9a2b69d..6b52788 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -14,4 +14,8 @@ options = { :hermes_enabled => true, } -use_test_app! options +use_test_app! options do |target| + target.app do + pod 'Resources', :path => './Resources' + end +end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d7b5f67..3fd40a1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1242,6 +1242,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-blob-util (0.19.11): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-draco (0.1.0): - DoubleConversion - glog @@ -1557,6 +1578,7 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) + - Resources (0.0.1-dev) - SocketRocket (0.7.1) - Yoga (0.0.0) @@ -1598,6 +1620,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-blob-util (from `../node_modules/react-native-blob-util`) - react-native-draco (from `../..`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -1629,6 +1652,7 @@ DEPENDENCIES: - "ReactNativeHost (from `../node_modules/@rnx-kit/react-native-host`)" - ReactTestApp-DevSupport (from `../node_modules/react-native-test-app`) - ReactTestApp-Resources (from `..`) + - Resources (from `./Resources`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1707,6 +1731,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-blob-util: + :path: "../node_modules/react-native-blob-util" react-native-draco: :path: "../.." React-nativeconfig: @@ -1769,6 +1795,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-test-app" ReactTestApp-Resources: :path: ".." + Resources: + :path: "./Resources" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -1808,6 +1836,7 @@ SPEC CHECKSUMS: React-logger: d1a89c7d9b3c847eb63eb85724c54b06cae2c939 React-Mapbuffer: b0b4ace5b62b269f3838df26ba2d8b4f39f90783 React-microtasksnativemodule: 0b7db04c18f6bb01ef5b1f9007c3229abecc35dd + react-native-blob-util: e6a3b23a99ac2c3d9fa48722db049a1e1808efc2 react-native-draco: a5b96516aa5730b6500d31f5a56de03aa58cc973 React-nativeconfig: 72c10ff34863148ef90df9c9c8eacba99d2faaaa React-NativeModulesApple: 5ec49182fa000b2215ee1bed03e2867f8323ccf5 @@ -1839,9 +1868,10 @@ SPEC CHECKSUMS: ReactNativeHost: a27bb5af1c4d73dd3e80cc7ce295407f414e0e8c ReactTestApp-DevSupport: 1e39530b4a8602e7138a5b9dfafe41d3a78382ff ReactTestApp-Resources: ef64fb77d09031eba0ada7240f39d4579fa02361 + Resources: 1be6baca2388aacddcc0328ea6ee3de074eef20d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: f8ec45ce98bba1bc93dd28f2ee37215180e6d2b6 -PODFILE CHECKSUM: ca74fa79f43a749ca0221609a315db5cd87b54fc +PODFILE CHECKSUM: 88db05552cc61eb79b2fccb6f3e09940db6299a2 COCOAPODS: 1.15.2 diff --git a/example/ios/Resources/bunny.drc b/example/ios/Resources/bunny.drc new file mode 100644 index 0000000..1e80a75 Binary files /dev/null and b/example/ios/Resources/bunny.drc differ diff --git a/example/ios/Resources/resources.podspec b/example/ios/Resources/resources.podspec new file mode 100644 index 0000000..812b239 --- /dev/null +++ b/example/ios/Resources/resources.podspec @@ -0,0 +1,10 @@ +Pod::Spec.new do |s| + s.name = 'Resources' + s.version = '0.0.1-dev' + s.author = { 'Oskar Kwaƛniewski' => 'oskarkwasniewski@icloud.com' } + s.license = 'MIT' + s.homepage = 'https://github.com/callstack/react-native-draco' + s.source = { :git => 'https://github.com/callstack/react-native-draco' } + s.summary = 'Resources for AwesomeApp' + s.resources = '*.{drc}' +end diff --git a/example/package.json b/example/package.json index f736247..1a1d92a 100644 --- a/example/package.json +++ b/example/package.json @@ -8,11 +8,13 @@ "build:ios": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist && react-native build-ios --scheme DracoExample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"", "ios": "react-native run-ios", "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"", - "start": "react-native start" + "start": "react-native start", + "postinstall": "npx patch-package" }, "dependencies": { "react": "18.3.1", - "react-native": "0.76.0" + "react-native": "0.76.0", + "react-native-blob-util": "^0.19.11" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/example/patches/@react-native+codegen+0.76.0.patch b/example/patches/@react-native+codegen+0.76.0.patch new file mode 100644 index 0000000..d3734d9 --- /dev/null +++ b/example/patches/@react-native+codegen+0.76.0.patch @@ -0,0 +1,91 @@ +diff --git a/node_modules/@react-native/codegen/lib/generators/modules/GenerateModuleH.js b/node_modules/@react-native/codegen/lib/generators/modules/GenerateModuleH.js +index 13232a1..fe52764 100644 +--- a/node_modules/@react-native/codegen/lib/generators/modules/GenerateModuleH.js ++++ b/node_modules/@react-native/codegen/lib/generators/modules/GenerateModuleH.js +@@ -424,7 +424,9 @@ function generateEnum(hasteModuleName, origEnumName, members, memberType) { + const nativeEnumMemberType = + memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t'; + const getMemberValueAppearance = value => +- memberType === 'StringTypeAnnotation' ? `"${value}"` : `${value}`; ++ { ++ return memberType === 'StringTypeAnnotation' ? `"${value}"` : `${value}`; ++ }; + const fromCases = + members + .map( +@@ -458,6 +460,7 @@ function generateEnum(hasteModuleName, origEnumName, members, memberType) { + function createEnums(hasteModuleName, enumMap, resolveAlias) { + return Object.entries(enumMap) + .map(([enumName, enumNode]) => { ++ console.log(enumMap) + return generateEnum( + hasteModuleName, + enumName, +diff --git a/node_modules/@react-native/codegen/lib/parsers/typescript/parser.js b/node_modules/@react-native/codegen/lib/parsers/typescript/parser.js +index da4afe3..69bbcb1 100644 +--- a/node_modules/@react-native/codegen/lib/parsers/typescript/parser.js ++++ b/node_modules/@react-native/codegen/lib/parsers/typescript/parser.js +@@ -174,12 +174,22 @@ class TypeScriptParser { + _typeAnnotation$membe === void 0 + ? void 0 + : _typeAnnotation$membe.initializer; +- const enumMembersType = ++ let enumMembersType = + !enumInitializer || enumInitializer.type === 'StringLiteral' + ? 'StringTypeAnnotation' + : enumInitializer.type === 'NumericLiteral' + ? 'NumberTypeAnnotation' + : null; ++ ++ // Handle case where enum is negative numbers ++ ++ if (enumMembersType === null) { ++ const isNegative = enumInitializer.type = 'UnaryExpression' && enumInitializer.operator === '-'; ++ if (isNegative) { ++ enumMembersType = 'NumberTypeAnnotation'; ++ } ++ } ++ + if (!enumMembersType) { + throw new Error( + 'Enum values must be either blank, number, or string values.', +@@ -194,9 +204,10 @@ class TypeScriptParser { + const enumInitializerType = + enumMembersType === 'StringTypeAnnotation' + ? 'StringLiteral' +- : enumMembersType === 'NumberTypeAnnotation' ++ : (enumMembersType === 'NumberTypeAnnotation' || enumMembersType === 'UnaryExpression') + ? 'NumericLiteral' + : null; ++ + typeAnnotation.members.forEach(member => { + var _member$initializer$t, _member$initializer; + if ( +@@ -209,6 +220,11 @@ class TypeScriptParser { + ? _member$initializer$t + : 'StringLiteral') !== enumInitializerType + ) { ++ if (member.initializer?.argument.type === 'NumericLiteral') { ++ return; ++ } ++ ++ console.log(member.initializer); + throw new Error( + 'Enum values can not be mixed. They all must be either blank, number, or string values.', + ); +@@ -218,6 +234,15 @@ class TypeScriptParser { + parseEnumMembers(typeAnnotation) { + return typeAnnotation.members.map(member => { + var _member$initializer$v, _member$initializer2; ++ console.log("PARSE ENUM MEMBERS", member.id.name, member.initializer.type, member.initializer.value); ++ if (member.initializer.operator == '-') { ++ console.log('NEGATIVE NUMBER'); ++ console.log(member.id.name, -member.initializer.argument.value); ++ return { ++ name: member.id.name, ++ value: -member.initializer.argument.value ++ } ++ } + return { + name: member.id.name, + value: diff --git a/example/src/App.tsx b/example/src/App.tsx index 6e6840e..f9a040b 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,11 +1,168 @@ -import { StyleSheet, View, Text } from 'react-native'; -import { multiply } from 'react-native-draco'; +import { StyleSheet, View, Text, Button } from 'react-native'; +import { + DracoDecoderModule, + EncodedGeometryType, + GeometryAttribute, + Status, +} from 'react-native-draco'; +import RNFetchBlob from 'react-native-blob-util'; + +const defaultAttributeIDs = { + position: 'POSITION', + normal: 'NORMAL', + color: 'COLOR', + uv: 'TEX_COORD', +}; + +const defaultAttributeTypes = { + position: 'Float32Array', + normal: 'Float32Array', + color: 'Float32Array', + uv: 'Float32Array', +}; + +const readFile = async (path: string): Promise => { + const filePath = RNFetchBlob.fs.asset(path); + const data = await RNFetchBlob.fs.readFile(filePath, 'base64'); + const byteCharacters = atob(data); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + return new Uint8Array(byteNumbers); +}; export default function App() { - const result = multiply(5, 2); + const decode = async () => { + const byteArray = await readFile('bunny.drc'); + console.log('Draco file loaded!'); + // Create the Draco decoder. + const decoderModule = DracoDecoderModule(); + const buffer = new decoderModule.DecoderBuffer(); + buffer.Init(byteArray, byteArray.length); + + // Create a buffer to hold the encoded data. + const decoder = new decoderModule.Decoder(); + const geometryType = decoder.GetEncodedGeometryType(buffer); + console.log({ geometryType: EncodedGeometryType[geometryType] }); + + const t0 = performance.now(); + // Decode the encoded geometry. + let outputGeometry; + let status; + if (geometryType === decoderModule.TRIANGULAR_MESH) { + outputGeometry = new decoderModule.Mesh(); + status = decoder.DecodeBufferToMesh(buffer, outputGeometry); + } else { + outputGeometry = new decoderModule.PointCloud(); + status = decoder.DecodeBufferToPointCloud(buffer, outputGeometry); + } + + const t1 = performance.now(); + console.log('Draco Decoder finished decoding!'); + + console.log(`Decoding took ${(t1 - t0) / 1000} seconds.`); + + console.log({ status: Status[status], outputGeometry }); + + if ('num_faces' in outputGeometry) { + const num_faces = outputGeometry.num_faces(); + console.log({ num_faces }); + } + const num_points = outputGeometry.num_points(); + + console.log({ num_points }); + + const useUniqueIDs = true; + + // const geometry = { index: null, attributes: [] }; + + for (const attributeName in defaultAttributeIDs) { + // @ts-ignore + const attributeType = defaultAttributeTypes[attributeName]; + + console.log({ attributeName, attributeType }); + + let attribute; + let attributeID; + + if (useUniqueIDs) { + // @ts-ignore + attributeID = defaultAttributeIDs[attributeName]; + attribute = decoder.GetAttributeByUniqueId(outputGeometry, attributeID); + } else { + attributeID = decoder.GetAttributeId( + outputGeometry, + // @ts-ignore + GeometryAttribute[defaultAttributeIDs[attributeName]] + ); + console.log('GetAttributeId', GeometryAttribute[attributeID]); + + if (attributeID === -1) continue; + + attribute = decoder.GetAttribute(outputGeometry, attributeID); + } + + console.log('Received attribute', { + attribute_unique_id: attribute.unique_id(), + attribute_size: attribute.size(), + attribute_type: attribute.attribute_type(), + attribute_data_type: attribute.data_type(), + attribute_num_components: attribute.num_components(), + attribute_normalized: attribute.normalized(), + attribute_byte_stride: attribute.byte_stride(), + attribute_byte_offset: attribute.byte_offset(), + }); + + const numComponents = attribute.num_components(); + const numValues = num_points * numComponents; + const byteLength = numValues * 4; + const dataType = attribute.data_type(); + + const outputArray = new Float32Array(numValues); + console.log('GetAttributeDataArrayForAllPoints before:'); + console.log(outputArray.slice(0, 100)); + + // NOTE: This API is different from Draco.js api as it relied on wasm memory. Has to be adjusted in IR-Engine. + decoder.GetAttributeDataArrayForAllPoints( + outputGeometry, + attribute, + dataType, + byteLength, + outputArray + ); + console.log('GetAttributeDataArrayForAllPoints after:'); + console.log(outputArray.slice(0, 100)); + } + + if ( + 'num_faces' in outputGeometry && + geometryType === decoderModule.TRIANGULAR_MESH + ) { + // Generate mesh faces. + var numFaces = outputGeometry.num_faces(); + var numIndices = numFaces * 3; + var dataSize = numIndices * 4; + console.log('GetTrianglesUInt32Array before:'); + // NOTE: This API is different from Draco.js api as it relied on wasm memory. Has to be adjusted in IR-Engine. + const outputArray = new Uint32Array(dataSize); + console.log(outputArray.slice(0, 100)); + decoder.GetTrianglesUInt32Array(outputGeometry, dataSize, outputArray); + console.log('GetTrianglesUInt32Array after:'); + console.log(outputArray.slice(0, 100)); + } + + // You must explicitly delete objects created from the DracoDecoderModule + // or Decoder. + decoderModule.destroy(outputGeometry); + decoderModule.destroy(decoder); + decoderModule.destroy(buffer); + }; + return ( - Result: {result} + Draco Example +