diff --git a/applications/src/main/java/boofcv/app/MeshViewerApp.java b/applications/src/main/java/boofcv/app/MeshViewerApp.java index 7095a42120..4168d8643f 100644 --- a/applications/src/main/java/boofcv/app/MeshViewerApp.java +++ b/applications/src/main/java/boofcv/app/MeshViewerApp.java @@ -26,15 +26,11 @@ import boofcv.struct.image.ImageType; import boofcv.struct.image.InterleavedU8; import boofcv.struct.mesh.VertexMesh; -import org.apache.commons.io.FilenameUtils; -import org.ddogleg.struct.DogArray_I32; import javax.swing.*; import java.awt.*; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.util.Locale; /** * Very simple app for opening and viewing a 3D mesh @@ -49,19 +45,8 @@ public MeshViewerApp() { private static void loadFile( File file ) { // Load the mesh var mesh = new VertexMesh(); - var colors = new DogArray_I32(); - String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(Locale.ENGLISH); - var type = switch (extension) { - case "ply" -> PointCloudIO.Format.PLY; - case "stl" -> PointCloudIO.Format.STL; - case "obj" -> PointCloudIO.Format.OBJ; - default -> { - throw new RuntimeException("Unknown file type"); - } - }; - - try (var input = new FileInputStream(file)) { - PointCloudIO.load(type, input, mesh, colors); + try { + PointCloudIO.load(file, mesh); } catch (IOException e) { e.printStackTrace(System.err); System.exit(1); @@ -86,8 +71,8 @@ private static void loadFile( File file ) { SwingUtilities.invokeLater(() -> { var panel = new MeshViewerPanel(); panel.setMesh(mesh, false); - if (colors.size > 0) - panel.setVertexColors("RGB", colors.data); + if (mesh.rgb.size > 0) + panel.setVertexColors("RGB", mesh.rgb.data); if (_image != null) panel.setTextureImage(_image); panel.setPreferredSize(new Dimension(500, 500)); diff --git a/integration/boofcv-swing/src/main/java/boofcv/gui/mesh/MeshColorizeOps.java b/integration/boofcv-swing/src/main/java/boofcv/gui/mesh/MeshColorizeOps.java index 484c979b1e..5d9fd33be7 100644 --- a/integration/boofcv-swing/src/main/java/boofcv/gui/mesh/MeshColorizeOps.java +++ b/integration/boofcv-swing/src/main/java/boofcv/gui/mesh/MeshColorizeOps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -40,7 +40,7 @@ public class MeshColorizeOps { * @return SurfaceColor */ public static RenderMesh.SurfaceColor colorizeByVertex( VertexMesh mesh, int[] vertexColor ) { - return ( shapeIdx ) -> vertexColor[mesh.indexes.get(mesh.offsets.get(shapeIdx))]; + return ( shapeIdx ) -> vertexColor[mesh.faceVertexes.get(mesh.faceOffsets.get(shapeIdx))]; } /** @@ -57,7 +57,7 @@ public static RenderMesh.SurfaceColor colorizeByNormal( VertexMesh mesh ) { var axisZ = new Vector3D_F64(0, 0, 1); for (int i = 0; i < mesh.size(); i++) { - mesh.getShape(i, facet); + mesh.getFaceVectors(i, facet); // Handle case of invalid facet gracefully by assigning it to an arbitrary color if (facet.size < 3) { diff --git a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/DisparityToColorPointCloud.java b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/DisparityToColorPointCloud.java index 5a1ea921a3..dd4d432a5f 100644 --- a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/DisparityToColorPointCloud.java +++ b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/DisparityToColorPointCloud.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -175,7 +175,10 @@ private void process( GrayU8 disparity, ColorImage color, PointCloudWriter outpu // Bring it back into left camera frame GeometryMath_F32.multTran(rectifiedR, p, p); - output.add(p.x, p.y, p.z, getColor(color, pixelX, pixelY)); + output.startPoint(); + output.location(p.x, p.y, p.z); + output.color(getColor(color, pixelX, pixelY)); + output.stopPoint(); } } } @@ -217,7 +220,10 @@ private void process( GrayF32 disparity, ColorImage color, PointCloudWriter outp // Bring it back into left camera frame GeometryMath_F32.multTran(rectifiedR, p, p); - output.add(p.x, p.y, p.z, getColor(color, pixelX, pixelY)); + output.startPoint(); + output.location(p.x, p.y, p.z); + output.color(getColor(color, pixelX, pixelY)); + output.stopPoint(); } } } diff --git a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudReader.java b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudReader.java index 79ed635a3e..8bddc57bd2 100644 --- a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudReader.java +++ b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -38,6 +38,9 @@ public interface PointCloudReader { */ int size(); + /** True if each point has a color */ + boolean colors(); + /** * Copies the point */ @@ -58,6 +61,8 @@ static PointCloudReader wrap3FRGB( float[] cloud, float[] rgb, int offset, int l @Override public int size() {return length;} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) { int i = offset + index*3; @@ -85,6 +90,8 @@ static PointCloudReader wrapF32( List cloud ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return false;} + @Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));} @Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);} @@ -97,6 +104,8 @@ static PointCloudReader wrapF64( List cloud ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return false;} + @Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);} @Override public void get( int index, Point3D_F64 point ) {point.setTo(cloud.get(index));} @@ -109,6 +118,8 @@ static PointCloudReader wrapF32RGB( List cloud ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));} @Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);} @@ -121,6 +132,8 @@ static PointCloudReader wrapF64RGB( List cloud ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);} @Override public void get( int index, Point3D_F64 point ) {point.setTo(cloud.get(index));} @@ -133,6 +146,8 @@ static PointCloudReader wrapF32( List cloud, int[] rgb ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));} @Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);} @@ -145,6 +160,8 @@ static PointCloudReader wrapF64( List cloud, int[] rgb ) { return new PointCloudReader() { @Override public int size() {return cloud.size();} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);} @Override public void get( int index, Point3D_F64 point ) {point.setTo(cloud.get(index));} @@ -159,6 +176,8 @@ static PointCloudReader wrap( Generic op, int size ) { @Override public int size() {return size;} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F32 point ) { op.get(index, p); point.x = (float)p.x; diff --git a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudWriter.java b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudWriter.java index 47a818661b..a26aad531b 100644 --- a/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudWriter.java +++ b/main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -40,10 +40,21 @@ public interface PointCloudWriter { */ void initialize( int size, boolean hasColor ); + /** A new point is being added with new attributes to follow */ + void startPoint(); + + /** It's done specifying attributes for the point */ + void stopPoint(); + + /** + * Set the 3D location of the point + */ + void location( double x, double y, double z ); + /** - * Adds a 3D point with color information + * Sets the points color */ - void add( double x, double y, double z, int rgb ); + void color( int rgb ); class CloudArraysF32 implements PointCloudWriter { // Storage for point cloud @@ -57,10 +68,17 @@ class CloudArraysF32 implements PointCloudWriter { cloudXyz.reserve(size*3); } - @Override public void add( double x, double y, double z, int rgb ) { + @Override public void startPoint() {} + + @Override public void stopPoint() {} + + @Override public void location( double x, double y, double z ) { cloudXyz.add((float)x); cloudXyz.add((float)y); cloudXyz.add((float)z); + } + + @Override public void color( int rgb ) { cloudRgb.add(rgb); } @@ -79,10 +97,15 @@ static PointCloudWriter wrapF32( DogArray cloud ) { cloud.reset(); } - @Override - public void add( double x, double y, double z, int rgb ) { + @Override public void startPoint() {} + + @Override public void stopPoint() {} + + @Override public void location( double x, double y, double z ) { cloud.grow().setTo((float)x, (float)y, (float)z); } + + @Override public void color( int rgb ) {} }; } @@ -93,13 +116,37 @@ static PointCloudWriter wrapF64( DogArray cloud ) { cloud.reset(); } - @Override - public void add( double x, double y, double z, int rgb ) { + @Override public void startPoint() {} + + @Override public void stopPoint() {} + + @Override public void color( int rgb ) {} + + @Override public void location( double x, double y, double z ) { cloud.grow().setTo(x, y, z); } }; } + static PointCloudWriter wrapF64( DogArray cloud, DogArray_I32 colors ) { + return new PointCloudWriter() { + @Override public void initialize( int size, boolean hasColor ) { + cloud.reserve(size); + cloud.reset(); + colors.reserve(size); + colors.reset(); + } + + @Override public void startPoint() {} + + @Override public void stopPoint() {} + + @Override public void color( int rgb ) {colors.add(rgb);} + + @Override public void location( double x, double y, double z ) {cloud.grow().setTo(x, y, z);} + }; + } + static PointCloudWriter wrapF32RGB( DogArray cloud ) { return new PointCloudWriter() { @Override public void initialize( int size, boolean hasColor ) { @@ -107,9 +154,18 @@ static PointCloudWriter wrapF32RGB( DogArray cloud ) { cloud.reset(); } - @Override - public void add( double x, double y, double z, int rgb ) { - cloud.grow().setTo((float)x, (float)y, (float)z, rgb); + @Override public void startPoint() { + cloud.grow(); + } + + @Override public void stopPoint() {} + + @Override public void location( double x, double y, double z ) { + cloud.getTail().setTo((float)x, (float)y, (float)z); + } + + @Override public void color( int rgb ) { + cloud.getTail().rgb = rgb; } }; } @@ -120,9 +176,18 @@ static PointCloudWriter wrapF64RGB( DogArray cloud ) { cloud.reset(); } - @Override - public void add( double x, double y, double z, int rgb ) { - cloud.grow().setTo(x, y, z, rgb); + @Override public void startPoint() { + cloud.grow(); + } + + @Override public void stopPoint() {} + + @Override public void location( double x, double y, double z ) { + cloud.getTail().setTo(x, y, z); + } + + @Override public void color( int rgb ) { + cloud.getTail().rgb = rgb; } }; } diff --git a/main/boofcv-geo/src/test/java/boofcv/alg/cloud/TestPointCloudWriter.java b/main/boofcv-geo/src/test/java/boofcv/alg/cloud/TestPointCloudWriter.java index dce2b559bd..7c6297b568 100644 --- a/main/boofcv-geo/src/test/java/boofcv/alg/cloud/TestPointCloudWriter.java +++ b/main/boofcv-geo/src/test/java/boofcv/alg/cloud/TestPointCloudWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -35,32 +35,41 @@ class TestPointCloudWriter extends BoofStandardJUnit { static abstract class PcwTests { boolean supportsColor = true; + public abstract PointCloudWriter create(); + public abstract int size( PointCloudWriter data ); - public abstract Point3D_F64 getPoint(PointCloudWriter data , int i ); - public abstract int getColor(PointCloudWriter data , int i); + + public abstract Point3D_F64 getPoint( PointCloudWriter data, int i ); + + public abstract int getColor( PointCloudWriter data, int i ); @Test void simpleXyzColor() { PointCloudWriter alg = create(); - assertEquals(0,size(alg)); - alg.add(0,1,2, 345); - assertEquals(1,size(alg)); - alg.add(2,1,3,3434); - assertEquals(2,size(alg)); - - if( supportsColor ) { + assertEquals(0, size(alg)); + alg.startPoint(); + alg.location(0, 1, 2); + alg.color(345); + alg.stopPoint(); + assertEquals(1, size(alg)); + alg.startPoint(); + alg.location(2, 1, 3); + alg.color(3434); + alg.stopPoint(); + assertEquals(2, size(alg)); + + if (supportsColor) { assertEquals(345, getColor(alg, 0)); assertEquals(3434, getColor(alg, 1)); } - Point3D_F64 found = getPoint(alg,1); - assertEquals(2,found.x, UtilEjml.TEST_F64); - assertEquals(1,found.y, UtilEjml.TEST_F64); - assertEquals(3,found.z, UtilEjml.TEST_F64); + Point3D_F64 found = getPoint(alg, 1); + assertEquals(2, found.x, UtilEjml.TEST_F64); + assertEquals(1, found.y, UtilEjml.TEST_F64); + assertEquals(3, found.z, UtilEjml.TEST_F64); } - } @Nested @@ -72,18 +81,18 @@ public PointCloudWriter create() { } @Override - public int size(PointCloudWriter data) { + public int size( PointCloudWriter data ) { return ((CloudArraysF32)data).cloudXyz.size/3; } @Override - public Point3D_F64 getPoint(PointCloudWriter data, int i) { + public Point3D_F64 getPoint( PointCloudWriter data, int i ) { var list = ((CloudArraysF32)data).cloudXyz; - return new Point3D_F64( list.get(i*3), list.get(i*3+1), list.get(i*3+2)); + return new Point3D_F64(list.get(i*3), list.get(i*3 + 1), list.get(i*3 + 2)); } @Override - public int getColor(PointCloudWriter data, int i) { + public int getColor( PointCloudWriter data, int i ) { var list = ((CloudArraysF32)data).cloudRgb; return list.get(i); } @@ -102,18 +111,18 @@ public PointCloudWriter create() { } @Override - public int size(PointCloudWriter data) { + public int size( PointCloudWriter data ) { return cloud.size; } @Override - public Point3D_F64 getPoint(PointCloudWriter data, int i) { + public Point3D_F64 getPoint( PointCloudWriter data, int i ) { Point3D_F32 c = cloud.get(i); - return new Point3D_F64( c.x, c.y, c.z); + return new Point3D_F64(c.x, c.y, c.z); } @Override - public int getColor(PointCloudWriter data, int i) { + public int getColor( PointCloudWriter data, int i ) { throw new RuntimeException("Not supported"); } } @@ -131,17 +140,17 @@ public PointCloudWriter create() { } @Override - public int size(PointCloudWriter data) { + public int size( PointCloudWriter data ) { return cloud.size; } @Override - public Point3D_F64 getPoint(PointCloudWriter data, int i) { + public Point3D_F64 getPoint( PointCloudWriter data, int i ) { return cloud.get(i); } @Override - public int getColor(PointCloudWriter data, int i) { + public int getColor( PointCloudWriter data, int i ) { throw new RuntimeException("Not supported"); } } @@ -158,18 +167,18 @@ public PointCloudWriter create() { } @Override - public int size(PointCloudWriter data) { + public int size( PointCloudWriter data ) { return cloud.size; } @Override - public Point3D_F64 getPoint(PointCloudWriter data, int i) { + public Point3D_F64 getPoint( PointCloudWriter data, int i ) { Point3D_F32 c = cloud.get(i); - return new Point3D_F64( c.x, c.y, c.z); + return new Point3D_F64(c.x, c.y, c.z); } @Override - public int getColor(PointCloudWriter data, int i) { + public int getColor( PointCloudWriter data, int i ) { return cloud.get(i).rgb; } } @@ -186,19 +195,18 @@ public PointCloudWriter create() { } @Override - public int size(PointCloudWriter data) { + public int size( PointCloudWriter data ) { return cloud.size; } @Override - public Point3D_F64 getPoint(PointCloudWriter data, int i) { + public Point3D_F64 getPoint( PointCloudWriter data, int i ) { return cloud.get(i); } @Override - public int getColor(PointCloudWriter data, int i) { + public int getColor( PointCloudWriter data, int i ) { return cloud.get(i).rgb; } } - } diff --git a/main/boofcv-io/src/benchmark/java/boofcv/visualize/BenchmarkRenderMesh.java b/main/boofcv-io/src/benchmark/java/boofcv/visualize/BenchmarkRenderMesh.java index 5eca47a560..8374d2582d 100644 --- a/main/boofcv-io/src/benchmark/java/boofcv/visualize/BenchmarkRenderMesh.java +++ b/main/boofcv-io/src/benchmark/java/boofcv/visualize/BenchmarkRenderMesh.java @@ -87,7 +87,7 @@ private void createFlatSquareScene( CameraPinhole intrinsics, Random rand ) { shape.get(2).setTo(x1, y1, z); shape.get(3).setTo(x0, y1, z); - mesh.addShape(shape.toList()); + mesh.addFaceVectors(shape.toList()); } } diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/PointCloudIO.java b/main/boofcv-io/src/main/java/boofcv/io/points/PointCloudIO.java index 565bf5a38c..7846a386eb 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/PointCloudIO.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/PointCloudIO.java @@ -36,6 +36,7 @@ import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Locale; +import java.util.Map; /** * Code for reading different point cloud formats @@ -95,6 +96,8 @@ public static void save3D( Format format, @Override public int size() {return size;} + @Override public boolean colors() {return true;} + @Override public void get( int index, Point3D_F64 point ) {accessPoint.getPoint(index, point);} @Override public int getRGB( int index ) {return accessColor.getRGB(index);} @@ -148,37 +151,54 @@ public static void load( Format format, InputStream input, PointCloudWriter outp /** * Loads the mesh from a file. File type is determined by the file's extension. * + *

For OBJ files, if there are multiple shapes defined then only the first one is returned.

+ * * @param file Which file it should load * @param mesh (Output) storage for the mesh - * @param colors (Output) Storage for vertex colors */ - public static void load( File file, VertexMesh mesh, DogArray_I32 colors ) throws IOException { + public static void load( File file, VertexMesh mesh ) throws IOException { String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(Locale.ENGLISH); var type = switch (extension) { case "ply" -> PointCloudIO.Format.PLY; case "stl" -> PointCloudIO.Format.STL; case "obj" -> PointCloudIO.Format.OBJ; - default -> { - throw new RuntimeException("Unknown file type: " + extension); - } + default -> throw new RuntimeException("Unknown file type: " + extension); }; + + // OBJ files are special. They need to read in multiple files to get the texture map image and + // there can be multiple shapes defined. This will handle all those situations + if (type == PointCloudIO.Format.OBJ) { + var reader = new ObjLoadFromFiles(); + reader.load(file, mesh); + return; + } + try (var input = new FileInputStream(file)) { - PointCloudIO.load(type, input, mesh, colors); + PointCloudIO.load(type, input, mesh); } } + /** + * Loads a set of {@link VertexMesh} from an OBJ file. This can ready any type of OBJ file as it doesn't make + * assumptions about what is contained inside of it + */ + public static Map loadObj( File file ) { + var reader = new ObjLoadFromFiles(); + reader.load(file, null); + return reader.getShapeToMesh(); + } + /** * Reads a 3D mesh from the input stream in the specified format and writes it to the output. * * @param format Storage format * @param input Input stream * @param mesh (Output) 3D mesh - * @param vertexRgb (Output) color of each vertex */ - public static void load( Format format, InputStream input, VertexMesh mesh, DogArray_I32 vertexRgb ) throws IOException { + public static void load( Format format, InputStream input, VertexMesh mesh ) throws IOException { switch (format) { - case PLY -> PlyCodec.readMesh(input, mesh, vertexRgb); + case PLY -> PlyCodec.readMesh(input, mesh); case OBJ -> ObjFileCodec.load(input, mesh); case STL -> { var stlMesh = new StlDataStructure(); @@ -197,10 +217,23 @@ public static void load( Format format, InputStream input, VertexMesh mesh, DogA */ public static void load( Format format, InputStream input, FunctionalWriter output ) throws IOException { PointCloudWriter pcw = new PointCloudWriter() { + Point3D_F64 location = new Point3D_F64(); + int color = 0; + @Override public void initialize( int size, boolean hasColor ) {} - @Override public void add( double x, double y, double z, int rgb ) { - output.add(x, y, z, rgb); + @Override public void startPoint() {} + + @Override public void stopPoint() { + output.add(location.x, location.y, location.z, color); + } + + @Override public void location( double x, double y, double z ) { + this.location.setTo(x, y, z); + } + + @Override public void color( int rgb ) { + this.color = rgb; } }; load(format, input, pcw); diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/StlDataStructure.java b/main/boofcv-io/src/main/java/boofcv/io/points/StlDataStructure.java index 9730c443bf..63270f75aa 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/StlDataStructure.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/StlDataStructure.java @@ -186,13 +186,13 @@ public VertexMesh toMesh( @Nullable VertexMesh out ) { out = new VertexMesh(); out.vertexes.setTo(vertexes); - out.indexes.setTo(facetVertsIdx); + out.faceVertexes.setTo(facetVertsIdx); // All facets are triangles - out.offsets.resize(facetCount() + 1); - out.offsets.set(0, 0); - for (int i = 1; i < out.offsets.size; i++) { - out.offsets.set(i, i*3); + out.faceOffsets.resize(facetCount() + 1); + out.faceOffsets.set(0, 0); + for (int i = 1; i < out.faceOffsets.size; i++) { + out.faceOffsets.set(i, i*3); } return out; diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileCodec.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileCodec.java index 05e22c8845..3d55651a42 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileCodec.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -21,7 +21,10 @@ import boofcv.alg.cloud.PointCloudReader; import boofcv.alg.cloud.PointCloudWriter; import boofcv.struct.mesh.VertexMesh; +import georegression.struct.point.Point2D_F32; +import georegression.struct.point.Point3D_F32; import georegression.struct.point.Point3D_F64; +import org.apache.commons.io.FilenameUtils; import org.ddogleg.struct.DogArray_I32; import java.io.*; @@ -42,46 +45,137 @@ public static void save( PointCloudReader cloud, Writer writer ) throws IOExcept var obj = new ObjFileWriter(writer); obj.addComment("Created by BoofCV"); + boolean hasColor = cloud.colors(); + var point = new Point3D_F64(); int N = cloud.size(); for (int i = 0; i < N; i++) { cloud.get(i, point); - obj.addVertex(point.x, point.y, point.z); + if (hasColor) { + addRgbVertex(obj, point, cloud.getRGB(i)); + } else { + obj.addVertex(point.x, point.y, point.z); + } obj.addPoint(-1); } } + /** + * Creates a MTL file. This is required if the mesh is textured mapped + * + * @param textureFile Path to texture mapped file + * @param writer Where the MTL will be written to + */ + public static void saveMtl( String textureFile, Writer writer ) throws IOException { + // Use hard coded values + String text = """ + newmtl %s + Ka 1.0 1.0 1.0 + Kd 1.0 1.0 1.0 + Ks 0.0 0.0 0.0 + d 1.0 + Ns 0.0 + illum 0 + map_Kd %s"""; + + String baseName = FilenameUtils.getBaseName(textureFile); + writer.write(String.format(text, baseName, textureFile)); + } + public static void save( VertexMesh mesh, Writer writer ) throws IOException { var obj = new ObjFileWriter(writer); obj.addComment("Created by BoofCV"); + // If there's a texture file, add a material + if (!mesh.textureName.isEmpty()) { + String baseName = FilenameUtils.getBaseName(mesh.textureName); + obj.addLibrary(baseName + ".mtl"); + obj.addMaterial(baseName); + } + + boolean hasVertexColors = mesh.rgb.size > 0; + // First save the vertexes int N = mesh.vertexes.size(); for (int i = 0; i < N; i++) { Point3D_F64 p = mesh.vertexes.getTemp(i); - obj.addVertex(p.x, p.y, p.z); + if (hasVertexColors) { + addRgbVertex(obj, p, mesh.rgb.get(i)); + } else { + obj.addVertex(p.x, p.y, p.z); + } + } + + // Save vertex normals + for (int i = 0; i < mesh.normals.size(); i++) { + Point3D_F32 p = mesh.normals.getTemp(i); + obj.addVertexNormal(p.x, p.y, p.z); } + // Save vertex textures + for (int i = 0; i < mesh.texture.size(); i++) { + Point2D_F32 p = mesh.texture.getTemp(i); + obj.addTextureVertex(p.x, p.y); + } + + // See how many different types of vertexes need to be saved + int count = 0; + if (mesh.vertexes.size() > 0) + count++; + if (mesh.normals.size() > 0) + count++; + if (mesh.texture.size() > 0) + count++; + // Create the faces var indexes = new DogArray_I32(); - for (int i = 1; i < mesh.offsets.size; i++) { - int idx0 = mesh.offsets.get(i - 1); - int idx1 = mesh.offsets.get(i); + for (int i = 1; i < mesh.faceOffsets.size; i++) { + int idx0 = mesh.faceOffsets.get(i - 1); + int idx1 = mesh.faceOffsets.get(i); indexes.reset(); - indexes.addAll(mesh.indexes.data, idx0, idx1); - obj.addFace(indexes); + indexes.addAll(mesh.faceVertexes.data, idx0, idx1); + obj.addFace(indexes, count); } } + private static void addRgbVertex( ObjFileWriter obj, Point3D_F64 p, int rgb ) throws IOException { + // Convert to float format + double red = ((rgb >> 16) & 0xFF)/255.0; + double green = ((rgb >> 8) & 0xFF)/255.0; + double blue = (rgb & 0xFF)/255.0; + obj.addVertex(p.x, p.y, p.z, red, green, blue); + } + public static void load( InputStream input, PointCloudWriter output ) throws IOException { var obj = new ObjFileReader() { + @Override protected void addLibrary( String name ) {} + + @Override protected void addMaterial( String name ) {} + @Override protected void addVertex( double x, double y, double z ) { - output.add(x, y, z, 0); + output.startPoint(); + output.location(x, y, z); + output.stopPoint(); } + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + output.startPoint(); + output.location(x, y, z); + output.color(convertToInt(red, green, blue)); + output.stopPoint(); + } + + @Override protected void addVertexNormal( double x, double y, double z ) {} + + @Override protected void addVertexTexture( double x, double y ) {} + @Override protected void addPoint( int vertex ) {} + @Override protected void addLine( DogArray_I32 vertexes ) {} - @Override protected void addFace( DogArray_I32 vertexes ) {} + + @Override protected void addFace( DogArray_I32 indexes, int vertexCount ) {} }; obj.parse(new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))); } @@ -89,16 +183,65 @@ public static void load( InputStream input, PointCloudWriter output ) throws IOE public static void load( InputStream input, VertexMesh output ) throws IOException { output.reset(); var obj = new ObjFileReader() { + // Library and material doesn't translate well a stream input and mesh output + // A single obj file can define multiple objects with different texture maps + @Override protected void addLibrary( String name ) {} + + @Override protected void addMaterial( String name ) {} + @Override protected void addVertex( double x, double y, double z ) { output.vertexes.append(x, y, z); } + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + output.vertexes.append(x, y, z); + output.rgb.add(convertToInt(red, green, blue)); + } + + @Override protected void addVertexNormal( double x, double y, double z ) { + output.normals.append((float)x, (float)y, (float)z); + } + + @Override protected void addVertexTexture( double x, double y ) { + output.texture.append((float)x, (float)y); + } + @Override protected void addPoint( int vertex ) {} + @Override protected void addLine( DogArray_I32 vertexes ) {} - @Override protected void addFace( DogArray_I32 vertexes ) { - output.indexes.addAll(vertexes); - output.offsets.add(output.indexes.size); + + @Override protected void addFace( DogArray_I32 indexes, int vertexCount ) { + addFactToMesh(indexes, vertexCount, output); } }; obj.parse(new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))); } + + static void addFactToMesh( DogArray_I32 indexes, int vertexCount, VertexMesh output ) { + // order of that indexes are specifies is always vertex/texture/normal + + boolean hasTexture = output.texture.size() > 0; + boolean hasNormals = output.normals.size() > 0; + + int typeCount = indexes.size/vertexCount; + for (int idxVert = 0; idxVert < vertexCount; idxVert++) { + int index = idxVert*typeCount;; + output.faceVertexes.add(indexes.get(index++)); + + if (hasTexture) { + output.faceVertexTextures.add(indexes.get(index++)); + } + + if (hasNormals) { + output.faceVertexNormals.add(indexes.get(index)); + } + } + + output.faceOffsets.add(output.faceVertexes.size); + } + + static int convertToInt( double red, double green, double blue ) { + return ((int)(255*red + 0.5)) << 16 | ((int)(255*green + 0.5)) << 8 | ((int)(255*blue + 0.5)); + } } diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileReader.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileReader.java index 580dbb880a..5eeb4ac09e 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileReader.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -29,12 +29,16 @@ public abstract class ObjFileReader { DogArray_I32 vertexIndexes = new DogArray_I32(); int vertexCount = 0; + int vertexTextureCount = 0; + int vertexNormalCount = 0; /** * Decodes / reads the OBJ file encoded as text in the reader */ public void parse( BufferedReader reader ) throws IOException { vertexCount = 0; + vertexTextureCount = 0; + vertexNormalCount = 0; var builder = new StringBuilder(); int actualLineCount = 0; @@ -74,20 +78,52 @@ public void parse( BufferedReader reader ) throws IOException { double x = Double.parseDouble(words[1]); double y = Double.parseDouble(words[2]); double z = Double.parseDouble(words[3]); - addVertex(x, y, z); + + // See if it also has the optional RGB values for each vertex + if (words.length == 7) { + double r = Double.parseDouble(words[4]); + double g = Double.parseDouble(words[5]); + double b = Double.parseDouble(words[6]); + addVertexWithColor(x, y, z, r, g, b); + } else { + addVertex(x, y, z); + } + vertexCount++; } + + case "vn" -> { + double x = Double.parseDouble(words[1]); + double y = Double.parseDouble(words[2]); + double z = Double.parseDouble(words[3]); + addVertexNormal(x, y, z); + vertexNormalCount++; + } + + case "vt" -> { + double x = Double.parseDouble(words[1]); + double y = Double.parseDouble(words[2]); + addVertexTexture(x, y); + vertexTextureCount++; + } + case "p" -> addPoint(ensureIndex(Integer.parseInt(words[1]))); case "l" -> { readPoints(words); addLine(vertexIndexes); } case "f" -> { - readPoints(words); - addFace(vertexIndexes); + readFaceIndexes(words); + addFace(vertexIndexes, words.length - 1); } + + case "mtllib" -> addLibrary(words[1]); + case "usemtl" -> addMaterial(words[1]); default -> handleError(actualLineCount + " Unknown object type. '" + words[0] + "'"); } + } catch (ObjLoadFromFiles.MultipleMaterials e) { + // this exception is thrown as a way to stop reading the file + return; } catch (Exception e) { // Skip over locally bad data handleError(actualLineCount + " Bad object description " + words[0] + " '" + e.getMessage() + "'"); @@ -115,13 +151,52 @@ private void readPoints( String[] words ) { } } + private void readFaceIndexes( String[] words ) { + vertexIndexes.reset(); + for (int i = 1; i < words.length; i++) { + String word = words[i]; + + int idx0 = 0; + int idx1 = word.indexOf('/'); + while (idx1 != -1) { + vertexIndexes.add(ensureIndex(Integer.parseInt(word.substring(idx0, idx1)))); + idx0 = idx1 + 1; + idx1 = word.indexOf('/', idx0); + } + vertexIndexes.add(ensureIndex(Integer.parseInt(word.substring(idx0)))); + } + } + + /** + * Adds an MTL library that it should load + */ + protected abstract void addLibrary( String name ); + + /** + * Tells it that these vectors belongs the material defined in the MTL library + */ + protected abstract void addMaterial( String name ); + protected abstract void addVertex( double x, double y, double z ); + /** Adds a color for a vertex. RGB is specified in a range of 0 to 1.0 */ + protected abstract void addVertexWithColor( double x, double y, double z, double red, double green, double blue ); + + protected abstract void addVertexNormal( double x, double y, double z ); + + protected abstract void addVertexTexture( double x, double y ); + protected abstract void addPoint( int vertex ); protected abstract void addLine( DogArray_I32 vertexes ); - protected abstract void addFace( DogArray_I32 vertexes ); + /** + * Adds a face. Indexes are interleaved in vertex, normal, and texture order. + * + * @param indexes Indexes of vertex values + * @param vertexCount Number of vertexes in the face + */ + protected abstract void addFace( DogArray_I32 indexes, int vertexCount ); /** * If something goes where it's passed here diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileWriter.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileWriter.java index e310680f1f..c06ff0a6e8 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileWriter.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -52,6 +52,17 @@ public void addVertex( double x, double y, double z ) throws IOException { vertexCount++; } + public void addVertex( double x, double y, double z, double r, double g , double b ) throws IOException { + writer.write('v'); + writer.write(String.format(" " + formatFloat, x)); + writer.write(String.format(" " + formatFloat, y)); + writer.write(String.format(" " + formatFloat, z)); + writer.write(String.format(" " + formatFloat, r)); + writer.write(String.format(" " + formatFloat, g)); + writer.write(String.format(" " + formatFloat + "\n", b)); + vertexCount++; + } + public void addTextureVertex( double x, double y ) throws IOException { writer.write("vt"); writer.write(String.format(" " + formatFloat, x)); @@ -90,7 +101,7 @@ public void addPoint( int vertex ) throws IOException { * @param vertexes Index of vertexes in this face. If null then automatic. */ public void addLine( @Nullable DogArray_I32 vertexes ) throws IOException { - writeObject('l', vertexes); + writeObject('l', vertexes, 1); } /** @@ -99,13 +110,21 @@ public void addLine( @Nullable DogArray_I32 vertexes ) throws IOException { * *

NOTE: Vertexes indexes are 0-indexed. Not 1-indexed like they are in OBJ files.

* + * + * @param count Number of different vertex types, i.e. 3D, texture, normal. All are assumed to + * reference a point with the same index. * @param vertexes Index of vertexes in this face. If null then automatic. */ - public void addFace( @Nullable DogArray_I32 vertexes ) throws IOException { - writeObject('f', vertexes); + public void addFace( @Nullable DogArray_I32 vertexes, int count ) throws IOException { + writeObject('f', vertexes, count); } - private void writeObject( char name, @Nullable DogArray_I32 vertexes ) throws IOException { + /** + * + * @param count How many times it should duplicate the index. It's assumed all vertexes reference + * objects with the same index values. + */ + private void writeObject( char name, @Nullable DogArray_I32 vertexes, int count ) throws IOException { writer.write(name); if (vertexes == null || vertexes.size == 0) { int v = -1; @@ -125,7 +144,11 @@ else if (vertIndex <= -vertexCount) // Write the index and compensate for the file format being 1-indexed. if (vertIndex >= 0) vertIndex++; + writer.write(" " + vertIndex); + for (int idxCount = 1; idxCount < count; idxCount++) { + writer.write("/" + vertIndex); + } } } writer.write('\n'); diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjLoadFromFiles.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjLoadFromFiles.java new file mode 100644 index 0000000000..d36f642e4c --- /dev/null +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjLoadFromFiles.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. + * + * This file is part of BoofCV (http://boofcv.org). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package boofcv.io.points.impl; + +import boofcv.struct.mesh.VertexMesh; +import lombok.Getter; +import org.ddogleg.struct.DogArray_I32; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Loads a OBJ file and associated MTL file. This makes assumptions that it's reading from a file system and doesn't. + * This allows it to search for files referenced in the OBJ. + */ +public class ObjLoadFromFiles { + private Map materialToTextureFile = new HashMap<>(); + + /** Returns a map containing all the shapes that were loaded from the OBJ file */ + private @Getter Map shapeToMesh = new HashMap<>(); + + /** Indicates that it found multiple materials in the file. */ + private @Getter boolean ignoredMaterial = false; + + // If true then the material being seen is the first one + private boolean first = true; + + /** + * Loads the OBJ file from disk. Opens up and reads in any related MTL files as needed. To see if any materials + * were ignored because a mesh was passed in, look at {@link #ignoredMaterial}. If no material is defined then + * the output mesh will be placed in the map with a key of an empty String. + * + * @param outputMesh If not null the proved mesh will be used for storage. If more than one mesh is defined in + * the file then only the first one will be read. + */ + public void load( File file, @Nullable VertexMesh outputMesh ) { + ignoredMaterial = false; + first = true; + var reader = new ObjFileReader() { + VertexMesh active; + + { + // If the output mesh was passed in default to that, otherwise create a new mesh to default to + active = outputMesh != null ? outputMesh : new VertexMesh(); + active.reset(); + shapeToMesh.put("", active); + } + + @Override protected void addLibrary( String name ) { + readTextureFilesFromMTL(new File(file.getParent(), name)); + } + + @Override protected void addMaterial( String name ) { + // If this is the first material to add AND a shape has not been defined already, + // remove the default mesh from the map since it isn't being used + if (first && active.vertexes.size() == 0) { + shapeToMesh.remove(""); + } + + if (outputMesh != null) { + // If first isn't true that means another material was encountered. If vertexes are not zero that + // means it was reading in a default object already + if (!first || active.vertexes.size() != 0) { + ignoredMaterial = true; + throw new MultipleMaterials(); + } + } else { + active = new VertexMesh(); + } + + first = false; + + // Set the texture file name if it's specified for this material + if (materialToTextureFile.containsKey(name)) { + active.textureName = materialToTextureFile.get(name); + } else { + System.err.println("Unknown material '" + name + "'"); + } + shapeToMesh.put(name, active); + } + + @Override protected void addVertex( double x, double y, double z ) { + active.vertexes.append(x, y, z); + } + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + active.vertexes.append(x, y, z); + active.rgb.add(ObjFileCodec.convertToInt(red, green, blue)); + } + + @Override protected void addVertexNormal( double x, double y, double z ) { + active.normals.append((float)x, (float)y, (float)z); + } + + @Override protected void addVertexTexture( double x, double y ) { + active.texture.append((float)x, (float)y); + } + + @Override protected void addPoint( int vertex ) {} + + @Override protected void addLine( DogArray_I32 vertexes ) {} + + @Override protected void addFace( DogArray_I32 indexes, int vertexCount ) { + ObjFileCodec.addFactToMesh(indexes, vertexCount, active); + } + }; + + // Read in the OBJ file + try (var input = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { + reader.parse(new BufferedReader(input)); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (MultipleMaterials e) { + ignoredMaterial = true; + } + } + + /** + * Just read in the texture file names from the MTL file + */ + void readTextureFilesFromMTL( File file ) { + String materialName = ""; + try (var input = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { + String line = input.readLine(); + while (line != null) { + try { + // Find the first word + int idx1 = line.indexOf(' '); + if (idx1 == -1) + continue; + + // Find the second word + int idx2 = line.indexOf(' ', idx1 + 1); + + // See if it's at the end of the line + if (idx2 == -1) + idx2 = line.length(); + + String command = line.substring(0, idx1); + String value = line.substring(idx1 + 1, idx2); + + switch (command) { + case "newmtl" -> materialName = value; + case "map_Kd" -> materialToTextureFile.put(materialName, value); + } + } finally { + line = input.readLine(); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** Thrown as a way to escape if it starts to read a second material and a mesh was passed in */ + static class MultipleMaterials extends RuntimeException {} +} diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyCodec.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyCodec.java index 8df66ab78a..23800d27fb 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyCodec.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyCodec.java @@ -411,11 +411,23 @@ public static void readCloud( InputStream input, PointCloudWriter output ) throw output.initialize(vertexes, color); } - @Override public void addVertex( double x, double y, double z, int rgb ) { - output.add(x, y, z, rgb); + @Override public void startVertex() { + output.startPoint(); } - @Override public void addVertexNormal( double nx, double ny, double nz ) {} + @Override public void stopVertex() { + output.stopPoint(); + } + + @Override public void setVertexLocation( double x, double y, double z ) { + output.location(x, y, z); + } + + @Override public void setVertexColor( int r, int g, int b ) { + output.color(r << 16 | g << 8 | b); + } + + @Override public void setVertexNormal( double nx, double ny, double nz ) {} @Override public void addPolygon( int[] indexes, int offset, int length ) {} @@ -425,27 +437,33 @@ public static void readCloud( InputStream input, PointCloudWriter output ) throw }); } - public static void readMesh( InputStream input, VertexMesh mesh, DogArray_I32 colorRGB ) throws IOException { + public static void readMesh( InputStream input, VertexMesh mesh ) throws IOException { read(input, new PlyReader() { @Override public void initialize( int vertexes, int triangles, boolean color ) { - colorRGB.reset(); mesh.reset(); mesh.vertexes.reserve(vertexes); - mesh.indexes.reserve(triangles*3); + mesh.faceVertexes.reserve(triangles*3); } - @Override public void addVertex( double x, double y, double z, int rgb ) { + @Override public void startVertex() {} + + @Override public void stopVertex() {} + + @Override public void setVertexLocation( double x, double y, double z ) { mesh.vertexes.append(x, y, z); - colorRGB.add(rgb); } - @Override public void addVertexNormal( double nx, double ny, double nz ) { - mesh.vertexNormals.append((float)nx, (float)ny, (float)nz); + @Override public void setVertexColor( int r, int g, int b ) { + mesh.rgb.add(r << 16 | g << 8 | b); + } + + @Override public void setVertexNormal( double nx, double ny, double nz ) { + mesh.normals.append((float)nx, (float)ny, (float)nz); } @Override public void addPolygon( int[] indexes, int offset, int length ) { - mesh.offsets.add(mesh.indexes.size + length); - mesh.indexes.addAll(indexes, offset, offset + length); + mesh.faceOffsets.add(mesh.faceVertexes.size + length); + mesh.faceVertexes.addAll(indexes, offset, offset + length); } @Override public void setTextureName( String textureName ) { @@ -517,11 +535,16 @@ private static void readAscii( PlyReader output, InputStream reader, Header head } } } - output.addVertex(x, y, z, r << 16 | g << 8 | b); + // Creates the vertex and specify its attributes + output.startVertex(); + output.setVertexLocation(x, y, z); + if (header.rgb) + output.setVertexColor(r, g, b); if (header.normals) { - output.addVertexNormal(nx, ny, nz); + output.setVertexNormal(nx, ny, nz); } + output.stopVertex(); } int[] indexes = new int[100]; @@ -607,11 +630,15 @@ private static void readCloudBinary( PlyReader output, InputStream reader, Heade } } - output.addVertex(x, y, z, r << 16 | g << 8 | b); + // Creates the vertex and specify its attributes + output.startVertex(); + output.setVertexLocation(x, y, z); + if (header.rgb) + output.setVertexColor(r, g, b); if (header.normals) { - output.addVertexNormal(nx, ny, nz); + output.setVertexNormal(nx, ny, nz); } - + output.stopVertex(); //System.out.printf("vertex: %.1e %.1e %.1e | %2x %2x %2x\n", x, y, z, r, g, b); } @@ -688,7 +715,7 @@ private static PlyWriter wrapMeshForWriting( VertexMesh mesh, @Nullable DogArray return new PlyWriter() { @Override public int getVertexCount() {return mesh.vertexes.size();} - @Override public int getPolygonCount() {return mesh.offsets.size - 1;} + @Override public int getPolygonCount() {return mesh.faceOffsets.size - 1;} @Override public boolean isColor() {return colorRGB != null;} @@ -697,7 +724,7 @@ private static PlyWriter wrapMeshForWriting( VertexMesh mesh, @Nullable DogArray } @Override public boolean isVertexNormals() { - return mesh.vertexNormals.size() > 0; + return mesh.normals.size() > 0; } @Override public String getTextureName() { @@ -707,7 +734,7 @@ private static PlyWriter wrapMeshForWriting( VertexMesh mesh, @Nullable DogArray @Override public void getVertex( int which, Point3D_F64 vertex ) {mesh.vertexes.getCopy(which, vertex);} @Override public void getVertexNormal( int which, GeoTuple3D_F64 normal ) { - Point3D_F32 tmp = mesh.vertexNormals.getTemp(which); + Point3D_F32 tmp = mesh.normals.getTemp(which); normal.setTo(tmp.x, tmp.y, tmp.z); } @@ -715,19 +742,19 @@ private static PlyWriter wrapMeshForWriting( VertexMesh mesh, @Nullable DogArray @Override public int getColor( int which ) {return colorRGB.get(which);} @Override public int getIndexes( int which, int[] indexes ) { - int idx0 = mesh.offsets.get(which); - int idx1 = mesh.offsets.get(which + 1); + int idx0 = mesh.faceOffsets.get(which); + int idx1 = mesh.faceOffsets.get(which + 1); for (int i = idx0; i < idx1; i++) { - indexes[i - idx0] = mesh.indexes.get(i); + indexes[i - idx0] = mesh.faceVertexes.get(i); } return idx1 - idx0; } @Override public int getTextureCoors( int which, float[] coordinates ) { - int idx0 = mesh.offsets.get(which); - int idx1 = mesh.offsets.get(which + 1); + int idx0 = mesh.faceOffsets.get(which); + int idx1 = mesh.faceOffsets.get(which + 1); int idxArray = 0; for (int i = idx0; i < idx1; i++) { diff --git a/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyReader.java b/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyReader.java index 16d7e3b8b6..7e2e5bb137 100644 --- a/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyReader.java +++ b/main/boofcv-io/src/main/java/boofcv/io/points/impl/PlyReader.java @@ -26,9 +26,17 @@ public interface PlyReader { void initialize( int vertexes, int triangles, boolean color ); - void addVertex( double x, double y, double z, int rgb ); + /** Creates a new vertex */ + void startVertex(); - void addVertexNormal( double nx, double ny, double nz ); + /** Done adding attributes to the vertex and saves the results */ + void stopVertex(); + + void setVertexLocation( double x, double y, double z ); + + void setVertexColor( int r, int g, int b ); + + void setVertexNormal( double nx, double ny, double nz ); void addPolygon( int[] indexes, int offset, int length ); diff --git a/main/boofcv-io/src/main/java/boofcv/visualize/RenderMesh.java b/main/boofcv-io/src/main/java/boofcv/visualize/RenderMesh.java index cac6aa9ab6..6ba22643f4 100644 --- a/main/boofcv-io/src/main/java/boofcv/visualize/RenderMesh.java +++ b/main/boofcv-io/src/main/java/boofcv/visualize/RenderMesh.java @@ -189,10 +189,10 @@ public void render( VertexMesh mesh ) { // Decide if it should texture map or user a per shape color final boolean useColorizer = forceColorizer || mesh.texture.size() == 0; - for (int shapeIdx = 1; shapeIdx < mesh.offsets.size; shapeIdx++) { + for (int shapeIdx = 1; shapeIdx < mesh.faceOffsets.size; shapeIdx++) { // First and last point in the polygon - final int idx0 = mesh.offsets.get(shapeIdx - 1); - final int idx1 = mesh.offsets.get(shapeIdx); + final int idx0 = mesh.faceOffsets.get(shapeIdx - 1); + final int idx1 = mesh.faceOffsets.get(shapeIdx); // skip pathological case if (idx0 >= idx1) @@ -213,7 +213,7 @@ public void render( VertexMesh mesh ) { boolean behindCamera = false; for (int i = idx0; i < idx1; i++) { - Point3D_F64 world = mesh.vertexes.getTemp(mesh.indexes.get(i)); + Point3D_F64 world = mesh.vertexes.getTemp(mesh.faceVertexes.get(i)); worldToView.transform(world, camera); // If any part is behind the camera skip it. While not ideal this keeps the code simple, @@ -256,10 +256,10 @@ public void render( VertexMesh mesh ) { */ static boolean isFrontVisible( VertexMesh mesh, int shapeIdx, int idx0, Point3D_F64 worldCamera ) { // Get normal in world coordinates - Point3D_F32 normal = mesh.faceNormals.getTemp(shapeIdx); + Point3D_F32 normal = mesh.getFaceNormalTmp(shapeIdx); // vector from the camera to a vertex - Point3D_F64 v1 = mesh.vertexes.getTemp(mesh.indexes.get(idx0)); + Point3D_F64 v1 = mesh.vertexes.getTemp(mesh.faceVertexes.get(idx0)); v1.x -= worldCamera.x; v1.y -= worldCamera.y; v1.z -= worldCamera.z; diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/TestStlDataStructure.java b/main/boofcv-io/src/test/java/boofcv/io/points/TestStlDataStructure.java index 952c950e9d..cd0d39b3f8 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/TestStlDataStructure.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/TestStlDataStructure.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -127,7 +127,7 @@ class TestStlDataStructure extends BoofStandardJUnit { assertEquals(0.0, mesh.getShapeVertex(5, null).distance(0, 0, 0), UtilEjml.TEST_F64); assertEquals(3, mesh.vertexes.size()); - assertEquals(3, mesh.offsets.get(1)); - assertEquals(6, mesh.offsets.get(2)); + assertEquals(3, mesh.faceOffsets.get(1)); + assertEquals(6, mesh.faceOffsets.get(2)); } } diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileCodec.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileCodec.java index 103aa17112..f9aba7cdf2 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileCodec.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -24,6 +24,7 @@ import boofcv.testing.BoofStandardJUnit; import georegression.struct.point.Point3D_F64; import org.ddogleg.struct.DogArray; +import org.ddogleg.struct.DogArray_I32; import org.ejml.UtilEjml; import org.junit.jupiter.api.Test; @@ -31,7 +32,6 @@ import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; -import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,33 +39,62 @@ public class TestObjFileCodec extends BoofStandardJUnit { @Test void encode_decode_cloud() throws IOException { - List expected = new ArrayList<>(); + encode_decode_cloud(false); + encode_decode_cloud(true); + } + + void encode_decode_cloud( boolean colorTest ) throws IOException { + var expectedP = new ArrayList(); + var expectedColors = new DogArray_I32(); for (int i = 0; i < 10; i++) { - expected.add(new Point3D_F64(i*123.45, i - 1.01, i + 2.34)); + expectedP.add(new Point3D_F64(i*123.45, i - 1.01, i + 2.34)); + expectedColors.add(i*125); } - var found = new DogArray<>(Point3D_F64::new); - var output = new StringWriter(); - ObjFileCodec.save(PointCloudReader.wrapF64(expected), output); + if (colorTest) { + ObjFileCodec.save(PointCloudReader.wrapF64(expectedP, expectedColors.data), output); + } else { + ObjFileCodec.save(PointCloudReader.wrapF64(expectedP), output); + } var input = new ByteArrayInputStream(output.toString().getBytes(UTF_8)); - ObjFileCodec.load(input, PointCloudWriter.wrapF64(found)); - assertEquals(expected.size(), found.size); - for (int i = 0; i < found.size; i++) { - assertEquals(0.0, found.get(i).distance(expected.get(i)), UtilEjml.TEST_F64); + var foundP = new DogArray<>(Point3D_F64::new); + var foundColors = new DogArray_I32(); + + if (colorTest) { + ObjFileCodec.load(input, PointCloudWriter.wrapF64(foundP, foundColors)); + } else { + ObjFileCodec.load(input, PointCloudWriter.wrapF64(foundP)); + } + + if (colorTest) { + assertTrue(expectedColors.isEquals(foundColors)); + } + + assertEquals(expectedP.size(), foundP.size); + for (int i = 0; i < foundP.size; i++) { + assertEquals(0.0, foundP.get(i).distance(expectedP.get(i)), UtilEjml.TEST_F64); } } @Test void encode_decode_mesh() throws IOException { + encode_decode_mesh(false); + encode_decode_mesh(true); + } + + void encode_decode_mesh( boolean colorTest ) throws IOException { var mesh = new VertexMesh(); for (int i = 0; i < 10; i++) { mesh.vertexes.append(i, 2, 3); + if (colorTest) + mesh.rgb.add(i*18); + for (int foo = 0; foo < 3; foo++) { int vertIndex = (i*3 + foo)%10; - mesh.indexes.add(vertIndex); + mesh.faceVertexes.add(vertIndex); } - mesh.offsets.add(mesh.indexes.size); + mesh.faceOffsets.add(mesh.faceVertexes.size); } var output = new StringWriter(); ObjFileCodec.save(mesh, output); @@ -76,8 +105,10 @@ public class TestObjFileCodec extends BoofStandardJUnit { ObjFileCodec.load(input, foundMesh); assertEquals(mesh.vertexes.size(), mesh.vertexes.size()); - assertTrue(mesh.indexes.isEquals(foundMesh.indexes)); - assertTrue(mesh.offsets.isEquals(foundMesh.offsets)); + assertTrue(mesh.faceVertexes.isEquals(foundMesh.faceVertexes)); + assertTrue(mesh.faceOffsets.isEquals(foundMesh.faceOffsets)); + + assertTrue(mesh.rgb.isEquals(foundMesh.rgb)); for (int i = 0; i < mesh.vertexes.size(); i++) { Point3D_F64 expected = mesh.vertexes.getTemp(i); @@ -86,7 +117,7 @@ public class TestObjFileCodec extends BoofStandardJUnit { } for (int i = 0; i < mesh.vertexes.size(); i++) { - assertEquals(mesh.indexes.get(i), foundMesh.indexes.get(i)); + assertEquals(mesh.faceVertexes.get(i), foundMesh.faceVertexes.get(i)); } } } diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileReader.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileReader.java index 099f3efbf9..615c6b54f3 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileReader.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -19,6 +19,7 @@ package boofcv.io.points.impl; import boofcv.testing.BoofStandardJUnit; +import georegression.struct.point.Point2D_F64; import georegression.struct.point.Point3D_F64; import org.ddogleg.struct.DogArray; import org.ddogleg.struct.DogArray_I32; @@ -51,7 +52,12 @@ public class TestObjFileReader extends BoofStandardJUnit { vertexes.grow().setTo(x, y, z); } - @Override protected void addFace( DogArray_I32 vertexes ) { + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + throw new RuntimeException("Egads"); + } + + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) { assertEquals(3, vertexes.size); assertTrue(vertexes.isEquals(0, 1, 2)); } @@ -64,6 +70,100 @@ public class TestObjFileReader extends BoofStandardJUnit { assertEquals(0.0, vertexes.get(2).distance(1, 0, 0), UtilEjml.TEST_F64); } + /** + * Simple file with vertex, texture, and normals + */ + @Test void case1() throws IOException { + String text = """ + v 0.0 0.0 0.0 + v 0.0 1.0 0.0 + v 1.0 0.0 0.0 + vt 0.0 0.0 + vt 0.1 1.0 + vt 1.1 0.1 + vn 0.0 0.0 -1.0 + vn 0.0 -1.0 0.0 + vn -1.0 0.0 0.0 + f 1/1/1 2/2/2 3/3/3 + """; + + var vertexes = new DogArray<>(Point3D_F64::new, Point3D_F64::zero); + var normals = new DogArray<>(Point3D_F64::new, Point3D_F64::zero); + var textures = new DogArray<>(Point2D_F64::new, Point2D_F64::zero); + + var reader = new DummyReader() { + @Override protected void addVertex( double x, double y, double z ) { + vertexes.grow().setTo(x, y, z); + } + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + vertexes.grow().setTo(x, y, z); + } + + @Override protected void addVertexNormal( double x, double y, double z ) {normals.grow().setTo(x, y, z);} + + @Override protected void addVertexTexture( double x, double y ) {textures.grow().setTo(x, y);} + + @Override protected void addFace( DogArray_I32 indexes, int vertexCount ) { + assertEquals(3, vertexCount); + assertEquals(vertexCount*3, indexes.size); + assertTrue(indexes.isEquals(0, 0, 0, 1, 1, 1, 2, 2, 2)); + } + }; + reader.parse(new BufferedReader(new StringReader(text))); + + assertEquals(3, vertexes.size); + assertEquals(0.0, vertexes.get(0).distance(0, 0, 0), UtilEjml.TEST_F64); + assertEquals(0.0, vertexes.get(1).distance(0, 1, 0), UtilEjml.TEST_F64); + assertEquals(0.0, vertexes.get(2).distance(1, 0, 0), UtilEjml.TEST_F64); + assertEquals(3, normals.size); + assertEquals(0.0, normals.get(0).distance(0, 0, -1), UtilEjml.TEST_F64); + assertEquals(0.0, normals.get(1).distance(0, -1, 0), UtilEjml.TEST_F64); + assertEquals(0.0, normals.get(2).distance(-1, 0, 0), UtilEjml.TEST_F64); + assertEquals(3, textures.size); + assertEquals(0.0, textures.get(0).distance(0, 0), UtilEjml.TEST_F64); + assertEquals(0.0, textures.get(1).distance(0.1, 1), UtilEjml.TEST_F64); + assertEquals(0.0, textures.get(2).distance(1.1, 0.1), UtilEjml.TEST_F64); + } + + /** test case where vectors also have colors */ + @Test void vectorColor() throws IOException { + String text = """ + # Simple Wavefront file + v 0.0 0.0 0.0 0.2 0.3 0.4 + v 0.0 1.0 0.0 0.3 0.2 0.1 + v 1.0 0.0 0.0 0.4 0.3 0.2 + """; + + var vertexes = new DogArray<>(Point3D_F64::new, Point3D_F64::zero); + var colors = new DogArray<>(Point3D_F64::new, Point3D_F64::zero); + + var reader = new DummyReader() { + @Override protected void addVertex( double x, double y, double z ) { + vertexes.grow().setTo(x, y, z); + } + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + vertexes.grow().setTo(x, y, z); + colors.grow().setTo(red, green, blue); + } + + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) {} + }; + reader.parse(new BufferedReader(new StringReader(text))); + + assertEquals(3, vertexes.size); + assertEquals(0.0, vertexes.get(0).distance(0, 0, 0), UtilEjml.TEST_F64); + assertEquals(0.0, vertexes.get(1).distance(0, 1, 0), UtilEjml.TEST_F64); + assertEquals(0.0, vertexes.get(2).distance(1, 0, 0), UtilEjml.TEST_F64); + assertEquals(3, colors.size); + assertEquals(0.0, colors.get(0).distance(0.2, 0.3, 0.4), UtilEjml.TEST_F64); + assertEquals(0.0, colors.get(1).distance(0.3, 0.2, 0.1), UtilEjml.TEST_F64); + assertEquals(0.0, colors.get(2).distance(0.4, 0.3, 0.2), UtilEjml.TEST_F64); + } + /** * See if negative indexes are handled correct. Add two faces. Negative to be relative to current number * of read in vertexes @@ -84,9 +184,7 @@ public class TestObjFileReader extends BoofStandardJUnit { var reader = new DummyReader() { int count = 0; - @Override protected void addVertex( double x, double y, double z ) {} - - @Override protected void addFace( DogArray_I32 vertexes ) { + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) { assertEquals(3, vertexes.size); if (count == 0) { assertTrue(vertexes.isEquals(2, 1, 0)); @@ -117,7 +215,12 @@ public class TestObjFileReader extends BoofStandardJUnit { @Override protected void addVertex( double x, double y, double z ) {vertexCount++;} - @Override protected void addFace( DogArray_I32 vertexes ) { + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + vertexCount++; + } + + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) { assertEquals(6, vertexes.size); assertTrue(vertexes.isEquals(2, 1, 0, 0, 1, 2)); } @@ -128,9 +231,7 @@ public class TestObjFileReader extends BoofStandardJUnit { } @Test void ensureIndex() { - var reader = new DummyReader() { - @Override protected void addVertex( double x, double y, double z ) {} - }; + var reader = new DummyReader(){}; // Vertexes are stored in 1-index but need to make sure it's converted to 0-index assertEquals(0, reader.ensureIndex(1)); @@ -144,6 +245,19 @@ public class TestObjFileReader extends BoofStandardJUnit { } static abstract class DummyReader extends ObjFileReader { + @Override protected void addLibrary( String name ) {} + + @Override protected void addMaterial( String name ) {} + + @Override protected void addVertex( double x, double y, double z ) {} + + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) {} + + @Override protected void addVertexNormal( double x, double y, double z ) {} + + @Override protected void addVertexTexture( double x, double y ) {} + @Override protected void addPoint( int vertex ) { fail("there are no points"); } @@ -152,7 +266,7 @@ static abstract class DummyReader extends ObjFileReader { fail("there are no lines"); } - @Override protected void addFace( DogArray_I32 vertexes ) { + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) { fail("there are no faces"); } } diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileWriter.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileWriter.java index a0aa5cfaa8..a79f865662 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileWriter.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -39,17 +39,25 @@ public class TestObjFileWriter extends BoofStandardJUnit { var alg = new ObjFileWriter(output); alg.addVertex(1, 0, 0); alg.addVertex(0, 1, 0); - alg.addVertex(0, 0, 1); - alg.addFace(DogArray_I32.array(0, 1, 2)); + alg.addVertex(0, 0, 1, 0.1, 0.2, 0.3); + alg.addTextureVertex(1, 0); + alg.addTextureVertex(0, 1); + alg.addTextureVertex(1, 1); + alg.addFace(DogArray_I32.array(0, 1, 2), 1); alg.addComment("Foo Bar"); alg.addVertex(2, 0, 0); alg.addVertex(0, 2, 0); alg.addVertex(0, 0, 2); - alg.addFace(null); + alg.addTextureVertex(2, 0); + alg.addTextureVertex(0, 2); + alg.addTextureVertex(2, 2); + alg.addFace(null, 0); String text = output.toString(); var reader = new TestObjFileReader.DummyReader() { int countVertex = 0; + int countVertexColor = 0; + int countTexture = 0; int countFaces = 0; @Override protected void addVertex( double x, double y, double z ) { @@ -64,7 +72,30 @@ public class TestObjFileWriter extends BoofStandardJUnit { countVertex++; } - @Override protected void addFace( DogArray_I32 vertexes ) { + @Override + protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) { + addVertex(x, y, z); + assertEquals(0.1, red ); + assertEquals(0.2, green ); + assertEquals(0.3, blue ); + countVertexColor++; + } + + @Override protected void addVertexNormal( double x, double y, double z ) {} + + @Override protected void addVertexTexture( double x, double y ) { + double expected = countTexture < 3 ? 1.0 : 2.0; + double found = switch (countTexture%3) { + case 0 -> x; + case 1 -> y; + case 2 -> expected; + default -> throw new RuntimeException(); + }; + assertEquals(expected, found); + countTexture++; + } + + @Override protected void addFace( DogArray_I32 vertexes, int vertexCount ) { switch (countVertex) { case 0 -> assertTrue(vertexes.isEquals(0, 1, 2)); case 1 -> assertTrue(vertexes.isEquals(3, 4, 5)); @@ -74,6 +105,7 @@ public class TestObjFileWriter extends BoofStandardJUnit { }; reader.parse(new BufferedReader(new StringReader(text))); assertEquals(6, reader.countVertex); + assertEquals(1, reader.countVertexColor); assertEquals(2, reader.countFaces); } } diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjLoadFileSystem.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjLoadFileSystem.java new file mode 100644 index 0000000000..f97f21f6bc --- /dev/null +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestObjLoadFileSystem.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. + * + * This file is part of BoofCV (http://boofcv.org). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package boofcv.io.points.impl; + +import boofcv.io.UtilIO; +import boofcv.struct.mesh.VertexMesh; +import boofcv.testing.BoofStandardJUnit; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestObjLoadFileSystem extends BoofStandardJUnit { + private final static String fileObj = "file.obj"; + private final static String fileMtl = "file.mtl"; + + File workDir; + + @BeforeEach public void setup() { + try { + workDir = Files.createTempDirectory("obj").toFile(); + } catch (IOException ignore) { + } + } + + @AfterEach public void teardown() { + UtilIO.deleteRecursive(workDir); + } + + /** + * No texture mapping and everything is contained in a single file + */ + @Test void noMaterials() { + String text = """ + v 0.0 0.0 0.0 + v 0.0 1.0 0.0 + f 1 2 3 + """; + save(fileObj, text); + + var alg = new ObjLoadFromFiles(); + alg.load(new File(workDir, fileObj), null); + Map found = alg.getShapeToMesh(); + assertEquals(1, found.size()); + + VertexMesh mesh = found.get(""); + + assertEquals(2, mesh.vertexes.size()); + assertEquals(1, mesh.size()); + } + + @Test void multipleMaterials() { + createMultipleMaterialFiles(); + + var alg = new ObjLoadFromFiles(); + alg.load(new File(workDir, fileObj), null); + Map found = alg.getShapeToMesh(); + assertEquals(2, found.size()); + + VertexMesh mesh1 = found.get("a"); + assertEquals("a.jpg", mesh1.textureName); + assertEquals(2, mesh1.vertexes.size()); + assertEquals(0, mesh1.faceNormals.size()); + + + VertexMesh mesh2 = found.get("b"); + assertEquals("b.jpg", mesh2.textureName); + assertEquals(2, mesh2.vertexes.size()); + assertEquals(1, mesh2.size()); + assertEquals(0, mesh2.vertexes.getTemp(0).distance(1.0, 0, 0)); + assertEquals(0, mesh2.vertexes.getTemp(1).distance(0.0, 2, 0)); + } + + @Test void singleMesh_noMaterial() { + String text = """ + v 0.0 0.0 0.0 + v 0.0 1.0 0.0 + f 1 2 3 + """; + save(fileObj, text); + + var mesh = new VertexMesh(); + mesh.rgb.add(1); // test to see if it calls reset + + var alg = new ObjLoadFromFiles(); + alg.load(new File(workDir, fileObj), mesh); + Map found = alg.getShapeToMesh(); + + assertFalse(alg.isIgnoredMaterial()); + assertEquals(1, found.size()); + assertSame(mesh, found.get("")); + + assertEquals(2, mesh.vertexes.size()); + assertEquals(1, mesh.size()); + assertEquals(0, mesh.rgb.size()); + } + + @Test void singleMesh_multipleMaterial() { + createMultipleMaterialFiles(); + + var mesh = new VertexMesh(); + mesh.rgb.add(1); // test to see if it calls reset + + var alg = new ObjLoadFromFiles(); + alg.load(new File(workDir, fileObj), mesh); + Map found = alg.getShapeToMesh(); + assertTrue(alg.isIgnoredMaterial()); + + assertEquals(1, found.size()); + assertSame(mesh, found.get("a")); + + assertEquals(2, mesh.vertexes.size()); + assertEquals(0, mesh.vertexes.getTemp(0).distance(0.0, 0, 0)); + assertEquals(0, mesh.vertexes.getTemp(1).distance(0.0, 1, 0)); + assertEquals(0, mesh.size()); + assertEquals(0, mesh.rgb.size()); + } + + private void save( String fileName, String text ) { + try { + FileUtils.writeStringToFile(new File(workDir, fileName), text, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void createMultipleMaterialFiles() { + String textObj = """ + mtllib file.mtl + usemtl a + v 0.0 0.0 0.0 + v 0.0 1.0 0.0 + usemtl b + v 1.0 0.0 0.0 + v 0.0 2.0 0.0 + f 1 2 3 + """; + save(fileObj, textObj); + + String textMtl = """ + newmtl a + map_Kd a.jpg + newmtl b + map_Kd b.jpg + """; + save(fileMtl, textMtl); + } +} diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestPlyCodec.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestPlyCodec.java index 6e33669fb2..4202be93b6 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestPlyCodec.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestPlyCodec.java @@ -140,17 +140,17 @@ class TestPlyCodec extends BoofStandardJUnit { for (var endian : new ByteOrder[]{ByteOrder.LITTLE_ENDIAN, ByteOrder.BIG_ENDIAN}) { var mesh = new VertexMesh(); mesh.textureName = "foo"; - mesh.offsets.add(0); + mesh.faceOffsets.add(0); int numVertexes = 10; for (int i = 0; i < numVertexes; i++) { mesh.vertexes.append(i, 2, 3); - mesh.vertexNormals.append(i, 2, 3); + mesh.normals.append(i, 2, 3); // bound indexes to ensure they are in the valid range - mesh.indexes.add((i*3)%numVertexes); - mesh.indexes.add((i*3 + 1)%numVertexes); - mesh.indexes.add((i*3 + 2)%numVertexes); - mesh.offsets.add(mesh.indexes.size); + mesh.faceVertexes.add((i*3)%numVertexes); + mesh.faceVertexes.add((i*3 + 1)%numVertexes); + mesh.faceVertexes.add((i*3 + 2)%numVertexes); + mesh.faceOffsets.add(mesh.faceVertexes.size); for (int idxPoly = 0; idxPoly < 3; idxPoly++) { mesh.texture.append(1, idxPoly); @@ -165,27 +165,26 @@ class TestPlyCodec extends BoofStandardJUnit { PlyCodec.saveMeshBinary(mesh, colors, endian, true, output); var foundMesh = new VertexMesh(); - var foundColors = new DogArray_I32(); var input = new ByteArrayInputStream(output.toByteArray()); - PlyCodec.readMesh(input, foundMesh, foundColors); + PlyCodec.readMesh(input, foundMesh); assertEquals(mesh.vertexes.size(), mesh.vertexes.size()); - assertEquals(mesh.vertexNormals.size(), mesh.vertexNormals.size()); + assertEquals(mesh.normals.size(), mesh.normals.size()); assertEquals(mesh.texture.size(), mesh.texture.size()); - assertTrue(mesh.indexes.isEquals(foundMesh.indexes)); - assertTrue(mesh.offsets.isEquals(foundMesh.offsets)); - assertTrue(colors.isEquals(foundColors)); + assertTrue(mesh.faceVertexes.isEquals(foundMesh.faceVertexes)); + assertTrue(mesh.faceOffsets.isEquals(foundMesh.faceOffsets)); + assertTrue(colors.isEquals(foundMesh.rgb)); for (int i = 0; i < mesh.vertexes.size(); i++) { Point3D_F64 expected = mesh.vertexes.getTemp(i); Point3D_F64 found = foundMesh.vertexes.getTemp(i); assertEquals(0.0, expected.distance(found)); - assertTrue(mesh.vertexNormals.getTemp(i).isIdentical(foundMesh.vertexNormals.getTemp(i), 1e-4f)); + assertTrue(mesh.normals.getTemp(i).isIdentical(foundMesh.normals.getTemp(i), 1e-4f)); } for (int i = 0; i < mesh.vertexes.size(); i++) { - assertEquals(mesh.indexes.get(i), foundMesh.indexes.get(i)); + assertEquals(mesh.faceVertexes.get(i), foundMesh.faceVertexes.get(i)); } for (int i = 0; i < mesh.texture.size(); i++) { @@ -206,7 +205,7 @@ class TestPlyCodec extends BoofStandardJUnit { var input = new ByteArrayInputStream(output.toByteArray()); var foundMesh = new VertexMesh(); - PlyCodec.readMesh(input, foundMesh, new DogArray_I32()); + PlyCodec.readMesh(input, foundMesh); assertEquals("foo", foundMesh.textureName); } diff --git a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestStlFileWriter.java b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestStlFileWriter.java index 10ae334d96..a86ee03900 100644 --- a/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestStlFileWriter.java +++ b/main/boofcv-io/src/test/java/boofcv/io/points/impl/TestStlFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -189,8 +189,8 @@ private static VertexMesh createSquare() { mesh.vertexes.append(1, 0, 0); mesh.vertexes.append(1, 1, 0); mesh.vertexes.append(0, 1, 0); - mesh.indexes.addAll(new int[]{0, 1, 2, 3}, 0 , 4); - mesh.offsets.add(4); + mesh.faceVertexes.addAll(new int[]{0, 1, 2, 3}, 0 , 4); + mesh.faceOffsets.add(4); return mesh; } diff --git a/main/boofcv-io/src/test/java/boofcv/visualize/TestRenderMesh.java b/main/boofcv-io/src/test/java/boofcv/visualize/TestRenderMesh.java index def2d5bf70..6ca9fe453a 100644 --- a/main/boofcv-io/src/test/java/boofcv/visualize/TestRenderMesh.java +++ b/main/boofcv-io/src/test/java/boofcv/visualize/TestRenderMesh.java @@ -44,8 +44,8 @@ public class TestRenderMesh extends BoofStandardJUnit { mesh.vertexes.append(1, -1, 10); mesh.vertexes.append(1, 1, 10); mesh.vertexes.append(-1, 1, 10); - mesh.indexes.addAll(DogArray_I32.array(0, 1, 2, 3)); - mesh.offsets.add(4); + mesh.faceVertexes.addAll(DogArray_I32.array(0, 1, 2, 3)); + mesh.faceOffsets.add(4); // Configure var alg = new RenderMesh(); @@ -200,17 +200,19 @@ public class TestRenderMesh extends BoofStandardJUnit { // This should pass mesh.reset(); - mesh.indexes.add(0); - mesh.faceNormals.append((float)-c, (float)-s, 0); + mesh.faceNormals.add(0); + mesh.normals.append((float)-c, (float)-s, 0); + mesh.faceVertexes.add(0); mesh.vertexes.append(r*c, 2 + r*s, 2); assertTrue(RenderMesh.isFrontVisible(mesh, 0, 0, pointCam)); // This should fail mesh.reset(); - mesh.indexes.add(0); - mesh.faceNormals.append((float)c, (float)s, 0); + mesh.faceVertexes.add(0); mesh.vertexes.append(r*c, 2 + r*s, 2); + mesh.faceNormals.add(0); + mesh.normals.append((float)c, (float)s, 0); assertFalse(RenderMesh.isFrontVisible(mesh, 0, 0, pointCam)); } diff --git a/main/boofcv-reconstruction/src/main/java/boofcv/alg/meshing/DepthImageToMeshGridSample.java b/main/boofcv-reconstruction/src/main/java/boofcv/alg/meshing/DepthImageToMeshGridSample.java index a42dc228c0..2a4479bb66 100644 --- a/main/boofcv-reconstruction/src/main/java/boofcv/alg/meshing/DepthImageToMeshGridSample.java +++ b/main/boofcv-reconstruction/src/main/java/boofcv/alg/meshing/DepthImageToMeshGridSample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -100,7 +100,7 @@ public void processDisparity( DisparityParameters parameters, GrayF32 disparity, if (!checkAddIndexForIDisparity(x1, y, d3, parameters)) continue; - mesh.offsets.add(mesh.indexes.size); + mesh.faceOffsets.add(mesh.faceVertexes.size); } } } @@ -124,7 +124,7 @@ private boolean checkAddIndexForIDisparity( int x, int y, float disparity, // save where they came from, used to add color later vertexPixels.grow().setTo(x, y); } - mesh.indexes.add(index); + mesh.faceVertexes.add(index); return true; } @@ -172,7 +172,7 @@ public void processInvDepth( GrayF32 inverseDepth, checkAddIndexForInverseDepth(x1, y1, d2, pixelToNorm); checkAddIndexForInverseDepth(x1, y, d3, pixelToNorm); - mesh.offsets.add(mesh.indexes.size); + mesh.faceOffsets.add(mesh.faceVertexes.size); } } } @@ -195,6 +195,6 @@ private void checkAddIndexForInverseDepth( int x, int y, float invDepth, // save where they came from, used to add color later vertexPixels.grow().setTo(x, y); } - mesh.indexes.add(index); + mesh.faceVertexes.add(index); } } diff --git a/main/boofcv-reconstruction/src/test/java/boofcv/alg/meshing/TestDepthImageToMeshGridSample.java b/main/boofcv-reconstruction/src/test/java/boofcv/alg/meshing/TestDepthImageToMeshGridSample.java index 83522691f1..f8002284e6 100644 --- a/main/boofcv-reconstruction/src/test/java/boofcv/alg/meshing/TestDepthImageToMeshGridSample.java +++ b/main/boofcv-reconstruction/src/test/java/boofcv/alg/meshing/TestDepthImageToMeshGridSample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Peter Abeles. All Rights Reserved. + * Copyright (c) 2024, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * @@ -57,8 +57,8 @@ class TestDepthImageToMeshGridSample extends BoofStandardJUnit { VertexMesh found = alg.getMesh(); assertTrue(found.vertexes.size() >= 4); - assertTrue(found.indexes.size() >= 4); - assertTrue(found.offsets.size() >= 1); + assertTrue(found.faceVertexes.size() >= 4); + assertTrue(found.faceOffsets.size() >= 1); } private void depthToDisparityImage( CameraPinhole pinhole, DisparityParameters param, GrayF32 depth, GrayF32 disparity ) { @@ -101,8 +101,8 @@ private void depthToDisparityImage( CameraPinhole pinhole, DisparityParameters p VertexMesh found = alg.getMesh(); assertTrue(found.vertexes.size() >= 4); - assertTrue(found.indexes.size() >= 4); - assertTrue(found.offsets.size() >= 1); + assertTrue(found.faceVertexes.size() >= 4); + assertTrue(found.faceOffsets.size() >= 1); } @NotNull private SimulatePlanarWorld renderPlanarWorld() { diff --git a/main/boofcv-types/src/main/java/boofcv/struct/mesh/VertexMesh.java b/main/boofcv-types/src/main/java/boofcv/struct/mesh/VertexMesh.java index cfee48fb15..a2b80982b5 100644 --- a/main/boofcv-types/src/main/java/boofcv/struct/mesh/VertexMesh.java +++ b/main/boofcv-types/src/main/java/boofcv/struct/mesh/VertexMesh.java @@ -23,6 +23,7 @@ import boofcv.struct.packed.PackedBigArrayPoint3D_F64; import georegression.geometry.GeometryMath_F64; import georegression.struct.point.Point2D_F32; +import georegression.struct.point.Point3D_F32; import georegression.struct.point.Point3D_F64; import georegression.struct.point.Vector3D_F64; import org.ddogleg.struct.DogArray; @@ -40,31 +41,43 @@ public class VertexMesh { /** 3D location of each vertex */ public final PackedBigArrayPoint3D_F64 vertexes = new PackedBigArrayPoint3D_F64(10); - /** Which indexes correspond to each vertex in a face */ - public final DogArray_I32 indexes = new DogArray_I32(); - - /** Start index of each face + the last index */ - public final DogArray_I32 offsets = new DogArray_I32(); - - /** 2D coordinate of textures. units = fraction of width / height */ + /** 2D coordinate of textures. Corresponds to a vector. units = fraction of width / height. 0.0 to 1.0 */ public final PackedBigArrayPoint2D_F32 texture = new PackedBigArrayPoint2D_F32(); /** Optional precomputed normals for each vertex */ - public final PackedArrayPoint3D_F32 vertexNormals = new PackedArrayPoint3D_F32(); + public final PackedArrayPoint3D_F32 normals = new PackedArrayPoint3D_F32(); - /** Optional precomputed normal for each face */ - public final PackedArrayPoint3D_F32 faceNormals = new PackedArrayPoint3D_F32(); + /** Optional vertex colors in RGB format */ + public final DogArray_I32 rgb = new DogArray_I32(); + + /** Which vertex indexes correspond to normal for a face */ + public final DogArray_I32 faceVertexes = new DogArray_I32(); + + /** + * Which texture indexes correspond to normal for a face. If this array is empty then we assume that is one + * texture coordinate for each face vector that appears in the same order. + */ + public final DogArray_I32 faceVertexTextures = new DogArray_I32(); + + /** Which indexes correspond to each vertex normal in a face */ + public final DogArray_I32 faceVertexNormals = new DogArray_I32(); + + /** Specifies which normal is the normal for the face's plane */ + public final DogArray_I32 faceNormals = new DogArray_I32(); + + /** Start index of each face + the last index */ + public final DogArray_I32 faceOffsets = new DogArray_I32(); /** Name of the texture file associated with this mesh */ public String textureName = ""; { - offsets.add(0); + faceOffsets.add(0); } /** Returns number of shapes / polygons in the mesh */ public int size() { - return offsets.size - 1; + return faceOffsets.size - 1; } /** @@ -73,15 +86,15 @@ public int size() { public Point3D_F64 getShapeVertex( int which, @Nullable Point3D_F64 output ) { if (output == null) output = new Point3D_F64(); - vertexes.getCopy(indexes.get(which), output); + vertexes.getCopy(faceVertexes.get(which), output); return output; } /** - * Number of elements in a specific shapes + * Number of elements in a specific face */ - public int getShapeSize( int which ) { - return offsets.get(which + 1) - offsets.get(which); + public int getFaceSize( int which ) { + return faceOffsets.get(which + 1) - faceOffsets.get(which); } /** @@ -91,12 +104,20 @@ public int getShapeSize( int which ) { * @param output Output storage for the shape */ public void getTexture( int which, DogArray output ) { - int idx0 = offsets.get(which); - int idx1 = offsets.get(which + 1); + int idx0 = faceOffsets.get(which); + int idx1 = faceOffsets.get(which + 1); output.reset().resize(idx1 - idx0); - for (int i = idx0; i < idx1; i++) { - texture.getCopy(i, output.get(i - idx0)); + + // See if custom indexes are specified for a face's texture coordinates + if (faceVertexTextures.isEmpty()) { + for (int i = idx0; i < idx1; i++) { + texture.getCopy(i, output.get(i - idx0)); + } + } else { + for (int i = idx0; i < idx1; i++) { + texture.getCopy(faceVertexTextures.get(i), output.get(i - idx0)); + } } } @@ -113,33 +134,49 @@ public void addTexture( int count, float[] coordinates ) { } } + /** Copies the normal for the specified face inout 'out' */ + public void getFaceNormal( int face, Point3D_F32 out ) { + normals.getCopy(faceNormals.get(face), out); + } + + /** Returns the shape's normal in a temporary object */ + public Point3D_F32 getFaceNormalTmp( int face ) { + return normals.getTemp(faceNormals.get(face)); + } + /** * Copies the entire shape into the output array * * @param which Which shape to copy * @param output Output storage for the shape */ - public void getShape( int which, DogArray output ) { - int idx0 = offsets.get(which); - int idx1 = offsets.get(which + 1); + public void getFaceVectors( int which, DogArray output ) { + int idx0 = faceOffsets.get(which); + int idx1 = faceOffsets.get(which + 1); output.reset().resize(idx1 - idx0); - for (int i = idx0; i < idx1; i++) { - vertexes.getCopy(indexes.get(i), output.get(i - idx0)); + if (faceVertexes.isEmpty()) { + for (int i = idx0; i < idx1; i++) { + vertexes.getCopy(i, output.get(i - idx0)); + } + } else { + for (int i = idx0; i < idx1; i++) { + vertexes.getCopy(faceVertexes.get(i), output.get(i - idx0)); + } } } /** * Adds a new shape with the specified vertexes. */ - public void addShape( List shape ) { + public void addFaceVectors( List shape ) { // All the vertexes for this shape will reference newly added vertexes int idx0 = vertexes.size(); for (int i = 0; i < shape.size(); i++) { - indexes.add(idx0 + i); + faceVertexes.add(idx0 + i); } - offsets.add(idx0 + shape.size()); + faceOffsets.add(idx0 + shape.size()); vertexes.appendAll(shape); } @@ -152,9 +189,9 @@ public void computeFaceNormals() { var norm = new Vector3D_F64(); faceNormals.reset(); - faceNormals.reserve(offsets.size() - 1); - for (int idxShape = 1; idxShape < offsets.size; idxShape++) { - int idx0 = offsets.get(idxShape - 1); + faceNormals.reserve(faceOffsets.size() - 1); + for (int idxShape = 1; idxShape < faceOffsets.size; idxShape++) { + int idx0 = faceOffsets.get(idxShape - 1); // Use cross product to find the normal in the correct orientation pointsToVector(idx0, idx0 + 1, vector1); @@ -162,18 +199,20 @@ public void computeFaceNormals() { GeometryMath_F64.cross(vector1, vector2, norm); norm.normalize(); - faceNormals.append((float)norm.x, (float)norm.y, (float)norm.z); + + faceNormals.add(normals.size()); + normals.append((float)norm.x, (float)norm.y, (float)norm.z); } } private void pointsToVector( int idxA, int idxB, Vector3D_F64 out ) { - Point3D_F64 p = vertexes.getTemp(indexes.get(idxA)); + Point3D_F64 p = vertexes.getTemp(faceVertexes.get(idxA)); double x0 = p.x; double y0 = p.y; double z0 = p.z; - p = vertexes.getTemp(indexes.get(idxB)); + p = vertexes.getTemp(faceVertexes.get(idxB)); out.x = p.x - x0; out.y = p.y - y0; out.z = p.z - z0; @@ -181,23 +220,28 @@ private void pointsToVector( int idxA, int idxB, Vector3D_F64 out ) { public VertexMesh setTo( VertexMesh src ) { this.vertexes.setTo(src.vertexes); - this.indexes.setTo(src.indexes); - this.offsets.setTo(src.offsets); this.texture.setTo(src.texture); - this.vertexNormals.setTo(src.vertexNormals); + this.normals.setTo(src.normals); + this.rgb.setTo(src.rgb); + this.faceVertexes.setTo(src.faceVertexes); + this.faceVertexTextures.setTo(src.faceVertexTextures); this.faceNormals.setTo(src.faceNormals); + this.faceOffsets.setTo(src.faceOffsets); + this.faceVertexNormals.setTo(src.faceVertexNormals); this.textureName = src.textureName; return this; } public void reset() { vertexes.reset(); - indexes.reset(); - offsets.reset(); texture.reset(); - vertexNormals.reset(); + normals.reset(); + rgb.reset(); + faceVertexes.reset(); + faceVertexTextures.reset(); faceNormals.reset(); - offsets.add(0); + faceOffsets.reset().add(0); + faceVertexNormals.reset(); textureName = ""; } @@ -211,7 +255,7 @@ public MeshPolygonAccess toAccess() { } @Override public void getPolygon( int which, DogArray vertexes ) { - VertexMesh.this.getShape(which, vertexes); + VertexMesh.this.getFaceVectors(which, vertexes); } }; } @@ -221,6 +265,6 @@ public boolean isTextured() { } public boolean isNormals() { - return vertexNormals.size() > 0; + return normals.size() > 0; } } diff --git a/main/boofcv-types/src/test/java/boofcv/struct/mesh/TestVertexMesh.java b/main/boofcv-types/src/test/java/boofcv/struct/mesh/TestVertexMesh.java index 92369f25f9..bf29efc00d 100644 --- a/main/boofcv-types/src/test/java/boofcv/struct/mesh/TestVertexMesh.java +++ b/main/boofcv-types/src/test/java/boofcv/struct/mesh/TestVertexMesh.java @@ -36,22 +36,22 @@ public class TestVertexMesh extends BoofStandardJUnit { @Test void reset() throws Exception {checkReset(VertexMesh.class, "reset");} - @Test void addShape() { + @Test void addFaceVectors() { List shapeA = createRandomShape(3); List shapeB = createRandomShape(4); var alg = new VertexMesh(); - alg.addShape(shapeA); - alg.addShape(shapeB); + alg.addFaceVectors(shapeA); + alg.addFaceVectors(shapeB); assertEquals(2, alg.size()); assertEquals(shapeA.size() + shapeB.size(), alg.vertexes.size()); - assertEquals(alg.vertexes.size(), alg.indexes.size()); + assertEquals(alg.vertexes.size(), alg.faceVertexes.size()); var found = new DogArray<>(Point3D_F64::new); - alg.getShape(0, found); + alg.getFaceVectors(0, found); assertIdentical(shapeA, found.toList()); - alg.getShape(1, found); + alg.getFaceVectors(1, found); assertIdentical(shapeB, found.toList()); } @@ -59,9 +59,9 @@ public class TestVertexMesh extends BoofStandardJUnit { var shape = new DogArray<>(Point3D_F64::new); var alg = new VertexMesh(); - alg.addShape(shape.resize(3).toList()); - alg.addShape(shape.resize(4).toList()); - alg.addShape(shape.resize(5).toList()); + alg.addFaceVectors(shape.resize(3).toList()); + alg.addFaceVectors(shape.resize(4).toList()); + alg.addFaceVectors(shape.resize(5).toList()); alg.addTexture(3, new float[]{1,2,3,4,5,6,7,8,9,10}); alg.addTexture(4, new float[12]); @@ -88,13 +88,13 @@ public class TestVertexMesh extends BoofStandardJUnit { // create triangles that will have normals +1 and -1 var alg = new VertexMesh(); - alg.addShape(shape.toList()); + alg.addFaceVectors(shape.toList()); shape.reverse(); - alg.addShape(shape.toList()); + alg.addFaceVectors(shape.toList()); alg.computeFaceNormals(); - assertEquals(0.0, alg.faceNormals.getTemp(0).distance(0,0,-1), UtilEjml.TEST_F64); - assertEquals(0.0, alg.faceNormals.getTemp(1).distance(0,0,1), UtilEjml.TEST_F64); + assertEquals(0.0, alg.getFaceNormalTmp(0).distance(0,0,-1), UtilEjml.TEST_F64); + assertEquals(0.0, alg.getFaceNormalTmp(1).distance(0,0,1), UtilEjml.TEST_F64); // try it with a non-triangle shape.reset(); @@ -103,8 +103,9 @@ public class TestVertexMesh extends BoofStandardJUnit { shape.grow().setTo(2,2,0); shape.grow().setTo(2,9,0); alg.reset(); - alg.addShape(shape.toList()); - assertEquals(0.0, alg.faceNormals.getTemp(0).distance(0,0,-1), UtilEjml.TEST_F64); + alg.addFaceVectors(shape.toList()); + alg.computeFaceNormals(); + assertEquals(0.0, alg.getFaceNormalTmp(0).distance(0,0,-1), UtilEjml.TEST_F64); } private List createRandomShape( int count ) {