diff --git a/README.md b/README.md index 00790473..3229724f 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,20 @@ Android 3D Model Viewer ![codeship badge](https://codeship.com/projects/52cf9560-deb2-0134-4203-2aaddef843aa/status?branch=master) This is a demo of OpenGL ES 2.0. -It is basically an android application with a 3D renderer that can load Wavefront Obj files. +It is basically an android application with a 3D renderer that can load Wavefront Obj & STL files. The purpose of this application is to learn and share how to draw using OpenGL language. -https://en.wikipedia.org/wiki/Wavefront_.obj_file +* Wafefront format (OBJ): https://en.wikipedia.org/wiki/Wavefront_.obj_file +* STereoLithography format (STL): https://en.wikipedia.org/wiki/STL_(file_format) -News (16/04/2017) +News (17/04/2017) ================= -* Fixed #16: Toogle point drawing + several improvements -* Fixed #15: Toggle rotating light + fixed wireframe colors -* Fixed #14: Camera movement improved -* Fixed #13: Fixed parsing of vertices -* Fixed #12: Wireframe performance -* Fixed #11: Normals generation -* Fixed #10: Fixed texture issue +* Enhancement #17: Added support for TLS format +* Fixed #16: Toogle point drawing +* Fixed #15: Toggle rotating light * Fixed #1: Cpu Performance problems * Fixed #5: Memory Performance problems @@ -62,7 +59,7 @@ Features ======== - OpenGL ES 2.0 API - - OBJ format supported (wavefront) + - Formats: OBJ (wavefront) & STL (STereoLithography) - calculation of normals - transformations: scaling, rotation, translation - colors @@ -128,6 +125,11 @@ ChangeLog (f) fixed, (i) improved, (n) new feature +- 1.3.1 (17/04/2017) + - (n) #17: Added support for STL files + - (n) #17: Asynchronous building of model so the build rendering is previewed + - (f) #17: Added Toasts to buttons to show current state + - 1.2.10 (16/04/2017) - (f) #16: Immersive mode is now configurable in the ModelActivity Intent: b.putString("immersiveMode", "false"); - (f) #16: Background color configurable in the ModelActivity Intent: b.putString("backgroundColor", "0 0 0 1"); diff --git a/app/build/outputs/apk/app-release.apk b/app/build/outputs/apk/app-release.apk index 5b9e48a4..0342a819 100644 Binary files a/app/build/outputs/apk/app-release.apk and b/app/build/outputs/apk/app-release.apk differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 21960b2b..1c5510b0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="10" + android:versionName="1.3.0" > { + + /** + * URL to the 3D model + */ + protected final URL url; + /** + * Callback to notify of events + */ + protected final Object3DBuilder.Callback callback; + /** + * The dialog that will show the progress of the loading + */ + protected final ProgressDialog dialog; + /** + * The parent activity + */ + private final Activity parent; + /** + * Directory where the model is located (null when its loaded from asset) + */ + private final File currentDir; + /** + * Asset directory where the model is loaded (null when its loaded from the filesystem) + */ + private final String assetsDir; + /** + * Id of the data being loaded + */ + private final String modelId; + /** + * Exception when loading data (if any) + */ + protected Exception error; + + /** + * Build a new progress dialog for loading the data model asynchronously + * + * @param url the URL pointing to the 3d model + * @param currentDir the directory where the model is located (null when the model is an asset) + * @param modelId the id the data being loaded + */ + public LoaderTask(Activity parent, URL url, File currentDir, String assetsDir, String modelId, Object3DBuilder.Callback callback) { + this.parent = parent; + this.url = url; + this.currentDir = currentDir; + this.assetsDir = assetsDir; + this.modelId = modelId; + this.dialog = new ProgressDialog(parent); + this.callback = callback; + } + + + @Override + protected void onPreExecute() { + super.onPreExecute(); + // this.dialog = ProgressDialog.show(this.parent, "Please wait ...", "Loading model data...", true); + // this.dialog.setTitle(modelId); + this.dialog.setMessage("Loading..."); + this.dialog.setCancelable(false); + this.dialog.show(); + } + + + + @Override + protected Object3DData doInBackground(Void... params) { + try { + Object3DData data = build(); + callback.onLoadComplete(data); + build(data); + return data; + } catch (Exception ex) { + error = ex; + return null; + } + } + + protected abstract Object3DData build() throws Exception; + + protected abstract void build(Object3DData data) throws Exception; + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + switch (values[0]) { + case 0: + this.dialog.setMessage("Analyzing model..."); + break; + case 1: + this.dialog.setMessage("Allocating memory..."); + break; + case 2: + this.dialog.setMessage("Loading data..."); + break; + case 3: + this.dialog.setMessage("Scaling object..."); + break; + case 4: + this.dialog.setMessage("Building 3D model..."); + break; + case 5: + // Toast.makeText(parent, modelId + " Build!", Toast.LENGTH_LONG).show(); + break; + } + } + + @Override + protected void onPostExecute(Object3DData data) { + super.onPostExecute(data); + if (dialog.isShowing()) { + dialog.dismiss(); + } + if (error != null) { + callback.onLoadError(error); + } else { + callback.onBuildComplete(data); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DBuilder.java b/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DBuilder.java index 9c20ee94..34151726 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DBuilder.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DBuilder.java @@ -1,10 +1,8 @@ package org.andresoviedo.app.model3D.model; import android.app.Activity; -import android.app.ProgressDialog; import android.content.res.AssetManager; import android.opengl.GLES20; -import android.os.AsyncTask; import android.util.Log; import org.andresoviedo.app.model3D.services.WavefrontLoader; @@ -13,6 +11,8 @@ import org.andresoviedo.app.model3D.services.WavefrontLoader.Material; import org.andresoviedo.app.model3D.services.WavefrontLoader.Materials; import org.andresoviedo.app.model3D.services.WavefrontLoader.Tuple3; +import org.andresoviedo.app.model3D.services.stl.STLLoader; +import org.andresoviedo.app.model3D.services.wavefront.WavefrontLoader2; import org.andresoviedo.app.util.math.Math3DUtils; import org.apache.commons.io.IOUtils; @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -318,6 +319,11 @@ public static interface Callback { private Object3DV7 object3dv7; private Object3DV8 object3dv8; + static { + System.setProperty("java.protocol.handler.pkgs", "org.andresoviedo.app.util.url|"+System.getProperty("java.protocol.handler.pkgs")); + Log.i("Object3DBuilder", "java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs")); + } + public static Object3DData buildPoint(float[] point) { return new Object3DData(createNativeByteBuffer(point.length * 4).asFloatBuffer().put(point)) .setDrawMode(GLES20.GL_POINTS); @@ -382,14 +388,14 @@ public static Object3DData loadV5(AssetManager assets, String assetDir, String a is = assets.open(modelId); wfl.loadModel(is); - - wfl.centerScale(); is.close(); Object3DData data3D = new Object3DData(wfl.getVerts(), wfl.getNormals(), wfl.getTexCoords(), wfl.getFaces(), wfl.getFaceMats(), wfl.getMaterials()); data3D.setId(assetFilename); data3D.setAssetsDir(assetDir); + data3D.setDimensions(wfl.getDimensions()); + data3D.centerScale(); data3D.setDrawMode(GLES20.GL_TRIANGLES); generateArrays(assets, data3D); @@ -469,7 +475,7 @@ public static Object3DData generateArrays(AssetManager assets, Object3DData obj) final FloatBuffer vertexNormalsArrayBuffer = createNativeByteBuffer(faces.getSize() * 3 * 3 * 4).asFloatBuffer();; obj.setVertexNormalsArrayBuffer(vertexNormalsArrayBuffer); - // load file normals + // build file normals final FloatBuffer vertexNormalsBuffer = obj.getNormals().asReadOnlyBuffer(); if (vertexNormalsBuffer.capacity() > 0) { Log.i("Object3DBuilder", "Populating normals buffer..."); @@ -593,21 +599,23 @@ public static Object3DData generateArrays(AssetManager assets, Object3DData obj) ArrayList texCoords = obj.getTexCoords(); if (texCoords != null && texCoords.size() > 0) { - Log.i("Object3DBuilder", "Allocating texture buffer..."); + Log.i("Object3DBuilder", "Allocating/populating texture buffer..."); FloatBuffer textureCoordsBuffer = createNativeByteBuffer(texCoords.size() * 2 * 4).asFloatBuffer(); - FloatBuffer textureCoordsArraysBuffer = createNativeByteBuffer(2 * faces.getVerticesReferencesCount() * 4).asFloatBuffer(); - obj.setTextureCoordsArrayBuffer(textureCoordsArraysBuffer); - - Log.i("Object3DBuilder", "Populating texture buffer..."); for (Tuple3 texCor : texCoords) { textureCoordsBuffer.put(texCor.getX()); textureCoordsBuffer.put(obj.isFlipTextCoords() ? 1 - texCor.getY() : texCor.getY()); } + + Log.i("Object3DBuilder", "Populating texture array buffer..."); + FloatBuffer textureCoordsArraysBuffer = createNativeByteBuffer(2 * faces.getVerticesReferencesCount() * 4).asFloatBuffer(); + obj.setTextureCoordsArrayBuffer(textureCoordsArraysBuffer); + try { boolean anyTextureOk = false; String currentTexture = null; + Log.i("Object3DBuilder", "Populating texture array buffer..."); int counter = 0; for (int i = 0; i < faces.facesTexIdxs.size(); i++) { @@ -694,35 +702,54 @@ public static Object3DData buildBoundingBox(Object3DData obj) { */ public static Object3DData buildWireframe(Object3DData objData) { - if (objData.getDrawOrder() == null) return objData; - - try { - Log.i("Object3DBuilder", "Building wireframe..."); - IntBuffer drawBuffer = objData.getDrawOrder().asReadOnlyBuffer(); - IntBuffer wireframeDrawOrder = createNativeByteBuffer(drawBuffer.capacity() * 2 * 4).asIntBuffer(); - for (int i = 0; i < drawBuffer.capacity(); i+=3) { - int v0 = drawBuffer.get(i); - int v1 = drawBuffer.get(i+1); - int v2 = drawBuffer.get(i+2); - if (objData.isDrawUsingArrays()){ - v0 = i; - v1 = i+1; - v2 = i+2; + if (objData.getDrawOrder() != null) { + + try { + Log.i("Object3DBuilder", "Building wireframe..."); + IntBuffer drawBuffer = objData.getDrawOrder().asReadOnlyBuffer(); + IntBuffer wireframeDrawOrder = createNativeByteBuffer(drawBuffer.capacity() * 2 * 4).asIntBuffer(); + for (int i = 0; i < drawBuffer.capacity(); i += 3) { + int v0 = drawBuffer.get(i); + int v1 = drawBuffer.get(i + 1); + int v2 = drawBuffer.get(i + 2); + if (objData.isDrawUsingArrays()) { + v0 = i; + v1 = i + 1; + v2 = i + 2; + } + wireframeDrawOrder.put(v0); + wireframeDrawOrder.put(v1); + wireframeDrawOrder.put(v1); + wireframeDrawOrder.put(v2); + wireframeDrawOrder.put(v2); + wireframeDrawOrder.put(v0); } - wireframeDrawOrder.put(v0); - wireframeDrawOrder.put(v1); - wireframeDrawOrder.put(v1); - wireframeDrawOrder.put(v2); - wireframeDrawOrder.put(v2); - wireframeDrawOrder.put(v0); + return new Object3DData(objData.getVertexArrayBuffer()).setVertexBuffer(objData.getVertexBuffer()).setDrawOrder(wireframeDrawOrder). + setVertexNormalsArrayBuffer(objData.getVertexNormalsArrayBuffer()).setColor(objData.getColor()) + .setVertexColorsArrayBuffer(objData.getVertexColorsArrayBuffer()).setTextureCoordsArrayBuffer(objData.getTextureCoordsArrayBuffer()) + .setPosition(objData.getPosition()).setRotation(objData.getRotation()).setScale(objData.getScale()) + .setDrawMode(GLES20.GL_LINES).setDrawUsingArrays(false); + } catch (Exception ex) { + Log.e("Object3DBuilder", ex.getMessage(), ex); + } + } + else if (objData.getVertexArrayBuffer() != null){ + Log.i("Object3DBuilder", "Building wireframe..."); + FloatBuffer vertexBuffer = objData.getVertexArrayBuffer(); + IntBuffer wireframeDrawOrder = createNativeByteBuffer(vertexBuffer.capacity()/3 * 2 * 4).asIntBuffer(); + for (int i = 0; i < vertexBuffer.capacity()/3; i += 3) { + wireframeDrawOrder.put(i); + wireframeDrawOrder.put(i+1); + wireframeDrawOrder.put(i+1); + wireframeDrawOrder.put(i+2); + wireframeDrawOrder.put(i+2); + wireframeDrawOrder.put(i); } return new Object3DData(objData.getVertexArrayBuffer()).setVertexBuffer(objData.getVertexBuffer()).setDrawOrder(wireframeDrawOrder). setVertexNormalsArrayBuffer(objData.getVertexNormalsArrayBuffer()).setColor(objData.getColor()) .setVertexColorsArrayBuffer(objData.getVertexColorsArrayBuffer()).setTextureCoordsArrayBuffer(objData.getTextureCoordsArrayBuffer()) .setPosition(objData.getPosition()).setRotation(objData.getRotation()).setScale(objData.getScale()) .setDrawMode(GLES20.GL_LINES).setDrawUsingArrays(false); - } catch (Exception ex) { - Log.e("Object3DBuilder", ex.getMessage(), ex); } return objData; } @@ -831,267 +858,33 @@ private static ByteBuffer createNativeByteBuffer(int length) { return bb; } - public static void loadV5Async(final Activity parent, final File file, final String assetsDir, final String assetName, - final Callback callback) { + public static void loadV6AsyncParallel(final Activity parent, final URL url, final File file, final String assetsDir, final String assetName, + final Callback callback) { final String modelId = file != null ? file.getName() : assetName; - final File currentDir = file != null ? file.getParentFile() : null; - Log.i("Object3DBuilder", "Loading model "+modelId+". async.."); + Log.i("Object3DBuilder", "Loading model " + modelId + ". async and parallel.."); + if (modelId.toLowerCase().endsWith(".obj")) { + loadV6AsyncParallel_Obj(parent, file, assetsDir, assetName, callback); + } else if (modelId.toLowerCase().endsWith(".stl")) { + Log.i("Object3DBuilder", "Loading STL object from: "+url); + STLLoader.loadSTLAsync(parent, url, callback); + } + } - LoaderTask loaderTask = new LoaderTask(parent, currentDir, assetsDir, modelId) { - @Override - protected void onPostExecute(final Object3DData data) { - super.onPostExecute(data); - if (error != null) { - callback.onLoadError(error); - } else { - callback.onLoadComplete(data); - } - } - }; - loaderTask.execute(); - } - public static void loadV6AsyncParallel(final Activity parent, final File file, final String assetsDir, final String assetName, + public static void loadV6AsyncParallel_Obj(final Activity parent, final File file, final String assetsDir, final String assetName, final Callback callback) { final String modelId = file != null ? file.getName() : assetName; final File currentDir = file != null ? file.getParentFile() : null; Log.i("Object3DBuilder", "Loading model "+modelId+". async and parallel.."); - - LoaderTask loaderTask = new LoaderTask(parent, currentDir, assetsDir, modelId) { - - @Override - protected void onPostExecute(final Object3DData data) { - super.onPostExecute(data); - if (error != null) { - callback.onLoadError(error); - } else { - callback.onLoadComplete(data); - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - build3DData(currentDir,modelId,assetsDir,parent,data,callback); - return null; - } - }.execute(); - } - } - }; - loaderTask.execute(); + WavefrontLoader2.loadAsync(parent, null, currentDir, assetsDir, modelId, callback); } - - private static void build3DData(File file, String modelId, String assetsDir, Activity parent, Object3DData data, Callback callback) { - try { - InputStream modelDataStream = null; - if (file != null) { - modelDataStream = new FileInputStream(new File(file, modelId)); - } else if (assetsDir != null) { - modelDataStream = parent.getAssets().open(assetsDir + "/" + modelId); - } - if (modelDataStream == null) { - Log.e("Object3DBuilder", "Problem loading data. Null stream"); - return; - } - data.getLoader().loadModel(modelDataStream); - data.getLoader().centerScale(); - - modelDataStream.close(); - data.setDrawMode(GLES20.GL_TRIANGLES); - Object3DBuilder.generateArrays(parent.getAssets(), data); - - if (callback != null){ - callback.onLoadComplete(data); - } - } catch (Exception e) { - Log.e("Object3DBuilder",e.getMessage(),e); - - if (callback != null) { - callback.onLoadError(e); - } - } - } - - } -/** - * This component allows loading the model without blocking the UI. - * - * @author andresoviedo - */ -class LoaderTask extends AsyncTask { - - /** - * The parent activity - */ - private final Activity parent; - /** - * Directory where the model is located (null when its loaded from asset) - */ - private final File currentDir; - /** - * Asset directory where the model is loaded (null when its loaded from the filesystem) - */ - private final String assetsDir; - /** - * Id of the data being loaded - */ - private final String modelId; - /** - * The dialog that will show the progress of the loading - */ - private final ProgressDialog dialog; - /** - * Exception when loading data (if any) - */ - protected Exception error; - /** - * Whether to build the model immediately or lazily - */ - boolean immediateBuild = true; - - /** - * Build a new progress dialog for loading the data model asynchronously - * - * @param currentDir the directory where the model is located (null when the model is an asset) - * @param modelId the id the data being loaded - */ - public LoaderTask(Activity parent, File currentDir, String assetsDir, String modelId) { - this.parent = parent; - this.currentDir = currentDir; - this.assetsDir = assetsDir; - this.modelId = modelId; - this.dialog = new ProgressDialog(parent); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - // this.dialog = ProgressDialog.show(this.parent, "Please wait ...", "Loading model data...", true); - // this.dialog.setTitle(modelId); - // this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); - this.dialog.setCancelable(false); - this.dialog.show(); - } - - private InputStream getInputStream(){ - Log.i("LoaderTask", "Opening "+modelId+"..."); - try { - final InputStream ret; - if (currentDir != null) { - return new FileInputStream(new File(currentDir,modelId)); - } else if (assetsDir != null) { - return parent.getAssets().open(assetsDir + "/"+modelId); - } else { - throw new IllegalArgumentException("Model data source not specified"); - } - } catch (IOException ex) { - throw new RuntimeException( - "There was a problem opening file/asset '" + (currentDir != null ? currentDir : assetsDir) + "/"+modelId + "'"); - } - } - - private static void closeStream(InputStream stream){ - if (stream == null) return; - try { - if (stream != null) { - stream.close(); - } - } catch (IOException ex) { - Log.e("LoaderTask", "Problem closing stream: " + ex.getMessage(), ex); - } - } - - @Override - protected Object3DData doInBackground(Void... params) { - - InputStream params0 = getInputStream(); - InputStream params1 = getInputStream(); - try { - WavefrontLoader wfl = new WavefrontLoader(""); - - // allocate memory - publishProgress(0); - wfl.analyzeModel(params0); - - // load obj file - if (immediateBuild){ - - publishProgress(1); - wfl.allocateBuffers(); - - publishProgress(2); - wfl.loadModel(params1); - wfl.reportOnModel(); - - // scale object - publishProgress(3); - wfl.centerScale(); - } - - // create the 3D object - Object3DData data3D = new Object3DData(wfl.getVerts(), wfl.getNormals(), wfl.getTexCoords(), wfl.getFaces(), - wfl.getFaceMats(), wfl.getMaterials()); - data3D.setId(modelId); - data3D.setCurrentDir(currentDir); - data3D.setAssetsDir(assetsDir); - data3D.setLoader(wfl); - data3D.setDrawMode(GLES20.GL_TRIANGLES); - - // load 3D object buffers - if (immediateBuild){ - publishProgress(4); - Object3DBuilder.generateArrays(parent.getAssets(), data3D); - publishProgress(5); - } - - return data3D; - } catch (Exception ex) { - error = ex; - return null; - } finally { - closeStream(params0); - closeStream(params1); - } - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - switch (values[0]) { - case 0: - this.dialog.setMessage("Analyzing model..."); - break; - case 1: - this.dialog.setMessage("Allocating memory..."); - break; - case 2: - this.dialog.setMessage("Loading data..."); - break; - case 3: - this.dialog.setMessage("Scaling object..."); - break; - case 4: - this.dialog.setMessage("Building 3D model..."); - break; - case 5: - // Toast.makeText(parent, modelId + " Build!", Toast.LENGTH_LONG).show(); - break; - } - } - - @Override - protected void onPostExecute(Object3DData success) { - super.onPostExecute(success); - if (dialog.isShowing()) { - dialog.dismiss(); - } - } -} class BoundingBox { diff --git a/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DData.java b/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DData.java index 6b2bf092..89fccdfd 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DData.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/model/Object3DData.java @@ -7,11 +7,9 @@ import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; -import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.List; -import org.andresoviedo.app.model3D.entities.*; import org.andresoviedo.app.model3D.entities.BoundingBox; import org.andresoviedo.app.model3D.services.WavefrontLoader; import org.andresoviedo.app.model3D.services.WavefrontLoader.FaceMaterials; @@ -34,12 +32,12 @@ public class Object3DData { // opengl version to use to draw this object private int version = 5; /** - * The directory where the files reside so we can load referenced files in the model like material and textures + * The directory where the files reside so we can build referenced files in the model like material and textures * files */ private File currentDir; /** - * The assets directory where the files reside so we can load referenced files in the model like material and + * The assets directory where the files reside so we can build referenced files in the model like material and * textures files */ private String assetsDir; @@ -61,17 +59,13 @@ public class Object3DData { // Model data private FloatBuffer vertexBuffer = null; - private FloatBuffer normals; + private FloatBuffer vertexNormalsBuffer = null; private IntBuffer drawOrderBuffer = null; - private ArrayList texCoords; private Faces faces; private FaceMaterials faceMats; private Materials materials; - // Processed data - private FloatBuffer vertexNormalsBuffer = null; - // Processed arrays private FloatBuffer vertexArrayBuffer = null; private FloatBuffer vertexColorsArrayBuffer = null; @@ -93,6 +87,7 @@ public class Object3DData { private boolean changed; // Async Loader + private WavefrontLoader.ModelDimensions modelDimensions; private WavefrontLoader loader; public Object3DData(FloatBuffer vertexArrayBuffer) { @@ -126,14 +121,40 @@ public Object3DData(FloatBuffer verts, FloatBuffer normals, ArrayList te FaceMaterials faceMats, Materials materials) { super(); this.vertexBuffer = verts; - this.normals = normals; - this.drawOrderBuffer = faces.getIndexBuffer(); + this.vertexNormalsBuffer = normals; this.texCoords = texCoords; - this.faces = faces; + this.faces = faces; // parameter "faces" could be null in case of async loading this.faceMats = faceMats; this.materials = materials; } + public void setLoader(WavefrontLoader loader) { + this.loader = loader; + } + + + public WavefrontLoader getLoader() { + return loader; + } + + public void setDimensions(WavefrontLoader.ModelDimensions modelDimensions) { + this.modelDimensions = modelDimensions; + } + + public WavefrontLoader.ModelDimensions getDimensions() { + return modelDimensions; + } + + /** + * Can be called when the faces were loaded asynchronously + * + * @param faces 3d faces + */ + public void setFaces(Faces faces) { + this.faces = faces; + this.drawOrderBuffer = faces.getIndexBuffer(); + } + public boolean isVisible() { return isVisible; } @@ -310,7 +331,7 @@ public FloatBuffer getVerts() { } public FloatBuffer getNormals() { - return normals; + return vertexNormalsBuffer; } public ArrayList getTexCoords() { @@ -353,8 +374,9 @@ public FloatBuffer getVertexArrayBuffer() { return vertexArrayBuffer; } - public void setVertexArrayBuffer(FloatBuffer vertexArrayBuffer) { + public Object3DData setVertexArrayBuffer(FloatBuffer vertexArrayBuffer) { this.vertexArrayBuffer = vertexArrayBuffer; + return this; } public FloatBuffer getVertexNormalsArrayBuffer() { @@ -646,12 +668,40 @@ public BoundingBox getBoundingBox() { return boundingBox; } - public void setLoader(WavefrontLoader loader) { - this.loader = loader; - } + public void centerScale() + /* + * Position the model so it's center is at the origin, and scale it so its longest dimension is no bigger than + * maxSize. + */ + { + // calculate a scale factor + float scaleFactor = 1.0f; + float largest = modelDimensions.getLargest(); + // System.out.println("Largest dimension: " + largest); + if (largest != 0.0f) + scaleFactor = (1.0f / largest); + Log.i("Object3DData","Scaling model with factor: " + scaleFactor+". Largest: "+largest); + + // get the model's center point + Tuple3 center = modelDimensions.getCenter(); + Log.i("Object3DData","Objects actual position: " + center.toString()); + + // modify the model's vertices + float x0, y0, z0; + float x, y, z; + FloatBuffer vertexBuffer = getVertexBuffer() != null? getVertexBuffer() : getVertexArrayBuffer(); + for (int i = 0; i < vertexBuffer.capacity()/3; i++) { + x0 = vertexBuffer.get(i*3); + y0 = vertexBuffer.get(i*3+1); + z0 = vertexBuffer.get(i*3+2); + x = (x0 - center.getX()) * scaleFactor; + vertexBuffer.put(i*3,x); + y = (y0 - center.getY()) * scaleFactor; + vertexBuffer.put(i*3+1,y); + z = (z0 - center.getZ()) * scaleFactor; + vertexBuffer.put(i*3+2,z); + } + } // end of centerScale() - public WavefrontLoader getLoader() { - return loader; - } } diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/SceneLoader.java b/app/src/main/java/org/andresoviedo/app/model3D/services/SceneLoader.java index 7069d8ec..1bde902e 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/services/SceneLoader.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/SceneLoader.java @@ -1,5 +1,8 @@ package org.andresoviedo.app.model3D.services; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -7,9 +10,8 @@ import org.andresoviedo.app.model3D.model.Object3DBuilder.Callback; import org.andresoviedo.app.model3D.model.Object3DData; import org.andresoviedo.app.model3D.view.ModelActivity; +import org.andresoviedo.app.util.url.android.Handler; -import android.opengl.GLES20; -import android.opengl.Matrix; import android.os.SystemClock; import android.util.Log; import android.widget.Toast; @@ -84,11 +86,35 @@ public void init() { // Load object if (parent.getParamFile() != null || parent.getParamAssetDir() != null) { - Object3DBuilder.loadV5Async(parent, parent.getParamFile(), parent.getParamAssetDir(), + + // Initialize assets url handler + Handler.assets = parent.getAssets(); + // Handler.classLoader = parent.getClassLoader(); (optional) + // Handler.androidResources = parent.getResources(); (optional) + + // Create asset url + final URL url; + try { + if (parent.getParamFile() != null) { + url = parent.getParamFile().toURI().toURL(); + } else { + url = new URL("android://org.andresoviedo.dddmodel2/assets/" + parent.getParamAssetDir() + File.separator + parent.getParamAssetFilename()); + + } + } catch (MalformedURLException e) { + Log.e("SceneLoader", e.getMessage(), e); + throw new RuntimeException(e); + } + + Object3DBuilder.loadV6AsyncParallel(parent, url, parent.getParamFile(), parent.getParamAssetDir(), parent.getParamAssetFilename(), new Callback() { + long startTime = SystemClock.uptimeMillis(); + @Override public void onBuildComplete(Object3DData data) { + final String elapsed = (SystemClock.uptimeMillis() - startTime)/1000+" secs"; + makeToastText("Load complete ("+elapsed+")", Toast.LENGTH_LONG); } @Override @@ -105,11 +131,18 @@ public void onLoadError(Exception ex) { "There was a problem building the model: " + ex.getMessage(), Toast.LENGTH_LONG) .show(); } - }); } } + private void makeToastText(final String text, final int toastDuration) { + parent.runOnUiThread(new Runnable() { + public void run() { + Toast.makeText(parent.getApplicationContext(), text, toastDuration).show(); + } + }); + } + public Object3DData getLightBulb() { return lightPoint; } @@ -149,11 +182,14 @@ public void toggleWireframe() { if (this.drawWireframe && !this.drawingPoints) { this.drawWireframe = false; this.drawingPoints = true; + makeToastText("Points", Toast.LENGTH_SHORT); } else if (this.drawingPoints){ this.drawingPoints = false; + makeToastText("Faces", Toast.LENGTH_SHORT); } else { + makeToastText("Wireframe", Toast.LENGTH_SHORT); this.drawWireframe = true; } requestRender(); @@ -187,10 +223,16 @@ public void toggleTextures() { public void toggleLighting() { if (this.drawLighting && this.rotatingLight){ this.rotatingLight = false; + makeToastText("Light stopped", Toast.LENGTH_SHORT); + } + else if (this.drawLighting && !this.rotatingLight){ + this.drawLighting = false; + makeToastText("Lightsoff", Toast.LENGTH_SHORT); } else { - this.drawLighting = !drawLighting; - rotatingLight = true; + this.drawLighting = true; + this.rotatingLight = true; + makeToastText("Light on", Toast.LENGTH_SHORT); } requestRender(); } diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/WavefrontLoader.java b/app/src/main/java/org/andresoviedo/app/model3D/services/WavefrontLoader.java index a057bac0..9c923160 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/services/WavefrontLoader.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/WavefrontLoader.java @@ -81,7 +81,7 @@ public class WavefrontLoader { // buffers private FloatBuffer vertsBuffer; private FloatBuffer normalsBuffer; - // TODO: load texture data directly into this buffer + // TODO: build texture data directly into this buffer private FloatBuffer textureCoordsBuffer; // flags @@ -122,6 +122,10 @@ public Materials getMaterials() { return materials; } + public ModelDimensions getDimensions() { + return modelDims; + } + /** * Count verts, normals, faces etc and reserve buffers to save the data. * @param br data source @@ -156,7 +160,7 @@ public void analyzeModel(InputStream is) { numFaces += (faceSize - 2); // (faceSize-2)x3 = converting polygon to triangles numVertsReferences += (faceSize - 2) * 3; - } else if (line.startsWith("mtllib ")) // load material + } else if (line.startsWith("mtllib ")) // build material { materials = new Materials(line.substring(7)); } else if (line.startsWith("usemtl ")) {// use material @@ -263,7 +267,7 @@ private void readModel(BufferedReader br) else if (line.startsWith("f ")) { // face isLoaded = faces.addFace(line) && isLoaded; numFaces++; - } else if (line.startsWith("mtllib ")) // load material + } else if (line.startsWith("mtllib ")) // build material { // materials = new Materials(new File(modelFile.getParent(), // line.substring(7)).getAbsolutePath()); @@ -334,7 +338,7 @@ private boolean addVert(FloatBuffer buffer, int offset, String line, boolean isF }catch(NumberFormatException ex){ Log.e("WavefrontLoader",ex.getMessage()); } finally{ - // try to load even with errors + // try to build even with errors buffer.put(offset, x).put(offset+1, y).put(offset+2, z); } @@ -394,39 +398,6 @@ private Tuple3 readTCTuple(String line) return null; // means an error occurred } // end of readTCTuple() - public void centerScale() - /* - * Position the model so it's center is at the origin, and scale it so its longest dimension is no bigger than - * maxSize. - */ - { - // get the model's center point - Tuple3 center = modelDims.getCenter(); - - // calculate a scale factor - float scaleFactor = 1.0f; - float largest = modelDims.getLargest(); - // System.out.println("Largest dimension: " + largest); - if (largest != 0.0f) - scaleFactor = (maxSize / largest); - Log.i("WavefrontLoader","Scaling model with factor: " + scaleFactor); - - // modify the model's vertices - float x0, y0, z0; - float x, y, z; - for (int i = 0; i < vertsBuffer.capacity()/3; i++) { - x0 = vertsBuffer.get(i*3); - y0 = vertsBuffer.get(i*3+1); - z0 = vertsBuffer.get(i*3+2); - x = (x0 - center.getX()) * scaleFactor; - vertsBuffer.put(i*3,x); - y = (y0 - center.getY()) * scaleFactor; - vertsBuffer.put(i*3+1,y); - z = (z0 - center.getZ()) * scaleFactor; - vertsBuffer.put(i*3+2,z); - } - } // end of centerScale() - public void reportOnModel() { Log.i("WavefrontLoader","No. of vertices: " + vertsBuffer.capacity()/3); Log.i("WavefrontLoader","No. of normal coords: " + normalsBuffer.capacity()/3); diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/Component.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/Component.java new file mode 100644 index 00000000..577668e7 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/Component.java @@ -0,0 +1,10 @@ +package org.andresoviedo.app.model3D.services.stl; + +import android.app.Activity; + +/** + * Created by andres on 17/04/17. + */ + +public class Component extends Activity { +} diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/I18nManager.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/I18nManager.java new file mode 100644 index 00000000..bf5ce2dc --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/I18nManager.java @@ -0,0 +1,18 @@ +package org.andresoviedo.app.model3D.services.stl; + +/** + * Created by andres on 17/04/17. + */ + +public class I18nManager { + + private static final I18nManager manager = new I18nManager(); + + public static I18nManager getManager() { + return manager; + } + + public String getString(String unknownKeywordMsgProp) { + return unknownKeywordMsgProp; + } +} diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/LittleEndianConverter.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/LittleEndianConverter.java new file mode 100644 index 00000000..c127f901 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/LittleEndianConverter.java @@ -0,0 +1,795 @@ +/***************************************************************************** + * LittleEndianConverter.java + * Java Source + * + * This source is licensed under the GNU LGPL v2.1. + * Please read http://www.gnu.org/copyleft/lgpl.html for more information. + * + * This software is not designed or intended for use in on-line control of + * aircraft, air traffic, aircraft navigation or aircraft communications; or in + * the design, construction, operation or maintenance of any nuclear + * facility. Licensee represents and warrants that it will not use or + * redistribute the Software for such purposes. + * + * Copyright (c) 2001, 2002 Dipl. Ing. P. Szawlowski + * University of Vienna, Dept. of Medical Computer Sciences + ****************************************************************************/ + +package org.andresoviedo.app.model3D.services.stl; + +import java.io.*; + +/** + * Utility to convert little endain data to big endian data. + *

+ * TODO: Extend to convert big endian to little endain data and write to + * OutputStream + * + * @author Dipl. Ing. Paul Szawlowski - + * University of Vienna, Dept. of Medical Computer Sciences + * @version $Revision: 1.3 $ + */ +public class LittleEndianConverter +{ + /** + * Converts little endian data in srcBuffer to big endian + * signed short (2 bytes long) data. + * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least 2. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @return (even) number of processed bytes of srcBuffer + */ + static public int convertToBigEndian + ( + final byte[ ] srcBuffer, + final short[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength + ) + { + return convertToBigEndian + ( + srcBuffer, + destBuffer, + srcLength, + destOffset, + destLength, + ( short )0xFF + ); + } + + /** + * Converts little endian data in srcBuffer to big endian + * short (2 bytes long) data. Significant bits can be masked, e. g. to + * get unsigned 7 bit values use 0x7F as mask. + * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least 2. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param mask Mask for significant bits. Set significant bits to 1. + * @return (even) number of processed bytes of srcBuffer + */ + static public int convertToBigEndian + ( + final byte[ ] srcBuffer, + final short[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength, + final short mask + ) + { + final int length = Math.min( destLength * 2, ( srcLength / 2 ) * 2 ); + for( int i = 0; i < length; i += 2 ) + { + final int tmp = + ( srcBuffer[ i ] & 0xFF | ( srcBuffer[ i + 1 ] << 8 ) ) & mask; + destBuffer[ ( i / 2 ) + destOffset ] = ( short )tmp; + } + return length; + } + + /** + * Converts little endian data in srcBuffer to big endian + * signed integer (4 bytes long) data. + * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least 4. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Maximum number of data to be written in + * destBuffer + * @return number of processed bytes of srcBuffer (multiple of 4 ) + */ + public static int convertToBigEndian + ( + final byte[ ] srcBuffer, + final int[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength + ) + { + return convertToBigEndian + ( + srcBuffer, + destBuffer, + srcLength, + destOffset, + destLength, + 0xFFFFFFFF + ); + } + + /** + * Converts little endian data in srcBuffer to big endian + * integer (4 bytes long) data. Significant bits can be masked, e. g. to + * get unsigned 31 bit values use 0x7FFFFFFF as mask. + * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least 4. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Maximum number of data to be written in + * destBuffer + * @param mask Mask for significant bits. Set significant bits to 1. + * @return number of processed bytes of srcBuffer (multiple of 4 ) + */ + public static int convertToBigEndian + ( + final byte[ ] srcBuffer, + final int[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength, + final int mask + ) + { + final int length = Math.min( destLength * 4, ( srcLength / 4 ) * 4 ); + for( int i = 0; i < length; i += 4 ) + { + destBuffer[ ( i / 4 ) + destOffset ] = ( srcBuffer[ i ] & 0xFF + | ( srcBuffer[ i + 1 ] << 8 ) & 0xFF00 + | ( srcBuffer[ i + 2 ] << 16 ) & 0xFF0000 + | ( srcBuffer[ i + 3 ] << 24 ) ) & mask; + } + return length; + } + + /** + * Converts little endian data in srcBuffer to big endian + * signed integer data with a user defined block size of 2, 3, or 4 bytes. + *

+ * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least blockSize. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Maximum number of data to be written in + * destBuffer + * @param blockSize May be 2, 3 or 4. + * @return number of processed bytes of srcBuffer (multiple of + * blockSize) + */ + public static int convertToBigEndian + ( + final int blockSize, + final byte[ ] srcBuffer, + final int[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength + ) + { + return convertToBigEndian + ( + blockSize, + srcBuffer, + destBuffer, + srcLength, + destOffset, + destLength, + 0xFFFFFFFF + ); + } + + /** + * Converts little endian data in srcBuffer to big endian + * signed integer data with a user defined block size of 2, 3, or 4 bytes. + * Significant bits can be masked, e. g. to get unsigned 16 bit values use + * 0xFFFF as mask.

+ * @param blockSize May be 2, 3 or 4. + * @param srcBuffer Data in little endian format which shall be converted. + * The size of the array must be at least blockSize. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param srcLength Number of bytes of srcBuffer which shall + * be processed. Must be <= length of srcBuffer. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Maximum number of data to be written in + * destBuffer + * @param mask Mask for significant bits. Set significant bits to 1. + * @return number of processed bytes of srcBuffer (multiple of + * blockSize) + */ + public static int convertToBigEndian + ( + final int blockSize, + final byte[ ] srcBuffer, + final int[ ] destBuffer, + final int srcLength, + final int destOffset, + final int destLength, + final int mask + ) + { + final int length = Math.min + ( + destLength * blockSize, + ( srcLength / blockSize ) * blockSize + ); + if( blockSize == 2 ) + { + for( int i = 0; i < length; i += 2 ) + { + destBuffer[ ( i / 2 ) + destOffset ] = + ( srcBuffer[ i ] & 0xFF | ( srcBuffer[ i + 1 ] << 8 ) ) + & mask; + } + return length; + } + else if( blockSize == 3 ) + { + for( int i = 0; i < length; i += 3 ) + { + destBuffer[ ( i / 3 ) + destOffset ] = ( srcBuffer[ i ] & 0xFF + | ( srcBuffer[ i + 1 ] << 8 ) & 0xFF00 + | ( srcBuffer[ i + 2 ] << 24 ) ) & mask; + } + return length; + } + else if( blockSize == 4 ) + { + return convertToBigEndian + ( + srcBuffer, + destBuffer, + srcLength, + destOffset, + destLength, + mask + ); + } + else + { + return 0; + } + } + + /** + * Reads little endian data from an InputStream and converts + * it to big endian signed short (2 bytes long) data. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 2) + * depending on the size of the stream. The size of the array must be + * at least 2. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final byte[ ] readBuffer, + final short[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream + ) + throws IOException + { + return read + ( + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + ( short )0xFF + ); + } + /** + * Reads little endian data from an InputStream and converts + * it to big endian short (2 bytes long) data. Significant bits can be + * masked, e. g. to get unsigned 7 bit values use 0x7F as mask. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 2) + * depending on the size of the stream. The size of the array must be + * at least 2. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @param mask Mask for significant bits. Set significant bits to 1. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final byte[ ] readBuffer, + final short[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream, + final short mask + ) + throws IOException + { + int numOfBytesRead = 0; + int numOfData = 0; + int offset = 0; + final int length = ( readBuffer.length / 2 ) * 2; + while( ( numOfBytesRead >= 0 ) && ( numOfData < destLength ) ) + { + // calculate how many more bytes can be read so that destBuffer + // does not overflow; enables to continue reading from same stream + // without data loss + final int maxBytesToRead = + Math.min( ( destLength - numOfData ) * 2, length ); + numOfBytesRead = + stream.read( readBuffer, offset, maxBytesToRead - offset ); + int numOfProcessedBytes = convertToBigEndian + ( + readBuffer, + destBuffer, + numOfBytesRead + offset, + destOffset + numOfData, + destLength - numOfData, + mask + ); + // if an uneven number of bytes was read from stream + if( numOfBytesRead - numOfProcessedBytes == 1 ) + { + offset = 1; + readBuffer[ 0 ] = readBuffer[ numOfProcessedBytes ]; + } + else + { + offset = 0; + } + numOfData += ( numOfProcessedBytes / 2 ); + } + return numOfData; + } + + /** + * Reads little endian data from an InputStream and converts + * it to big endian signed int (4 bytes long) data. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 4) + * depending on the size of the stream. The size of the array must be + * at least 4. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream + ) + throws IOException + { + return read + ( + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + 0xFFFFFFFF + ); + } + + /** + * Reads little endian data from an InputStream and converts + * it to big endian int (4 bytes long) data. Significant bits can be masked, + * e. g. to get unsigned 31 bit values use 0x7FFFFFFF as mask. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 4) + * depending on the size of the stream. The size of the array must be + * at least 4. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @param mask Mask for significant bits. Set significant bits to 1. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream, + final int mask + ) + throws IOException + { + int numOfBytesRead = 0; + int numOfData = 0; + int offset = 0; + final int length = ( readBuffer.length / 4 ) * 4; + while( ( numOfBytesRead >= 0 ) && ( numOfData < destLength ) ) + { + // calculate how many more bytes can be read so that destBuffer + // does not overflow; enables to continue reading from same stream + // without data loss + final int maxBytesToRead = + Math.min( ( destLength - numOfData ) * 4, length ); + numOfBytesRead = + stream.read( readBuffer, offset, maxBytesToRead - offset ); + int numOfProcessedBytes = convertToBigEndian + ( + readBuffer, + destBuffer, + numOfBytesRead + offset, + destOffset + numOfData, + destLength - numOfData, + mask + ); + final int diff = numOfBytesRead - numOfProcessedBytes; + // if an number of bytes was read from stream was not a multiple + // of 4 + offset = 0; + if(diff == 1 ) + { + offset = 1; + readBuffer[ 0 ] = readBuffer[ numOfProcessedBytes ]; + } + if( diff == 2 ) + { + offset = 2; + readBuffer[ 1 ] = readBuffer[ numOfProcessedBytes + 1 ]; + } + if( diff == 3 ) + { + offset = 3; + readBuffer[ 2 ] = readBuffer[ numOfProcessedBytes + 2 ]; + } + numOfData += ( numOfProcessedBytes / 4 ); + } + return numOfData; + } + + /** + * Reads little endian data from an InputStream and converts + * it to to big endian signed integer data with a user defined block size + * of 1, 2, 3, or 4 bytes (1 is here for conveniance).

+ * @param blockSize May be 1, 2, 3 or 4. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 4) + * depending on the size of the stream. The size of the array must be + * at least blockSize. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final int blockSize, + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream + ) + throws IOException + { + return read + ( + blockSize, + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + 0xFFFFFFFF + ); + } + + /** + * Reads little endian data from an InputStream and converts + * it to to big endian signed integer data with a user defined block size + * of 1, 2, 3, or 4 bytes (1 is here for conveniance). Significant bits can + * be masked, e. g. to get unsigned 16 bit values use 0xFFFF + * as mask.

+ * @param blockSize May be 1, 2, 3 or 4. + * @param readBuffer Auxilary Buffer to be used to read from + * stream. Choose an appropriate size (multiple of 4) + * depending on the size of the stream. The size of the array must be + * at least blockSize. + * @param destBuffer Buffer to store the converted data. The size of the + * array must be at least destOffset + + * destLength. + * @param destOffset Offset for writing converted data in + * destBuffer. + * @param destLength Max. number of data to be written in + * destBuffer + * @param stream InputStream to read from. + * @param mask Mask for significant bits. Set significant bits to 1. + * @return number of data elements written in destBuffer + * (will be <= destLength). + */ + public static int read + ( + final int blockSize, + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream, + final int mask + ) + throws IOException + { + if( blockSize == 2 ) + { + return read2ByteBlock + ( + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + mask + ); + } + else if( blockSize == 3 ) + { + return read3ByteBlock + ( + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + mask + ); + } + else if( blockSize == 4 ) + { + return read + ( + readBuffer, + destBuffer, + destOffset, + destLength, + stream, + mask + ); + } + else + { + return 0; + } + } + + /** + * Reads 4 bytes in little endian format and converts it to a signed int.

+ * @throws IOException if EOF occurs and only one, 2 or 3 bytes were read or + * if error during reading occurs + */ + public static int read4ByteBlock( final InputStream stream ) + throws java.io.IOException + { + return read( stream ) & 0xFF + | ( read( stream ) << 8 ) & 0xFF00 + | ( read( stream ) << 16 ) & 0xFF0000 + | ( read( stream ) << 24 ); + } + + /** + * Reads 2 bytes in little endian format and converts it to a signed int.

+ * To Convert it to an unsigned int & the result with + * 0xFFFF. + * @throws IOException if EOF occurs and only one bytes was read or + * if error during reading occurs + */ + public static int read2ByteBlock( final InputStream stream ) + throws java.io.IOException + { + return read( stream ) & 0xFF + | ( read( stream ) << 8 ); + } + + /** + * Reads 3 bytes in little endian format and converts it to a signed int.

+ * To Convert it to an unsigned int & the result with + * 0xFFFFFF. + * @throws IOException if EOF occurs and only one or 2 bytes were read or + * if error during reading occurs + */ + public static int read3ByteBlock( final InputStream stream ) + throws java.io.IOException + { + return read( stream ) & 0xFF + | ( read( stream ) << 8 ) & 0xFF00 + | ( read( stream ) << 16 ); + } + + private static int read2ByteBlock + ( + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream, + final int mask + ) + throws IOException + { + int numOfBytesRead = 0; + int numOfData = 0; + int offset = 0; + final int length = ( readBuffer.length / 2 ) * 2; + while( ( numOfBytesRead >= 0 ) && ( numOfData < destLength ) ) + { + // calculate how many more bytes can be read so that destBuffer + // does not overflow; enables to continue reading from same stream + // without data loss + final int maxBytesToRead = + Math.max( ( destLength - numOfData ) * 2, length ); + numOfBytesRead = + stream.read( readBuffer, offset, maxBytesToRead - offset ); + int numOfProcessedBytes = convertToBigEndian + ( + 2, + readBuffer, + destBuffer, + numOfBytesRead + offset, + destOffset + numOfData, + destLength - numOfData, + mask + ); + // if an uneven number of bytes was read from stream + if( numOfBytesRead - numOfProcessedBytes == 1 ) + { + offset = 1; + readBuffer[ 0 ] = readBuffer[ numOfProcessedBytes ]; + } + else + { + offset = 0; + } + numOfData += ( numOfProcessedBytes / 2 ); + } + return numOfData; + } + + private static int read3ByteBlock + ( + final byte[ ] readBuffer, + final int[ ] destBuffer, + final int destOffset, + final int destLength, + final InputStream stream, + final int mask + ) + throws IOException + { + int numOfBytesRead = 0; + int numOfData = 0; + int offset = 0; + final int length = ( readBuffer.length / 3 ) * 3; + while( ( numOfBytesRead >= 0 ) && ( numOfData < destLength ) ) + { + // calculate how many more bytes can be read so that destBuffer + // does not overflow; enables to continue reading from same stream + // without data loss + final int maxBytesToRead = + Math.max( ( destLength - numOfData ) * 3, length ); + numOfBytesRead = + stream.read( readBuffer, offset, maxBytesToRead - offset ); + int numOfProcessedBytes = convertToBigEndian + ( + 3, + readBuffer, + destBuffer, + numOfBytesRead + offset, + destOffset + numOfData, + destLength - numOfData, + mask + ); + final int diff = numOfBytesRead - numOfProcessedBytes; + // if an number of bytes was read from stream was not a multiple + // of 3 + offset = 0; + if(diff == 1 ) + { + offset = 1; + readBuffer[ 0 ] = readBuffer[ numOfProcessedBytes ]; + } + if( diff == 2 ) + { + offset = 2; + readBuffer[ 1 ] = readBuffer[ numOfProcessedBytes + 1 ]; + } + numOfData += ( numOfProcessedBytes / 3 ); + } + return numOfData; + } + + private static int read( final InputStream stream ) throws IOException + { + final int tempValue = stream.read( ); + if( tempValue == -1 ) + { + throw new IOException( "Filesize does not match blocksize" ); + } + return tempValue; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLASCIIParser.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLASCIIParser.java new file mode 100644 index 00000000..f73de194 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLASCIIParser.java @@ -0,0 +1,626 @@ +/***************************************************************************** + * STLASCIIParser.java + * Java Source + * + * This source is licensed under the GNU LGPL v2.1. + * Please read http://www.gnu.org/copyleft/lgpl.html for more information. + * + * Copyright (c) 2002 Dipl. Ing. P. Szawlowski + * University of Vienna, Dept. of Medical Computer Sciences + ****************************************************************************/ + +package org.andresoviedo.app.model3D.services.stl; + +// External imports +import android.content.res.Resources; + +import org.andresoviedo.app.util.io.ProgressMonitorInputStream; + +import java.io.*; +import java.util.*; + +import java.net.URL; +import java.net.URLConnection; + +// Internal imports + +/** + * Class to parse STL (stereolithography) files in ASCII format.

+ * + *

+ * Internationalisation Resource Names + *

+ *

    + *
  • invalidKeywordMsg: Unknown keyword encountered.
  • + *
  • emptyFileMsg: File contained the header but no content.
  • + *
  • invalidDataMsg: Some strange data was encountered.
  • + *
  • unexpectedEofMsg: We hit an EOF before we were expecting to.
  • + *
+ * + * @see STLFileReader + * @see STLLoader + * @author Dipl. Ing. Paul Szawlowski - + * University of Vienna, Dept of Medical Computer Sciences + * @version $Revision: 2.0 $ + */ +class STLASCIIParser extends STLParser +{ + /** Error message of a keyword that we don't recognise */ + private static final String UNKNOWN_KEYWORD_MSG_PROP = + "org.j3d.loaders.stl.STLASCIIParser.invalidKeywordMsg"; + + /** + * Error message when the solid header is found, but there is no + * geometry after it. Basically an empty file. + */ + private static final String EMPTY_FILE_MSG_PROP = + "org.j3d.loaders.stl.STLASCIIParser.emptyFileMsg"; + + /** Unexpected data is encountered during parsing */ + private static final String INVALID_NORMAL_DATA_MSG_PROP = + "org.j3d.loaders.stl.STLASCIIParser.invalidNormalDataMsg"; + + /** Unexpected data is encountered during parsing */ + private static final String INVALID_VERTEX_DATA_MSG_PROP = + "org.j3d.loaders.stl.STLASCIIParser.invalidVertexDataMsg"; + + /** Unexpected EOF is encountered during parsing */ + private static final String EOF_WTF_MSG_PROP = + "org.j3d.loaders.stl.STLASCIIParser.unexpectedEofMsg"; + + /** Reader for the main stream */ + private BufferedReader itsReader; + + /** The line number that we're at in the file */ + private int lineCount; + + /** + * Create a new default parser instance. + */ + public STLASCIIParser() + { + } + + + /** + * Create a new default parser instance. + */ + public STLASCIIParser(boolean strict) + { + super(strict); + + } + + /** + * Finish the parsing off now. + */ + public void close() throws IOException + { + if(itsReader != null) + itsReader.close(); + } + + /** + * Fetch a single face from the stream + * + * @param normal Array length 3 to copy the normals in to + * @param vertices A [3][3] array for each vertex + * @throws IllegalArgumentException The file was structurally incorrect + * @throws IOException Something happened during the reading + */ + public boolean getNextFacet(double[] normal, double[][] vertices) + throws IOException + { + // format of a triangle is: + // + // facet normal number number number + // outer loop + // vertex number number number + // vertex number number number + // vertex number number number + // end loop + // endfacet + + // First line with normals + String input_line = readLine(); + + if (input_line == null) { + return false; + } + + StringTokenizer strtok = new StringTokenizer(input_line); + String token = strtok.nextToken(); + + // are we the first line of the file? If so, skip it + if(token.equals("solid")) + { + input_line = readLine(); + strtok = new StringTokenizer(input_line); + token = strtok.nextToken(); + lineCount = 1; + } + + // Have we reached the end of file? + // We've encountered a lot of broken files where they use two words + // "end solid" rather than the spec-required "endsolid". + if(token.equals("endsolid") || input_line.contains("end solid")) { + // Skip line and read next + try { + return getNextFacet(normal, vertices); + } catch(IOException ioe) { + // gone past end of file + return false; + } + } + + if(!token.equals("facet")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount + " word: " + token; + throw new IllegalArgumentException(msg); + } + + token = strtok.nextToken(); + if(!token.equals("normal")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + readNormal(strtok, normal); + + // Skip the outer loop line + input_line = readLine(); + + if (input_line == null) { + return false; + } + + strtok = new StringTokenizer(input_line); + token = strtok.nextToken(); + lineCount++; + + if(!token.equals("outer")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + token = strtok.nextToken(); + if(!token.equals("loop")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + // Next 3x vertex reads + for(int i = 0; i < 3; i++) { + input_line = readLine(); + strtok = new StringTokenizer(input_line); + lineCount++; + + token = strtok.nextToken(); + + if(!token.equals("vertex")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + readCoordinate(strtok, vertices[i]); + } + + // Read and skip the endloop && endfacet lines + + input_line = readLine(); + if (input_line == null) { + return false; + } + + strtok = new StringTokenizer(input_line); + token = strtok.nextToken(); + lineCount++; + + if(!token.equals("endloop")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + input_line = readLine(); + if (input_line == null) { + return false; + } + + strtok = new StringTokenizer(input_line); + token = strtok.nextToken(); + lineCount++; + + if(!token.equals("endfacet")) + { + close(); + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + ": " + + lineCount; + throw new IllegalArgumentException(msg); + } + + return true; + } + + /** + * @throws IllegalArgumentException The file was structurally incorrect + */ + public boolean parse(URL url, Component parentComponent) + throws InterruptedIOException, IOException + { + InputStream stream = null; + try + { + stream = url.openStream(); + } + catch(IOException e) + { + if(stream != null) + stream.close(); + + throw e; + } + + stream = new ProgressMonitorInputStream( + parentComponent, "analyzing " + url.toString(), stream); + + BufferedReader reader = + new BufferedReader(new InputStreamReader(stream)); + + boolean isAscii = false; + + try + { + isAscii = parse(reader); + } + finally + { + reader.close(); + } + + if(!isAscii) + return false; + + try + { + stream = url.openStream(); + } + catch(IOException e) + { + stream.close(); + throw e; + } + + stream = new ProgressMonitorInputStream ( + parentComponent, + "parsing " + url.toString(), + stream); + + reader = new BufferedReader(new InputStreamReader(stream)); + itsReader = reader; + + return true; + } + + /** + * @throws IllegalArgumentException The file was structurally incorrect + */ + public boolean parse(URL url) + throws IOException + { + InputStream stream = null; + try { + stream = url.openStream(); + } + catch(IOException e) + { + if(stream != null) + stream.close(); + + throw e; + } + + BufferedReader reader = + new BufferedReader(new InputStreamReader(stream)); + boolean isAscii = false; + + try + { + isAscii = parse(reader); + } + catch(InterruptedIOException e) + { + // should never happen + e.printStackTrace(); + } + finally + { + reader.close(); + } + + if(!isAscii) + return false; + + try + { + stream = url.openStream(); + } + catch(IOException e) + { + stream.close(); + throw e; + } + + reader = new BufferedReader(new InputStreamReader(stream)); + itsReader = reader; + + return true; + } + + /** + * Parse the stream now from the given reader. + * + * @param reader The reader to source the file from + * @return true if this is a ASCII format file, false if not + * @throws IllegalArgumentException The file was structurally incorrect + * @throws IOException Something happened during the reading + */ + private boolean parse(BufferedReader reader) + throws IOException, IllegalArgumentException + { + int numOfObjects = 0; + int numOfFacets = 0; + ArrayList facetsPerObject = new ArrayList(10); + ArrayList names = new ArrayList(10); + boolean isAscii = true; + + + itsReader = reader; + String line = readLine(); + int line_count = 1; + + line = line.trim(); // "Spec" says whitespace maybe anywhere except within numbers or words. Great design! + + // check if ASCII format + if(!line.startsWith("solid")) + { + return false; + } + else + { + if(line.length() > 6) + names.add(line.substring(6)); + else + names.add(null); + } + + line = readLine(); + + if(line == null) + { + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(EMPTY_FILE_MSG_PROP); + throw new IllegalArgumentException(msg); + } + + while(line != null) + { + line_count++; + + if(line.indexOf("facet") >= 0) + { + numOfFacets ++; + // skip next 6 lines: + // outer loop, 3 * vertex, endloop, endfacet + for(int i = 0; i < 6; i ++) + { + readLine(); + } + + line_count += 6; + } + + // watch order of if: solid contained also in endsolid + // JC: We have found a lot of badly formatted STL files generated + // from some program that incorrectly end a solid object with a + // space between end and solid. Deal with that here. + else if((line.indexOf("endsolid") >= 0) || + (line.indexOf("end solid") >= 0)) + { + facetsPerObject.add(new Integer(numOfFacets)); + numOfFacets = 0; + numOfObjects++; + } + else if(line.indexOf("solid") >= 0) + { + line = line.trim(); + + if(line.length() > 6) + names.add(line.substring(6)); + } + else + { + line = line.trim(); + if(line.length() != 0) { + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(UNKNOWN_KEYWORD_MSG_PROP) + + ": " + lineCount; + + throw new IllegalArgumentException(msg); + } + } + + line = readLine(); + } + + if (numOfFacets > 0 && numOfObjects == 0) { + numOfObjects = 1; + facetsPerObject.add(new Integer(numOfFacets)); + } + + itsNumOfObjects = numOfObjects; + itsNumOfFacets = new int[numOfObjects]; + itsNames = new String[numOfObjects]; + + for(int i = 0; i < numOfObjects; i ++) + { + Integer num = (Integer)facetsPerObject.get(i); + itsNumOfFacets[i] = num.intValue(); + + itsNames[i] = (String)names.get(i); + } + + return true; + } + + /** + * Read three numbers from the tokeniser and place them in the double value + * returned. + */ + private void readNormal(StringTokenizer strtok, double[] vector) + throws IOException + { + boolean error_found = false; + + for(int i = 0; i < 3; i ++) + { + String num_str = strtok.nextToken(); + + try + { + vector[i] = Double.parseDouble(num_str); + } + catch(NumberFormatException e) + { + if (!strictParsing) + { + error_found = true; + continue; + } + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(INVALID_NORMAL_DATA_MSG_PROP) + + num_str; + throw new IllegalArgumentException(msg); + } + + } + + if (error_found) { + // STL spec says use 0 0 0 for autocalc + vector[0] = 0; + vector[1] = 0; + vector[2] = 0; + } + } + + /** + * Read three numbers from the tokeniser and place them in the double value + * returned. + */ + private void readCoordinate(StringTokenizer strtok, double[] vector) + throws IOException + { + for(int i = 0; i < 3; i ++) + { + String num_str = strtok.nextToken(); + + boolean error_found = false; + + try + { + vector[i] = Double.parseDouble(num_str); + } + catch(NumberFormatException e) + { + if (strictParsing) + { + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(INVALID_VERTEX_DATA_MSG_PROP) + + ": Cannot parse vertex: " + num_str; + throw new IllegalArgumentException(msg); + } else { + // Common error is to use commas instead of . in Europe + String new_str = num_str.replace(",","."); + + try + { + vector[i] = Double.parseDouble(new_str); + } + catch(NumberFormatException e2) + { + + I18nManager intl_mgr = I18nManager.getManager(); + + String msg = intl_mgr.getString(INVALID_VERTEX_DATA_MSG_PROP) + + ": Cannot parse vertex: " + num_str; + throw new IllegalArgumentException(msg); + } + } + } + } + } + + /** + * Read a line from the input. Ignore whitespace. + */ + private String readLine() throws IOException { + String input_line = ""; + + while(input_line.length() == 0) { + input_line = itsReader.readLine(); + + if (input_line == null) { + break; + } + + if (input_line.length() > 0 && Character.isWhitespace(input_line.charAt(0))) { + input_line = input_line.trim(); + } + } + + return input_line; + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLBinaryParser.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLBinaryParser.java new file mode 100644 index 00000000..a4a52244 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLBinaryParser.java @@ -0,0 +1,234 @@ +/***************************************************************************** + * STLBinaryParser.java + * Java Source + * + * This source is licensed under the GNU LGPL v2.1. + * Please read http://www.gnu.org/copyleft/lgpl.html for more information. + * + * Copyright (c) 2002 Dipl. Ing. P. Szawlowski + * University of Vienna, Dept. of Medical Computer Sciences + ****************************************************************************/ + +package org.andresoviedo.app.model3D.services.stl; + +// External imports +import org.andresoviedo.app.util.io.ProgressMonitorInputStream; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; + +// Local imports + +/** + * Class to parse STL (stereolithography) files in binary format.

+ * @see STLFileReader + * @see STLLoader + * @author Dipl. Ing. Paul Szawlowski - + * University of Vienna, Dept of Medical Computer Sciences + * @version $Revision: 1.3 $ + */ +class STLBinaryParser extends STLParser +{ + /** size of binary header */ + private static int HEADER_SIZE = 84; + + /** size of one facet record in binary format */ + private static int RECORD_SIZE = 50; + + /** size of comments in header */ + private static int COMMENT_SIZE = 80; + + /** The stream that is being read from */ + private BufferedInputStream itsStream; + + /** Common buffer for reading */ + private byte[] itsReadBuffer; + + /** Common buffer for reading the converted data from bytes */ + private int[] itsDataBuffer; + + public STLBinaryParser() + { + itsReadBuffer = new byte[48]; + itsDataBuffer = new int[12]; + } + + /** + * Constructor. + * + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + */ + public STLBinaryParser(boolean strict) + { + super(strict); + + itsReadBuffer = new byte[48]; + itsDataBuffer = new int[12]; + } + + public void close() throws IOException + { + if(itsStream != null) + { + itsStream.close(); + } + } + + public boolean parse(URL url) + throws IllegalArgumentException, IOException + { + InputStream stream = null; + int length = -1; + try + { + URLConnection connection = url.openConnection(); + stream = connection.getInputStream(); + length = connection.getContentLength(); + } + catch(IOException e) + { + if(stream != null) + { + stream.close(); + } + } + itsStream = new BufferedInputStream(stream); + return parse(length); + } + + public boolean parse(URL url, Component parentComponent) + throws IllegalArgumentException, IOException + { + InputStream stream = null; + int length = -1; + try + { + URLConnection connection = url.openConnection(); + stream = connection.getInputStream(); + length = connection.getContentLength(); + } + catch(IOException e) + { + if(stream != null) + { + stream.close(); + } + } + stream = new ProgressMonitorInputStream( + parentComponent, + "parsing " + url.toString(), + stream); + + itsStream = new BufferedInputStream(stream); + return parse(length); + } + + /** + * Internal convenience method that does the stream parsing regardless of + * the input source the stream came from. Assumes itsStream is already + * initialised before it called here. + * + * @param length The length of data from the incoming stream, if not. Use + * -1 not known. + * @return true if the method does not work out + */ + private boolean parse(int length) + throws IllegalArgumentException, IOException + { + try + { + // skip header until number of facets info + for(int i = 0; i < COMMENT_SIZE; i ++) + itsStream.read(); + + // binary file contains only on object + itsNumOfObjects = 1; + itsNumOfFacets = + new int[]{ LittleEndianConverter.read4ByteBlock(itsStream) }; + itsNames = new String[1]; + // if length of file is known, check if it matches with the content + // binary file contains only on object + if(strictParsing && length != -1 && + length != itsNumOfFacets[0] * RECORD_SIZE + HEADER_SIZE) + { + String msg = "File size does not match the expected size for" + + " the given number of facets. Given " + + itsNumOfFacets[0] + " facets for a total size of " + + (itsNumOfFacets[0] * RECORD_SIZE + HEADER_SIZE) + + " but the file size is " + length; + close(); + + throw new IllegalArgumentException(msg); + } else if (!strictParsing && length != -1 && + length != itsNumOfFacets[0] * RECORD_SIZE + HEADER_SIZE) { + + String msg = "File size does not match the expected size for" + + " the given number of facets. Given " + + itsNumOfFacets[0] + " facets for a total size of " + + (itsNumOfFacets[0] * RECORD_SIZE + HEADER_SIZE) + + " but the file size is " + length; + + if (parsingMessages == null) { + parsingMessages = new ArrayList(); + } + parsingMessages.add(msg); + } + } + catch(IOException e) + { + close(); + throw e; + } + return false; + } + + /** + * Read the next face from the underlying stream + * + * @return true if the read completed successfully + */ + public boolean getNextFacet(double[] normal, double[][] vertices) + throws IOException + { + LittleEndianConverter.read(itsReadBuffer, + itsDataBuffer, + 0, + 12, + itsStream); + + boolean nan_found = false;; + + for(int i = 0; i < 3; i ++) + { + normal[i] = Float.intBitsToFloat(itsDataBuffer[i]); + if (Double.isNaN(normal[i]) || Double.isInfinite(normal[i])) { + nan_found = true; + } + } + + if (nan_found) + { + // STL spec says use 0 0 0 for autocalc + normal[0] = 0; + normal[1] = 0; + normal[2] = 0; + } + + for(int i = 0; i < 3; i ++) + { + for(int j = 0; j < 3; j ++) + { + vertices[i][j] = + Float.intBitsToFloat(itsDataBuffer[i * 3 + j + 3]); + } + } + + // skip last 2 padding bytes + itsStream.read(); + itsStream.read(); + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLFileReader.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLFileReader.java new file mode 100644 index 00000000..69905dea --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLFileReader.java @@ -0,0 +1,324 @@ +/***************************************************************************** + * STLFileReader.java + * Java Source + * + * This source is licensed under the GNU LGPL v2.1. + * Please read http://www.gnu.org/copyleft/lgpl.html for more information. + * + * Copyright (c) 2001, 2002 Dipl. Ing. P. Szawlowski + * University of Vienna, Dept. of Medical Computer Sciences + ****************************************************************************/ + +package org.andresoviedo.app.model3D.services.stl; + +// External Imports +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; + +// Local imports + +/** + * Class to read STL (Stereolithography) files.

+ * Usage: First create a STLFileReader object. To obtain the number + * of objects, name of objects and number of facets for each object use the + * appropriate methods. Then use the {@link #getNextFacet} method repetitively + * to obtain the geometric data for each facet. Call {@link #close} to free the + * resources.

+ * In case that the file uses the binary STL format, no check can be done to + * assure that the file is in STL format. A wrong format will only be + * recognized if an invalid amount of data is contained in the file.

+ * + * @author Dipl. Ing. Paul Szawlowski - + * University of Vienna, Dept. of Medical Computer Sciences + * @version $Revision: 1.3 $ + */ +public class STLFileReader +{ + private STLParser itsParser; + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. + * @param file File object of STL file to read. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(File file) + throws IllegalArgumentException, IOException + { + this(file.toURL()); + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. + * @param fileName Name of STL file to read. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(String fileName) + throws IllegalArgumentException, IOException + { + this(new URL(fileName)); + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. + * @param fileName Name of STL file to read. + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(String fileName, boolean strict) + throws IllegalArgumentException, IOException + { + this(new URL(fileName), strict); + } + + /** + * Creates a STLFileReader object to read a STL file from an + * URL. The data may be in ASCII or binary format. + * @param url URL of STL file to read. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(URL url) + throws IllegalArgumentException, IOException + { + final STLASCIIParser asciiParser = new STLASCIIParser(); + + if(asciiParser.parse(url)) + { + itsParser = asciiParser; + } + else + { + final STLBinaryParser binParser = new STLBinaryParser(); + binParser.parse(url); + itsParser = binParser; + } + } + + /** + * Creates a STLFileReader object to read a STL file from an + * URL. The data may be in ASCII or binary format. + * @param url URL of STL file to read. + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(URL url, boolean strict) + throws IllegalArgumentException, IOException + { + + final STLParser asciiParser = new STLASCIIParser(strict); + + if(asciiParser.parse(url)) + { + itsParser = asciiParser; + } + else + { + final STLBinaryParser binParser = new STLBinaryParser(strict); + binParser.parse(url); + itsParser = binParser; + } + } + + + /** + * Creates a STLFileReader object to read a STL file from an + * URL. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param url URL of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(URL url, Component parentComponent) + throws IllegalArgumentException, IOException + { + final STLASCIIParser asciiParser = new STLASCIIParser(); + if(asciiParser.parse(url, parentComponent)) + { + itsParser = asciiParser; + } + else + { + final STLBinaryParser binParser = new STLBinaryParser(); + binParser.parse(url, parentComponent); + itsParser = binParser; + } + } + + /** + * Creates a STLFileReader object to read a STL file from an + * URL. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param url URL of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(URL url, Component parentComponent, boolean strict) + throws IllegalArgumentException, IOException + { + final STLASCIIParser asciiParser = new STLASCIIParser(strict); + if(asciiParser.parse(url, parentComponent)) + { + itsParser = asciiParser; + } + else + { + final STLBinaryParser binParser = new STLBinaryParser(strict); + binParser.parse(url, parentComponent); + itsParser = binParser; + } + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param file File object of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(File file, Component parentComponent) + throws IllegalArgumentException, IOException + { + this(file.toURL(), parentComponent); + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param file File object of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader(File file, Component parentComponent, boolean strict) + throws IllegalArgumentException, IOException + { + this(file.toURL(), parentComponent, strict); + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param fileName Name of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader (String fileName, Component parentComponent) + throws IllegalArgumentException, IOException + { + this(new URL(fileName), parentComponent); + } + + /** + * Creates a STLFileReader object to read a STL file from a + * file. The data may be in ASCII or binary format. A progress monitor will + * show the progress during reading. + * @param fileName Name of STL file to read. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public STLFileReader (String fileName, Component parentComponent, boolean strict) + throws IllegalArgumentException, IOException + { + this(new URL(fileName), parentComponent, strict); + } + + /** + * Returns the data for a facet. The orientation of the facets (which way + * is out and which way is in) is specified redundantly. First, the + * direction of the normal is outward. Second, the vertices are listed in + * counterclockwise order when looking at the object from the outside + * (right-hand rule).

+ * Call consecutively until all data is read. + * @param normal array of size 3 to store the normal vector. + * @param vertices array of size 3x3 to store the vertex data. + *

    + *
  • first index: vertex + *
  • second index: + *
      + *
    • 0: x coordinate + *
    • 1: y coordinate + *
    • 2: z coordinate + *
    + *
+ * @return True if facet data is contained in + * normal and vertices. False + * if end of file is reached. Further calls of this method after + * the end of file is reached will lead to an IOException. + * @throws IllegalArgumentException The file was structurally incorrect + */ + public boolean getNextFacet(double[ ] normal, double[ ][ ] vertices) + throws IllegalArgumentException, IOException + { + return itsParser.getNextFacet(normal, vertices); + } + + /** + * Get array with object names. + * @return Array of strings with names of objects. Size of array = number + * of objects in file. If name is not contained then the appropriate + * string is null. + */ + public String[] getObjectNames() + { + return itsParser.getObjectNames(); + } + + /** + * Get number of facets per object. + * @return Array with the number of facets per object. Size of array = + * number of objects in file. + */ + public int[] getNumOfFacets() + { + return itsParser.getNumOfFacets(); + } + + /** + * Get detailed messages on what was wrong when parsing. Only can happen + * when strictParsing is false. Means things like getNumOfFacets might + * be larger then reality. + */ + public List getParsingMessages() + { + return itsParser.getParsingMessages(); + } + + /** + * Get number of objects in file. + */ + public int getNumOfObjects() + { + return itsParser.getNumOfObjects(); + } + + /** + * Releases used resources. Must be called after finishing reading. + */ + public void close() throws IOException + { + if(itsParser != null) + { + itsParser.close(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLLoader.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLLoader.java new file mode 100644 index 00000000..49d62b0e --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLLoader.java @@ -0,0 +1,132 @@ +package org.andresoviedo.app.model3D.services.stl; + +import android.app.Activity; +import android.opengl.GLES20; +import android.util.Log; + +import org.andresoviedo.app.model3D.controller.LoaderTask; +import org.andresoviedo.app.model3D.model.Object3DBuilder; +import org.andresoviedo.app.model3D.model.Object3DData; +import org.andresoviedo.app.model3D.services.WavefrontLoader; + +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + + STL loader supported by the org.j3d STL parser + * + * @author andresoviedo + */ +public final class STLLoader { + + private static ByteBuffer createNativeByteBuffer(int length) { + // initialize vertex byte buffer for shape coordinates + ByteBuffer bb = ByteBuffer.allocateDirect(length); + // use the device hardware's native byte order + bb.order(ByteOrder.nativeOrder()); + return bb; + } + + public static void loadSTLAsync(final Activity parent, URL url, final Object3DBuilder.Callback callback) { + new STLLoaderTask(parent,url,callback).execute(); + } + + private static class STLLoaderTask extends LoaderTask { + + STLFileReader stlFileReader; + + STLLoaderTask(Activity parent, URL url, Object3DBuilder.Callback callback){ + super(parent,url,null,null,null, callback); + } + + @Override + protected Object3DData build() throws IOException { + // Parse STL + this.stlFileReader = new STLFileReader(url); + int totalFaces = stlFileReader.getNumOfFacets()[0]; + Log.i("STLLoader", "Num of objects: " + stlFileReader.getNumOfObjects()); + Log.i("STLLoader", "Found '" + totalFaces + "' facets"); + Log.i("STLLoader", "Parsing messages: " + stlFileReader.getParsingMessages()); + + // Allocate data + FloatBuffer normalsBuffer = createNativeByteBuffer(totalFaces * 3 * 3 * 4).asFloatBuffer(); + FloatBuffer vertexBuffer = createNativeByteBuffer(totalFaces * 3 * 3 * 4).asFloatBuffer(); + + // Initialize model dimensions (needed by the Object3DData#scaleCenter() + WavefrontLoader.ModelDimensions modelDimensions = new WavefrontLoader.ModelDimensions(); + + // notify succeded! + Object3DData data3D = new Object3DData(vertexBuffer).setVertexNormalsArrayBuffer(normalsBuffer); + data3D.setDimensions(modelDimensions); + data3D.setDrawUsingArrays(true); + data3D.setDrawMode(GLES20.GL_TRIANGLES); + return data3D; + } + + @Override + protected void build(Object3DData data) throws Exception + { + int counter = 0; + try { + // Parse all facets... + double[] normal = new double[3]; + double[][] vertices = new double[3][3]; + int normalCounter=0, vertexCounter=0; + + FloatBuffer normalsBuffer = data.getVertexNormalsArrayBuffer(); + FloatBuffer vertexBuffer = data.getVertexArrayBuffer(); + WavefrontLoader.ModelDimensions modelDimensions = data.getDimensions(); + + int totalFaces = stlFileReader.getNumOfFacets()[0]; + boolean first = true; + while (stlFileReader.getNextFacet(normal, vertices) && counter < totalFaces) { + Log.d("STLLoader", "Loading facet " + counter++ + ""); + normalsBuffer.put(normalCounter++,(float) normal[0]); + normalsBuffer.put(normalCounter++,(float) normal[1]); + normalsBuffer.put(normalCounter++,(float) normal[2]); + normalsBuffer.put(normalCounter++,(float) normal[0]); + normalsBuffer.put(normalCounter++,(float) normal[1]); + normalsBuffer.put(normalCounter++,(float) normal[2]); + normalsBuffer.put(normalCounter++,(float) normal[0]); + normalsBuffer.put(normalCounter++,(float) normal[1]); + normalsBuffer.put(normalCounter++,(float) normal[2]); + + vertexBuffer.put(vertexCounter++,(float) vertices[0][0]); + vertexBuffer.put(vertexCounter++,(float) vertices[0][1]); + vertexBuffer.put(vertexCounter++,(float) vertices[0][2]); + vertexBuffer.put(vertexCounter++,(float) vertices[1][0]); + vertexBuffer.put(vertexCounter++,(float) vertices[1][1]); + vertexBuffer.put(vertexCounter++,(float) vertices[1][2]); + vertexBuffer.put(vertexCounter++,(float) vertices[2][0]); + vertexBuffer.put(vertexCounter++,(float) vertices[2][1]); + vertexBuffer.put(vertexCounter++,(float) vertices[2][2]); + + // update model dimensions + if (first){ + modelDimensions.set((float) vertices[0][0],(float) vertices[0][1],(float) vertices[0][2]); + first = false; + } + modelDimensions.update((float) vertices[0][0],(float) vertices[0][1],(float) vertices[0][2]); + modelDimensions.update((float) vertices[1][0],(float) vertices[1][1],(float) vertices[1][2]); + modelDimensions.update((float) vertices[2][0],(float) vertices[2][1],(float) vertices[2][2]); + } + + Log.i("STLLoader", "Building 3D object..."); + data.centerScale(); + + } catch (Exception e) { + Log.e("STLLoader", "Face '"+counter+"'"+e.getMessage(), e); + throw e; + }finally { + try { + stlFileReader.close(); + } catch (IOException e) { + throw e; + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLParser.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLParser.java new file mode 100644 index 00000000..e713d1af --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/STLParser.java @@ -0,0 +1,154 @@ +/***************************************************************************** + * STLParser.java + * Java Source + * + * This source is licensed under the GNU LGPL v2.1. + * Please read http://www.gnu.org/copyleft/lgpl.html for more information. + * + * Copyright (c) 2002 Dipl. Ing. P. Szawlowski + * University of Vienna, Dept. of Medical Computer Sciences + ****************************************************************************/ + +package org.andresoviedo.app.model3D.services.stl; + +// External imports +import java.net.URL; +import java.io.IOException; +import java.util.List; + +// Local imports + +/** + * Abstract base class for parsing STL (stereolithography) files. Subclasses + * of this class implement parsing the two formats of STL files: binary and + * ASCII.

+ * @author Dipl. Ing. Paul Szawlowski - + * University of Vienna, Dept of Medical Computer Sciences + * @version $Revision: 1.3 $ + * Copyright (c) Dipl. Ing. Paul Szawlowski

+ */ +abstract class STLParser +{ + protected int itsNumOfObjects = 0; + protected int[] itsNumOfFacets = null; + protected String[] itsNames = null; + + /** Do we strictly parse or try harder */ + protected boolean strictParsing; + + /** Detailed parsing messages or null if none */ + protected List parsingMessages; + + public STLParser() + { + this(false); + } + + /** + * Constructor. + * + * @param strict Attempt to deal with crappy data or short downloads. + * Will try to return any useable geometry. + */ + public STLParser(boolean strict) + { + strictParsing = strict; + } + + /** + * Get array with object names. {@link #parse} must be called once before + * calling this method. + * @return Array of strings with names of objects. Size of array = number + * of objects in file. If name is not contained then the appropriate + * string is null. + */ + String[] getObjectNames() + { + return itsNames; + } + + /** + * Get number of facets per object. {@link #parse} must be called once + * before calling this method. + * @return Array with the number of facets per object. Size of array = + * number of objects in file. + */ + int[] getNumOfFacets() + { + return itsNumOfFacets; + } + + /** + * Get number of objects in file. {@link #parse} must be called once + * before calling this method. + */ + int getNumOfObjects() + { + return itsNumOfObjects; + } + + /** + * Get detailed messages on what was wrong when parsing. Only can happen + * when strictParsing is false. Means things like getNumOfFacets might + * be larger then reality. + */ + public List getParsingMessages() + { + return parsingMessages; + } + + /** + * Releases used resources. Must be called after finishing reading. + */ + abstract void close() throws IOException; + + /** + * Parses the file to obtain the number of objects, object names and number + * of facets per object. + * @param url URL to read from. + * @return true if file is in ASCII format, false + * otherwise. Use the appropriate subclass for reading. + */ + abstract boolean parse(URL url) + throws IOException; + + /** + * Parses the file to obtain the number of objects, object names and number + * of facets per object. A progress monitor will show the progress during + * parsing. + * @param url URL to read from. + * @param parentComponent Parent Component of progress monitor. + * Use null if there is no parent. + * @return true if file is in ASCII format, false + * otherwise. Use the appropriate subclass for reading. + */ + abstract boolean parse(URL url, Component parentComponent) + throws IllegalArgumentException, IOException; + + /** + * Returns the data for a facet. The orientation of the facets (which way + * is out and which way is in) is specified redundantly. First, the + * direction of the normal is outward. Second, the vertices are listed in + * counterclockwise order when looking at the object from the outside + * (right-hand rule).

+ * Call consecutively until all data is read. Call {@link #close} after + * finishing reading or if an exception occurs. + * @param normal array of size 3 to store the normal vector. + * @param vertices array of size 3x3 to store the vertex data. + *

    + *
  • first index: vertex + *
  • second index: + *
      + *
    • 0: x coordinate + *
    • 1: y coordinate + *
    • 2: z coordinate + *
    + *
+ * @return True if facet data is contained in + * normal and vertices. False + * if end of file is reached. Further calls of this method after + * the end of file is reached will lead to an IOException. + */ + abstract boolean getNextFacet(double[] normal, double[][] vertices) + throws IllegalArgumentException, IOException; +} \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/stl/package-info.java b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/package-info.java new file mode 100644 index 00000000..10b3e4e7 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/stl/package-info.java @@ -0,0 +1,8 @@ +/** + * Class for parsing and loading STL files. + * STL (Stereolithographie) files can be parsed and loaded using the standard loader interface with this package. + * Separate classes are provided for the parsing process if you do not wish to use the loader. + * + * Acknowledgements + * The code in this package is provided by Dipl. Ing. Paul Szawlowski of the University Of Vienna, Department of Medical Computer Sciences + */ \ No newline at end of file diff --git a/app/src/main/java/org/andresoviedo/app/model3D/services/wavefront/WavefrontLoader2.java b/app/src/main/java/org/andresoviedo/app/model3D/services/wavefront/WavefrontLoader2.java new file mode 100644 index 00000000..ef3dc8ab --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/services/wavefront/WavefrontLoader2.java @@ -0,0 +1,118 @@ +package org.andresoviedo.app.model3D.services.wavefront; + +import android.app.Activity; +import android.opengl.GLES20; +import android.util.Log; + +import org.andresoviedo.app.model3D.controller.LoaderTask; +import org.andresoviedo.app.model3D.model.Object3DBuilder; +import org.andresoviedo.app.model3D.model.Object3DData; +import org.andresoviedo.app.model3D.services.stl.STLLoader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * Wavefront loader implementation + * + * @author andresoviedo + */ + +public class WavefrontLoader2 { + + public static void loadAsync(final Activity parent, URL url, final File currentDir, + final String assetsDir, final String modelId, final Object3DBuilder.Callback callback) + { + new LoaderTask(parent,url,currentDir,assetsDir,modelId,callback){ + + // TODO: move this method inside the wavefront loader + private InputStream getInputStream() { + Log.i("LoaderTask", "Opening " + modelId + "..."); + try { + final InputStream ret; + if (currentDir != null) { + return new FileInputStream(new File(currentDir, modelId)); + } else if (assetsDir != null) { + return parent.getAssets().open(assetsDir + "/" + modelId); + } else { + throw new IllegalArgumentException("Model data source not specified"); + } + } catch (IOException ex) { + throw new RuntimeException( + "There was a problem opening file/asset '" + (currentDir != null ? currentDir : assetsDir) + "/" + modelId + "'"); + } + } + + private void closeStream(InputStream stream) { + if (stream == null) return; + try { + if (stream != null) { + stream.close(); + } + } catch (IOException ex) { + Log.e("LoaderTask", "Problem closing stream: " + ex.getMessage(), ex); + } + } + + @Override + protected Object3DData build() throws IOException { + InputStream params0 = getInputStream(); + org.andresoviedo.app.model3D.services.WavefrontLoader wfl = new org.andresoviedo.app.model3D.services.WavefrontLoader(""); + + // allocate memory + publishProgress(0); + wfl.analyzeModel(params0); + closeStream(params0); + + // Allocate memory + publishProgress(1); + wfl.allocateBuffers(); + wfl.reportOnModel(); + + // create the 3D object + Object3DData data3D = new Object3DData(wfl.getVerts(), wfl.getNormals(), wfl.getTexCoords(), wfl.getFaces(), + wfl.getFaceMats(), wfl.getMaterials()); + data3D.setId(modelId); + data3D.setCurrentDir(currentDir); + data3D.setAssetsDir(assetsDir); + data3D.setLoader(wfl); + data3D.setDrawMode(GLES20.GL_TRIANGLES); + data3D.setDimensions(data3D.getLoader().getDimensions()); + + return data3D; + } + + @Override + protected void build(Object3DData data) throws Exception { + InputStream stream = getInputStream(); + try { + // parse model + publishProgress(2); + data.getLoader().loadModel(stream); + closeStream(stream); + + // scale object + publishProgress(3); + data.centerScale(); + + // draw triangles instead of points + data.setDrawMode(GLES20.GL_TRIANGLES); + + // build 3D object buffers + publishProgress(4); + Object3DBuilder.generateArrays(parent.getAssets(), data); + publishProgress(5); + + } catch (Exception e) { + Log.e("Object3DBuilder", e.getMessage(), e); + throw e; + } finally { + closeStream(stream); + } + } + }.execute(); + } +} diff --git a/app/src/main/java/org/andresoviedo/app/model3D/view/DemoActivity.java b/app/src/main/java/org/andresoviedo/app/model3D/view/DemoActivity.java index 00edcfdb..3c9360f1 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/view/DemoActivity.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/view/DemoActivity.java @@ -58,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { // add 1 entry per model found rowItems = new ArrayList(); for (String model : models) { - if (model.toLowerCase().endsWith(".obj")) { + if (model.toLowerCase().endsWith(".obj") || model.toLowerCase().endsWith(".stl")) { RowItem item = new RowItem("models/" + model, model, "models/" + model + ".jpg"); rowItems.add(item); } @@ -136,7 +136,7 @@ class RowItem { */ String name; /** - * Assets path from where to load the .obj file + * Assets path from where to build the .obj file */ String path; diff --git a/app/src/main/java/org/andresoviedo/app/util/io/ProgressMonitorInputStream.java b/app/src/main/java/org/andresoviedo/app/util/io/ProgressMonitorInputStream.java new file mode 100644 index 00000000..3d51d540 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/util/io/ProgressMonitorInputStream.java @@ -0,0 +1,28 @@ +package org.andresoviedo.app.util.io; + +import android.app.Activity; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Dummy InputStream wrapper. No implementation yet. + * + * @author andresoviedo + */ + +public class ProgressMonitorInputStream extends InputStream { + + private final Activity parentActivity; + private final InputStream stream; + + public ProgressMonitorInputStream(Activity parentActivity, String text, InputStream stream) { + this.parentActivity = parentActivity; + this.stream = stream; + } + + @Override + public int read() throws IOException { + return stream.read(); + } +} diff --git a/app/src/main/java/org/andresoviedo/app/util/url/android/Handler.java b/app/src/main/java/org/andresoviedo/app/util/url/android/Handler.java new file mode 100644 index 00000000..c3000d92 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/util/url/android/Handler.java @@ -0,0 +1,87 @@ +package org.andresoviedo.app.util.url.android; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A {@link URLStreamHandler} that handles resources on the classpath. + */ +public class Handler extends URLStreamHandler { + + /** + * The classloader to find resources from. + */ + public static ClassLoader classLoader; + /** + * Android resources + */ + public static Resources androidResources; + /** + * Android assets manager + */ + public static AssetManager assets; + + public Handler() { + this.classLoader = getClass().getClassLoader(); + } + + @Override + protected URLConnection openConnection(final URL url) throws IOException { + return new ClasspathURLConnection(url); + } + + private class ClasspathURLConnection extends URLConnection { + + private final URL url; + private InputStream stream; + + public ClasspathURLConnection(URL url) { + super(url); + this.url = url; + } + + @Override + public void connect() throws IOException + { + if (stream != null) return; + + Log.i("Handler","Connecting to '"+url+"'..."); + + // resources implementation + if (url.getPath().startsWith("/res")) + { + String resPath = url.getHost()+":"+url.getPath().substring(5); + + Log.i("Handler","Opening resource '"+ resPath +"'..."); + int raw = androidResources.getIdentifier(resPath, null, null); + if (raw == 0) throw new IOException("Resource /ref not found: "+resPath); + stream = androidResources.openRawResource(raw); + } + + // assets implementation + else if (url.getPath().startsWith("/assets")) + { + String resPath = url.getPath().substring(8); + + Log.i("Handler","Opening asset '"+ resPath +"'..."); + stream = assets.open(resPath); + } + + if (stream == null) throw new IOException("stream is null"); + } + + @Override + public InputStream getInputStream() throws IOException { + connect(); + return stream; + } + + } +} \ No newline at end of file