diff --git a/README.md b/README.md index 21a377e..8b9b3bb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![license](https://img.shields.io/github/license/rastapasta/react-native-gl-model-view.svg) A `` component for [react-native](https://github.com/facebook/react-native), allowing you to -display and animate any Wavefront .OBJ 3D object. Realized with a native bridge to [GLView](https://github.com/nicklockwood/GLView). +display and animate any Wavefront .OBJ 3D object. Realized with a native bridge to [GLView](https://github.com/nicklockwood/GLView) for iOS and a native bridge to [jPCT-AE](http://www.jpct.net/jpct-ae/) for Android. @@ -12,23 +12,26 @@ Main features: * Display, rotate, scale and translate textured 3D models! * Animate with blasting fast 60 fps by using the [Animated API](https://facebook.github.io/react-native/docs/animations.html#using-the-native-driver) native driver -* Supports [Wavefront .OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) and GLEssentials model formats +* Supports [Wavefront .OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) and GLEssentials model formats (iOS) +* Supports [Wavefront .OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file), [Autodesk 3DS](https://en.wikipedia.org/wiki/.3ds), [Quake 2 MD2](https://en.wikipedia.org/wiki/MD2_(file_format)), [ASC](https://codeyarns.com/2011/08/17/asc-file-format-for-3d-points/) and GLEssentials model formats (Android) * Supports all texture image formats supported by [UIImage](https://developer.apple.com/library/content/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/LoadingImages/LoadingImages.html#//apple_ref/doc/uid/TP40010156-CH17-SW8) ## Requirements -* iOS - feel free to PR an Android port ;) -* Cocoapods - to install the [GLView](https://github.com/nicklockwood/GLView) dependency. +* Cocoapods (for iOS) - to install the [GLView](https://github.com/nicklockwood/GLView) dependency. ## Getting started You can install and try linking the project automatically: -`$ react-native add react-native-gl-model-view` +```sh +$ npm install --save react-native-gl-model-view +$ react-native link +``` or do it manually as described below: -### Manual installation +### iOS Manual installation `$ npm install --save react-native-gl-model-view` @@ -39,8 +42,39 @@ pod 'React', :path => '../node_modules/react-native' pod 'RNGLModelView', :path => '../node_modules/react-native-gl-model-view' ``` +### Android Manual installation + +`$ npm install --save react-native-gl-model-view` + +Afterwards, add the following lines to the **android/settings.gradle** file: + +```gradle +include ':react-native-gl-model-view' +project(':react-native-gl-model-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gl-model-view/android/app') +``` + +Finally, add the react-native-gl-model-view project as a dependency of the **android/app/build.gradle** file: + +```gradle +dependencies { + compile project(':react-native-gl-model-view') + ... +} +``` + + ## Usage +### Model and texture loading + +#### iOS + +To load a model on iOS, you need to put the file at the root of your Xcode project via the editor. + +#### Android + +To load a model on Android, you need to place the model in the **android/app/src/main/assets** folder. Create a new folder if it doesn't exist yet. + ### Static ```javascript @@ -132,6 +166,8 @@ Check out the [example project](https://github.com/rastapasta/react-native-gl-mo To build it, switch into the `example` folder and set it up as following: +#### For iOS + ```sh $ npm install $ cd ios @@ -140,10 +176,18 @@ $ cd .. $ react-native run-ios ``` +#### For Android + +```sh +$ npm install +$ react-native link +$ react-native run-android +``` + ## Backlog * Bridge to [GLModel.modelWithData](https://github.com/nicklockwood/GLView/blob/master/GLView/Models/GLModel.m#L424) to allow flexbile model sources -* Android bridge via [jPCT 3D engine](http://www.jpct.net/jpct-ae/) +* Update to the latest react-native version to fix the Android gesture responder bug ## Special thanks diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..0e8cb11 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.1' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + lintOptions { + abortOnError false + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.facebook.react:react-native:+' + compile fileTree(dir: "libs", include: ["*.jar"]) +} diff --git a/android/libs/jpct_ae.jar b/android/libs/jpct_ae.jar new file mode 100644 index 0000000..732f1ef Binary files /dev/null and b/android/libs/jpct_ae.jar differ diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..74b203c --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/android/src/main/java/com/rnglmodelview/MainActivity.java b/android/src/main/java/com/rnglmodelview/MainActivity.java new file mode 100644 index 0000000..66b933c --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/MainActivity.java @@ -0,0 +1,15 @@ +package com.rnglmodelview; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "RNGLModelView"; + } +} diff --git a/android/src/main/java/com/rnglmodelview/MainApplication.java b/android/src/main/java/com/rnglmodelview/MainApplication.java new file mode 100644 index 0000000..67cdafb --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/MainApplication.java @@ -0,0 +1,46 @@ +package com.rnglmodelview; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new RNGLModelViewPackage() + ); + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } +} diff --git a/android/src/main/java/com/rnglmodelview/RNGLModelView.java b/android/src/main/java/com/rnglmodelview/RNGLModelView.java new file mode 100644 index 0000000..12e003c --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/RNGLModelView.java @@ -0,0 +1,184 @@ +package com.rnglmodelview; + +import android.content.Context; +import android.opengl.GLSurfaceView; + +import com.rnglmodelview.exceptions.ModelObjectNotSupportedException; +import com.threed.jpct.Loader; +import com.threed.jpct.Matrix; +import com.threed.jpct.Object3D; +import com.threed.jpct.Texture; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.Nullable; + +public class RNGLModelView extends GLSurfaceView { + + private RNGLModelViewRenderer mRenderer; + + private Object3D mModel; + + private float mModelRotateX = 0; + private float mModelRotateY = 0; + private float mModelRotateZ = 0; + private float mModelTranslateX = 0; + private float mModelTranslateY = 0; + private float mModelTranslateZ = 0; + private float mModelScaleX = 1; + private float mModelScaleY = 1; + private float mModelScaleZ = 1; + + public RNGLModelView(Context context) { + super(context); + setEGLContextClientVersion(2); + + mRenderer = new RNGLModelViewRenderer(context); + setRenderer(mRenderer); + } + + public void setModel(String modelFileName) { + mModel = loadModel(modelFileName); + + // In jpct, the coordinate system is rotated 180 degrees around x and Object3D forces the mesh + // into that orientation. Since we want to keep the OpenGL-like coordinate system, we force the + // mesh back into its previous rotation. + mModel.rotateX((float)Math.PI); + mModel.rotateMesh(); + mModel.clearRotation(); + + mRenderer.setModel(mModel); + updateModelTransform(); + } + + public void setModelTexture(@Nullable String textureFileName) { + mRenderer.setTexture(loadTexture(textureFileName)); + } + + public void setAnimate(@Nullable boolean animate) { + mRenderer.setAnimate(animate); + } + + public void setModelRotateX(@Nullable float rotateX) { + mModelRotateX = rotateX; + updateModelTransform(); + } + + public void setModelRotateY(@Nullable float rotateY) { + mModelRotateY = rotateY; + updateModelTransform(); + } + + public void setModelRotateZ(@Nullable float rotateZ) { + mModelRotateZ = rotateZ; + updateModelTransform(); + } + + public void setModelScale(@Nullable float scale) { + mModelScaleX = scale; + mModelScaleY = scale; + mModelScaleZ = scale; + updateModelTransform(); + } + + public void setModelScaleX(@Nullable float scaleX) { + mModelScaleX = scaleX; + updateModelTransform(); + } + + public void setModelScaleY(@Nullable float scaleY) { + mModelScaleY = scaleY; + updateModelTransform(); + } + + public void setModelScaleZ(@Nullable float scaleZ) { + mModelScaleZ = scaleZ; + updateModelTransform(); + } + + public void setModelTranslateX(@Nullable float translateX) { + mModelTranslateX = translateX; + updateModelTransform(); + } + + public void setModelTranslateY(@Nullable float translateY) { + mModelTranslateY = translateY; + updateModelTransform(); + } + + public void setModelTranslateZ(@Nullable float translateZ) { + mModelTranslateZ = translateZ; + updateModelTransform(); + } + + private Object3D loadModel(String modelFileName) { + String modelFileNameArray[] = modelFileName.split("\\."); + String extension = modelFileNameArray[modelFileNameArray.length - 1].toLowerCase(); + + Object3D model = null; + + try { + InputStream modelStream = getContext().getAssets().open(modelFileName); + + switch (extension) { + case "obj": + model = Object3D.mergeAll(Loader.loadOBJ(modelStream, null, 1)); + break; + case "3ds": + model = Object3D.mergeAll(Loader.load3DS(modelStream, 1)); + break; + case "md2": + model = Loader.loadMD2(modelStream, 1); + break; + case "asc": + model = Loader.loadASC(modelStream, 1, false); + break; + case "model": + model = RNGLModelViewModelLoader.loadMODEL(modelStream); + break; + } + } catch (IOException | ModelObjectNotSupportedException e) { + e.printStackTrace(); + } + + return model; + } + + private Texture loadTexture(String textureFileName) { + Texture texture = null; + + try { + InputStream textureStream = getContext().getAssets().open(textureFileName); + texture = new Texture(textureStream); + + } catch (IOException e) { + e.printStackTrace(); + } + + return texture; + } + + private void updateModelTransform() { + if (mModel != null) { + Matrix rotationMatrix = new Matrix(); + + // First, we scale the identity matrix + rotationMatrix.setRow(0, mModelScaleX, 0, 0, 0); + rotationMatrix.setRow(1, 0, mModelScaleY, 0, 0); + rotationMatrix.setRow(2, 0, 0, mModelScaleZ, 0); + + // Second, we rotate the scaled matrix + rotationMatrix.rotateZ((float)Math.toRadians(mModelRotateZ)); + rotationMatrix.rotateY((float)Math.toRadians(mModelRotateY)); + rotationMatrix.rotateX((float)Math.toRadians(mModelRotateX)); + + // Finally, we create the translation matrix + Matrix translatonMatrix = new Matrix(); + translatonMatrix.translate(mModelTranslateX, mModelTranslateY, mModelTranslateZ); + + mModel.setTranslationMatrix(translatonMatrix); + mModel.setRotationMatrix(rotationMatrix); + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rnglmodelview/RNGLModelViewManager.java b/android/src/main/java/com/rnglmodelview/RNGLModelViewManager.java new file mode 100644 index 0000000..0b0f624 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/RNGLModelViewManager.java @@ -0,0 +1,93 @@ +package com.rnglmodelview; + +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.annotations.ReactProp; + +import javax.annotation.Nullable; + +@ReactModule(name = RNGLModelViewManager.REACT_CLASS) +public class RNGLModelViewManager extends SimpleViewManager { + + protected static final String REACT_CLASS = "RNGLModelView"; + + @Override + public String getName() { + return REACT_CLASS; + } + + private RNGLModelView mModelView; + + @Override + public RNGLModelView createViewInstance(ThemedReactContext context) { + mModelView = new RNGLModelView(context); + + return mModelView; + } + + @ReactProp(name = "model") + public void setModel(RNGLModelView view, String modelFileName) { + view.setModel(modelFileName); + } + + @ReactProp(name = "texture") + public void setModelTexture(RNGLModelView view, @Nullable String textureFileName) { + view.setModelTexture(textureFileName); + } + + @ReactProp(name = "animate") + public void setAnimate(RNGLModelView view, @Nullable boolean animate) { + view.setAnimate(animate); + } + + @ReactProp(name = "rotateX") + public void setModelRotateX(RNGLModelView view, @Nullable float rotateX) { + view.setModelRotateX(rotateX); + } + + @ReactProp(name = "rotateY") + public void setModelRotateY(RNGLModelView view, @Nullable float rotateY) { + view.setModelRotateY(rotateY); + } + + @ReactProp(name = "rotateZ") + public void setModelRotateZ(RNGLModelView view, @Nullable float rotateZ) { + view.setModelRotateZ(rotateZ); + } + + @ReactProp(name = "scale") + public void setModelScale(RNGLModelView view, @Nullable float scale) { + view.setModelScale(scale); + } + + @ReactProp(name = "scaleX") + public void setModelScaleX(RNGLModelView view, @Nullable float scaleX) { + view.setModelScaleX(scaleX); + } + + @ReactProp(name = "scaleY") + public void setModelScaleY(RNGLModelView view, @Nullable float scaleY) { + view.setModelScaleY(scaleY); + } + + @ReactProp(name = "scaleZ") + public void setModelScaleZ(RNGLModelView view, @Nullable float scaleZ) { + view.setModelScaleZ(scaleZ); + } + + @ReactProp(name = "translateX") + public void setModelTranslateX(RNGLModelView view, @Nullable float translateX) { + view.setModelTranslateX(translateX); + } + + @ReactProp(name = "translateY") + public void setModelTranslateY(RNGLModelView view, @Nullable float translateY) { + view.setModelTranslateY(translateY); + } + + @ReactProp(name = "translateZ") + public void setModelTranslateZ(RNGLModelView view, @Nullable float translateZ) { + view.setModelTranslateZ(translateZ); + } +} diff --git a/android/src/main/java/com/rnglmodelview/RNGLModelViewModelLoader.java b/android/src/main/java/com/rnglmodelview/RNGLModelViewModelLoader.java new file mode 100644 index 0000000..8507206 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/RNGLModelViewModelLoader.java @@ -0,0 +1,247 @@ +package com.rnglmodelview; + +import com.rnglmodelview.exceptions.IndexTypeNotSupportedException; +import com.rnglmodelview.exceptions.NormalTypeNotSupportedException; +import com.rnglmodelview.exceptions.PrimitiveTypeNotSupportedException; +import com.rnglmodelview.exceptions.UVTypeNotSupportedException; +import com.rnglmodelview.exceptions.VertexTypeNotSupportedException; +import com.threed.jpct.Object3D; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import javax.microedition.khronos.opengles.GL10; + +public class RNGLModelViewModelLoader { + /** + * Loads a .model file + * @param modelStream the InputStream of the .model file + * @return the parsed .model file as an Object3D object + * @throws IOException + * @throws IndexTypeNotSupportedException + * @throws PrimitiveTypeNotSupportedException + * @throws VertexTypeNotSupportedException + * @throws UVTypeNotSupportedException + * @throws NormalTypeNotSupportedException + */ + public static Object3D loadMODEL(InputStream modelStream) throws + IOException, + IndexTypeNotSupportedException, + PrimitiveTypeNotSupportedException, + VertexTypeNotSupportedException, + UVTypeNotSupportedException, + NormalTypeNotSupportedException { + // Note: The .model format byte order is in little endian + byte[] fileIdentifier = new byte[32]; + + // We don't do anything with the version yet. If some .model files don't work in the future, + // we might need to do a version check + byte[] majorVersionBytes = new byte[4]; + byte[] minorVersionBytes = new byte[4]; + + // Read the header + modelStream.read(fileIdentifier, 0, 32); + modelStream.read(majorVersionBytes, 0, 4); + modelStream.read(minorVersionBytes, 0, 4); + + // Read the table of contents + byte[] attribHeaderSizeBytes = new byte[4]; + byte[] indexBufferOffsetBytes = new byte[4]; + byte[] vertexBufferOffsetBytes = new byte[4]; + byte[] uvBufferOffsetBytes = new byte[4]; + byte[] normalBufferOffsetBytes = new byte[4]; + + modelStream.read(attribHeaderSizeBytes, 0, 4); + modelStream.read(indexBufferOffsetBytes, 0, 4); + modelStream.read(vertexBufferOffsetBytes, 0, 4); + modelStream.read(uvBufferOffsetBytes, 0, 4); + modelStream.read(normalBufferOffsetBytes, 0, 4); + + int[] indices = getModelIndices(modelStream); + float[] vertexElements = getModelVertexElements(modelStream); + float[] uvElements = getModelUVElements(modelStream); + float[] normalElements = getModelNormalElements(modelStream); + + return new Object3D(vertexElements, normalElements, uvElements, indices, 0); + } + + private static int[] getModelIndices(InputStream modelStream) + throws IOException, IndexTypeNotSupportedException, PrimitiveTypeNotSupportedException { + byte[] indexBufferSize32Bits = new byte[4]; + byte[] dataTypeBytes = new byte[4]; + byte[] primTypeBytes = new byte[4]; + byte[] sizePerElementBytes = new byte[4]; + byte[] numIndices32Bits = new byte[4]; + + modelStream.read(indexBufferSize32Bits, 0, 4); + modelStream.read(dataTypeBytes, 0, 4); + modelStream.read(primTypeBytes, 0, 4); + modelStream.read(sizePerElementBytes, 0, 4); + modelStream.read(numIndices32Bits, 0, 4); + + int indexBufferSize = ByteBuffer.wrap(indexBufferSize32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int primType = ByteBuffer.wrap(primTypeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int numIndices = ByteBuffer.wrap(numIndices32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + + if (primType != GL10.GL_TRIANGLES) { + // We only support the triangle primitives right now + throw new PrimitiveTypeNotSupportedException("Only triangle primitives are supported"); + } + + int indexTypeSize = indexBufferSize / numIndices; + + int[] indices = new int[numIndices]; + byte[] currentIndexBytes = new byte[indexTypeSize]; + + // Read the indices into a buffer + for (int i = 0; i < numIndices; i++) { + modelStream.read(currentIndexBytes, 0, indexTypeSize); + + int currentIndex = -1; + + switch (indexTypeSize) { + case 4: + currentIndex = ByteBuffer.wrap(currentIndexBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + break; + case 2: + currentIndex = ByteBuffer.wrap(currentIndexBytes).order(ByteOrder.LITTLE_ENDIAN).getShort(); + break; + case 1: + currentIndex = currentIndexBytes[0]; + break; + default: + // We don't support other types + throw new IndexTypeNotSupportedException("Only integer, short and byte indices are supported"); + } + + indices[i] = currentIndex; + } + + return indices; + } + + private static float[] getModelVertexElements(InputStream modelStream) + throws IOException, VertexTypeNotSupportedException { + byte[] vertexELementBufferSize32Bits = new byte[4]; + byte[] dataTypeBytes = new byte[4]; + byte[] primTypeBytes = new byte[4]; + byte[] sizePerElementBytes = new byte[4]; + byte[] numVertexElements32Bits = new byte[4]; + + modelStream.read(vertexELementBufferSize32Bits, 0, 4); + modelStream.read(dataTypeBytes, 0, 4); + modelStream.read(primTypeBytes, 0, 4); + modelStream.read(sizePerElementBytes, 0, 4); + modelStream.read(numVertexElements32Bits, 0, 4); + + int vertexElementBufferSize = ByteBuffer.wrap(vertexELementBufferSize32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int dataType = ByteBuffer.wrap(dataTypeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int sizePerElement = ByteBuffer.wrap(sizePerElementBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int numVertices = ByteBuffer.wrap(numVertexElements32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + + if (dataType != GL10.GL_FLOAT) { + throw new VertexTypeNotSupportedException("Only float vertices are supported"); + } + + // We have multiple elements per vertex and we will eventually throw away the fourth elements (w) + int numVertexElements = numVertices * (sizePerElement == 4 ? 3 : sizePerElement); + int vertexElementSize = vertexElementBufferSize / numVertices / sizePerElement; + float[] vertexElements = new float[numVertexElements]; + byte[] currentVertexElement32Bit = new byte[vertexElementSize]; + + // Read the indices into a buffer + for (int i = 0; i < numVertexElements; i++) { + modelStream.read(currentVertexElement32Bit, 0, vertexElementSize); + float currentVertexElement = ByteBuffer.wrap(currentVertexElement32Bit).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + vertexElements[i] = currentVertexElement; + + // If the model contains Vector4s, we need to throw away the fourth element (w) here + if (sizePerElement == 4 && (i + 1) % 3 == 0) { + modelStream.read(currentVertexElement32Bit, 0, vertexElementSize); + } + } + + return vertexElements; + } + + private static float[] getModelUVElements(InputStream modelStream) + throws IOException, UVTypeNotSupportedException { + byte[] uvBufferSize32Bits = new byte[4]; + byte[] dataTypeBytes = new byte[4]; + byte[] primTypeBytes = new byte[4]; + byte[] sizePerElementBytes = new byte[4]; + byte[] numUVs32Bits = new byte[4]; + + modelStream.read(uvBufferSize32Bits, 0, 4); + modelStream.read(dataTypeBytes, 0, 4); + modelStream.read(primTypeBytes, 0, 4); + modelStream.read(sizePerElementBytes, 0, 4); + modelStream.read(numUVs32Bits, 0, 4); + + int uvBufferSize = ByteBuffer.wrap(uvBufferSize32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int dataType = ByteBuffer.wrap(dataTypeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int sizePerElement = ByteBuffer.wrap(sizePerElementBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int numUVs = ByteBuffer.wrap(numUVs32Bits).order(ByteOrder.LITTLE_ENDIAN).getInt(); + + if (dataType != GL10.GL_FLOAT) { + throw new UVTypeNotSupportedException("Only float UVs are supported"); + } + + // We have multiple elements per uv + int numUVElements = numUVs * sizePerElement; + int uvElementTypeSize = uvBufferSize / numUVElements; + float[] uvElements = new float[numUVElements]; + byte[] currentUVElement32Bit = new byte[uvElementTypeSize]; + + // Read the indices into a buffer + for (int i = 0; i < numUVElements; i++) { + modelStream.read(currentUVElement32Bit, 0, uvElementTypeSize); + float currentUVElement = ByteBuffer.wrap(currentUVElement32Bit).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + uvElements[i] = currentUVElement; + } + + return uvElements; + } + + private static float[] getModelNormalElements(InputStream modelStream) + throws IOException, NormalTypeNotSupportedException { + byte[] normalElementBufferSize32Bit = new byte[4]; + byte[] dataTypeBytes = new byte[4]; + byte[] primTypeBytes = new byte[4]; + byte[] sizePerElementBytes = new byte[4]; + byte[] numNormalElements32Bit = new byte[4]; + + modelStream.read(normalElementBufferSize32Bit, 0, 4); + modelStream.read(dataTypeBytes, 0, 4); + modelStream.read(primTypeBytes, 0, 4); + modelStream.read(sizePerElementBytes, 0, 4); + modelStream.read(numNormalElements32Bit, 0, 4); + + int normalElementBufferSize = ByteBuffer.wrap(normalElementBufferSize32Bit).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int dataType = ByteBuffer.wrap(dataTypeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int sizePerElement = ByteBuffer.wrap(sizePerElementBytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int numNormals = ByteBuffer.wrap(numNormalElements32Bit).order(ByteOrder.LITTLE_ENDIAN).getInt(); + + if (dataType != GL10.GL_FLOAT) { + throw new NormalTypeNotSupportedException("Only float normals are supported"); + } + + // We have multiple elements per normal + int numNormalElements = numNormals * sizePerElement; + int normalElementSize = normalElementBufferSize / numNormalElements; + float[] normalElements = new float[numNormalElements]; + byte[] currentNormalElement32Bit = new byte[normalElementSize]; + + // Read the indices into a buffer + for (int i = 0; i < numNormalElements; i++) { + modelStream.read(currentNormalElement32Bit, 0, normalElementSize); + float currentNormalElement = ByteBuffer.wrap(currentNormalElement32Bit).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + normalElements[i] = currentNormalElement; + } + + return normalElements; + } +} + diff --git a/android/src/main/java/com/rnglmodelview/RNGLModelViewPackage.java b/android/src/main/java/com/rnglmodelview/RNGLModelViewPackage.java new file mode 100644 index 0000000..3558262 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/RNGLModelViewPackage.java @@ -0,0 +1,29 @@ +package com.rnglmodelview; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class RNGLModelViewPackage implements ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(new RNGLModelViewManager()); + } +} diff --git a/android/src/main/java/com/rnglmodelview/RNGLModelViewRenderer.java b/android/src/main/java/com/rnglmodelview/RNGLModelViewRenderer.java new file mode 100644 index 0000000..5047840 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/RNGLModelViewRenderer.java @@ -0,0 +1,138 @@ +package com.rnglmodelview; + +import android.content.Context; +import android.content.res.Resources; +import android.opengl.GLSurfaceView; + +import com.threed.jpct.Camera; +import com.threed.jpct.FrameBuffer; +import com.threed.jpct.GLSLShader; +import com.threed.jpct.Light; +import com.threed.jpct.Logger; +import com.threed.jpct.Object3D; +import com.threed.jpct.RGBColor; +import com.threed.jpct.SimpleVector; +import com.threed.jpct.Texture; +import com.threed.jpct.TextureInfo; +import com.threed.jpct.TextureManager; +import com.threed.jpct.World; +import com.threed.jpct.util.MemoryHelper; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class RNGLModelViewRenderer implements GLSurfaceView.Renderer { + + private boolean hasToCreateBuffer = false; + private int w = 0; + private int h = 0; + + private long time = System.currentTimeMillis(); + private FrameBuffer fb; + private World world; + private Object3D mModel; + private Texture mTexture; + private GLSLShader shader; + private Light light; + private RGBColor clearColor = new RGBColor(255, 255, 255); + private Context mContext; + private static RNGLModelViewRenderer master = null; + private GL10 previousGL = null; + private float scale = 0.05f; + private boolean mAnimate = false; + + public RNGLModelViewRenderer(Context context) { + Texture.defaultToMipmapping(true); + Texture.defaultTo4bpp(true); + mContext = context; + } + + @Override + public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { + Logger.log("onSurfaceCreated"); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + Resources res = mContext.getResources(); + + if (world == null) { + fb = new FrameBuffer(width, height); + world = new World(); + + light = new Light(world); + light.enable(); + light.setIntensity(51, 51, 51); + light.setPosition(SimpleVector.create(0, 16, 6)); + + // In jpct, the coordinate system is rotated 180 degrees around x compared to the OpenGL + // coordinate system, which means that y faces towards the bottom and z faces away from the + // screen. Since most engines and models use the OpenGL coordinate system, we fix the + // rotation on the camera. + Camera cam = world.getCamera(); + SimpleVector pos = cam.getPosition(); + cam.rotateX((float)Math.PI); + + // We move the camera to make the iOS and Android views look alike as much as possible + cam.setPosition(0, 16, 5); + + if (mModel != null) { + TextureManager tm = TextureManager.getInstance(); + + if (mTexture == null) { + if (tm.containsTexture("texture")) { + tm.removeTexture("texture"); + } + } else { + if (tm.containsTexture("texture")) { + tm.replaceTexture("texture", mTexture); + } else { + tm.addTexture("texture", mTexture); + } + + TextureInfo ti = new TextureInfo(TextureManager.getInstance().getTextureID("texture")); + mModel.setTexture(ti); + } + + mModel.setSpecularLighting(true); + mModel.build(); + mModel.strip(); + world.addObject(mModel); + } + + MemoryHelper.compact(); + + world.compileAllObjects(); + } else if (previousGL != gl) { + fb = new FrameBuffer(width, height); + } + + previousGL = gl; + + renderFrame(); + } + + @Override + public void onDrawFrame(GL10 gl) { + if (mAnimate) { + renderFrame(); + } + } + + private void renderFrame() { + fb.clear(clearColor); + world.renderScene(fb); + world.draw(fb); + fb.display(); + } + + public void setModel(Object3D model) { + mModel = model; + } + + public void setTexture(Texture texture) { + mTexture = texture; + } + + public void setAnimate(boolean animate) { mAnimate = animate; } +} \ No newline at end of file diff --git a/android/src/main/java/com/rnglmodelview/exceptions/IndexTypeNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/IndexTypeNotSupportedException.java new file mode 100644 index 0000000..4e2367d --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/IndexTypeNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public class IndexTypeNotSupportedException extends ModelObjectNotSupportedException { + public IndexTypeNotSupportedException(String message) { + super(message); + } +} diff --git a/android/src/main/java/com/rnglmodelview/exceptions/ModelObjectNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/ModelObjectNotSupportedException.java new file mode 100644 index 0000000..e4b4bf5 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/ModelObjectNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public abstract class ModelObjectNotSupportedException extends Exception { + ModelObjectNotSupportedException(String message) { + super(message); + } +} diff --git a/android/src/main/java/com/rnglmodelview/exceptions/NormalTypeNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/NormalTypeNotSupportedException.java new file mode 100644 index 0000000..64d01e4 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/NormalTypeNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public class NormalTypeNotSupportedException extends ModelObjectNotSupportedException { + public NormalTypeNotSupportedException(String message) { + super(message); + } +} diff --git a/android/src/main/java/com/rnglmodelview/exceptions/PrimitiveTypeNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/PrimitiveTypeNotSupportedException.java new file mode 100644 index 0000000..f8e467e --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/PrimitiveTypeNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public class PrimitiveTypeNotSupportedException extends ModelObjectNotSupportedException { + public PrimitiveTypeNotSupportedException(String message) { + super(message); + } +} diff --git a/android/src/main/java/com/rnglmodelview/exceptions/UVTypeNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/UVTypeNotSupportedException.java new file mode 100644 index 0000000..40dc4bf --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/UVTypeNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public class UVTypeNotSupportedException extends ModelObjectNotSupportedException { + public UVTypeNotSupportedException(String message) { + super(message); + } +} diff --git a/android/src/main/java/com/rnglmodelview/exceptions/VertexTypeNotSupportedException.java b/android/src/main/java/com/rnglmodelview/exceptions/VertexTypeNotSupportedException.java new file mode 100644 index 0000000..9a16ae9 --- /dev/null +++ b/android/src/main/java/com/rnglmodelview/exceptions/VertexTypeNotSupportedException.java @@ -0,0 +1,7 @@ +package com.rnglmodelview.exceptions; + +public class VertexTypeNotSupportedException extends ModelObjectNotSupportedException { + public VertexTypeNotSupportedException(String message) { + super(message); + } +} diff --git a/example/.gitignore b/example/.gitignore index 5435af8..b6da9f0 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -4,3 +4,164 @@ npm-debug.* .DS_Store Pods/ Podfile.lock + +### Android ### +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +### Android Patch ### +gen-external-apklibs + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*~ +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/libraries/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.gitignore.io/api/android,androidstudio \ No newline at end of file diff --git a/example/README.md b/example/README.md index 3ae32b3..668e580 100644 --- a/example/README.md +++ b/example/README.md @@ -2,6 +2,8 @@ To build it, switch into this folder and set it up as following: +## iOS + ```sh $ npm install $ cd ios @@ -9,3 +11,11 @@ $ pod install $ cd .. $ react-native run-ios ``` + +## Android + +```sh +$ npm install +$ react-native link +$ react-native run-android +``` \ No newline at end of file diff --git a/example/android/app/BUCK b/example/android/app/BUCK new file mode 100644 index 0000000..f85311a --- /dev/null +++ b/example/android/app/BUCK @@ -0,0 +1,65 @@ +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] + +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = "all-libs", + exported_deps = lib_deps, +) + +android_library( + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], +) + +android_build_config( + name = "build_config", + package = "com.rnglmodelviewexample", +) + +android_resource( + name = "res", + package = "com.rnglmodelviewexample", + res = "src/main/res", +) + +android_binary( + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], +) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..140a017 --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,140 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"] + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + applicationId "com.rnglmodelviewexample" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +} + +dependencies { + compile project(':react-native-gl-model-view') + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:+" // From node_modules +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro new file mode 100644 index 0000000..48361a9 --- /dev/null +++ b/example/android/app/proguard-rules.pro @@ -0,0 +1,66 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Disabling obfuscation is useful if you collect stack traces from production crashes +# (unless you are using a system that supports de-obfuscate the stack traces). +-dontobfuscate + +# React Native + +# Keep our interfaces so they can be used by other ProGuard rules. +# See http://sourceforge.net/p/proguard/bugs/466/ +-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip +-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters +-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip + +# Do not strip any method/class that is annotated with @DoNotStrip +-keep @com.facebook.proguard.annotations.DoNotStrip class * +-keep @com.facebook.common.internal.DoNotStrip class * +-keepclassmembers class * { + @com.facebook.proguard.annotations.DoNotStrip *; + @com.facebook.common.internal.DoNotStrip *; +} + +-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { + void set*(***); + *** get*(); +} + +-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } +-keep class * extends com.facebook.react.bridge.NativeModule { *; } +-keepclassmembers,includedescriptorclasses class * { native ; } +-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } + +-dontwarn com.facebook.react.** + +# okhttp + +-keepattributes Signature +-keepattributes *Annotation* +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +# okio + +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9287512 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/assets/demon.model b/example/android/app/src/main/assets/demon.model new file mode 100644 index 0000000..1ce8171 Binary files /dev/null and b/example/android/app/src/main/assets/demon.model differ diff --git a/example/android/app/src/main/assets/demon.png b/example/android/app/src/main/assets/demon.png new file mode 100644 index 0000000..55c1ddf Binary files /dev/null and b/example/android/app/src/main/assets/demon.png differ diff --git a/example/android/app/src/main/java/com/rnglmodelviewexample/MainActivity.java b/example/android/app/src/main/java/com/rnglmodelviewexample/MainActivity.java new file mode 100644 index 0000000..84ae103 --- /dev/null +++ b/example/android/app/src/main/java/com/rnglmodelviewexample/MainActivity.java @@ -0,0 +1,15 @@ +package com.rnglmodelviewexample; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "rnglmodelviewexample"; + } +} diff --git a/example/android/app/src/main/java/com/rnglmodelviewexample/MainApplication.java b/example/android/app/src/main/java/com/rnglmodelviewexample/MainApplication.java new file mode 100644 index 0000000..c4c4441 --- /dev/null +++ b/example/android/app/src/main/java/com/rnglmodelviewexample/MainApplication.java @@ -0,0 +1,42 @@ +package com.rnglmodelviewexample; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.rnglmodelview.RNGLModelViewPackage; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new RNGLModelViewPackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } +} diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..e907271 --- /dev/null +++ b/example/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + rnglmodelviewexample + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..319eb0c --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..eed9972 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + } +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..1fd964e --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useDeprecatedNdk=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b5166da Binary files /dev/null and b/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..dbdc05d --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/example/android/gradlew b/example/android/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/example/android/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/example/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/android/keystores/BUCK b/example/android/keystores/BUCK new file mode 100644 index 0000000..88e4c31 --- /dev/null +++ b/example/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = "debug", + properties = "debug.keystore.properties", + store = "debug.keystore", + visibility = [ + "PUBLIC", + ], +) diff --git a/example/android/keystores/debug.keystore.properties b/example/android/keystores/debug.keystore.properties new file mode 100644 index 0000000..121bfb4 --- /dev/null +++ b/example/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..a821175 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'rnglmodelviewexample' +include ':react-native-gl-model-view' +project(':react-native-gl-model-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gl-model-view/android') + +include ':app' diff --git a/example/index.android.js b/example/index.android.js new file mode 100644 index 0000000..6738a03 --- /dev/null +++ b/example/index.android.js @@ -0,0 +1,3 @@ +import { AppRegistry } from 'react-native'; +import App from './src/App'; +AppRegistry.registerComponent('rnglmodelviewexample', () => App); diff --git a/example/yarn.lock b/example/yarn.lock index f94fc4f..7d94c2a 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -2364,8 +2364,8 @@ react-devtools-core@^2.0.8: cross-env "^3.1.4" ws "^2.0.3" -react-native-gl-model-view@../: - version "1.0.0" +"react-native-gl-model-view@file:..": + version "1.0.1" react-native@0.43.2: version "0.43.2" diff --git a/index.js b/index.js index 8df1c17..698e7eb 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ import React, { Component, PropTypes } from 'react'; -import { requireNativeComponent } from 'react-native'; +import { requireNativeComponent, View } from 'react-native'; class GLModelView extends Component { render() { @@ -8,6 +8,7 @@ class GLModelView extends Component { } GLModelView.propTypes = { + ...View.propTypes, animate: PropTypes.bool, model: PropTypes.string.isRequired,