Skip to content

Commit 1162f47

Browse files
OBJ Mesh Files
- Reading texture mapped files now works PointCloudIO - Modified how it reads from files
1 parent 3249a5a commit 1162f47

File tree

11 files changed

+612
-85
lines changed

11 files changed

+612
-85
lines changed

applications/src/main/java/boofcv/app/MeshViewerApp.java

+2-15
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,11 @@
2626
import boofcv.struct.image.ImageType;
2727
import boofcv.struct.image.InterleavedU8;
2828
import boofcv.struct.mesh.VertexMesh;
29-
import org.apache.commons.io.FilenameUtils;
3029

3130
import javax.swing.*;
3231
import java.awt.*;
3332
import java.io.File;
34-
import java.io.FileInputStream;
3533
import java.io.IOException;
36-
import java.util.Locale;
3734

3835
/**
3936
* Very simple app for opening and viewing a 3D mesh
@@ -48,18 +45,8 @@ public MeshViewerApp() {
4845
private static void loadFile( File file ) {
4946
// Load the mesh
5047
var mesh = new VertexMesh();
51-
String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(Locale.ENGLISH);
52-
var type = switch (extension) {
53-
case "ply" -> PointCloudIO.Format.PLY;
54-
case "stl" -> PointCloudIO.Format.STL;
55-
case "obj" -> PointCloudIO.Format.OBJ;
56-
default -> {
57-
throw new RuntimeException("Unknown file type");
58-
}
59-
};
60-
61-
try (var input = new FileInputStream(file)) {
62-
PointCloudIO.load(type, input, mesh);
48+
try {
49+
PointCloudIO.load(file, mesh);
6350
} catch (IOException e) {
6451
e.printStackTrace(System.err);
6552
System.exit(1);

main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudReader.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2024, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -38,6 +38,9 @@ public interface PointCloudReader {
3838
*/
3939
int size();
4040

41+
/** True if each point has a color */
42+
boolean colors();
43+
4144
/**
4245
* Copies the point
4346
*/
@@ -58,6 +61,8 @@ static PointCloudReader wrap3FRGB( float[] cloud, float[] rgb, int offset, int l
5861
@Override
5962
public int size() {return length;}
6063

64+
@Override public boolean colors() {return true;}
65+
6166
@Override
6267
public void get( int index, Point3D_F32 point ) {
6368
int i = offset + index*3;
@@ -85,6 +90,8 @@ static PointCloudReader wrapF32( List<Point3D_F32> cloud ) {
8590
return new PointCloudReader() {
8691
@Override public int size() {return cloud.size();}
8792

93+
@Override public boolean colors() {return false;}
94+
8895
@Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));}
8996

9097
@Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);}
@@ -97,6 +104,8 @@ static PointCloudReader wrapF64( List<Point3D_F64> cloud ) {
97104
return new PointCloudReader() {
98105
@Override public int size() {return cloud.size();}
99106

107+
@Override public boolean colors() {return false;}
108+
100109
@Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);}
101110

102111
@Override public void get( int index, Point3D_F64 point ) {point.setTo(cloud.get(index));}
@@ -109,6 +118,8 @@ static PointCloudReader wrapF32RGB( List<Point3dRgbI_F32> cloud ) {
109118
return new PointCloudReader() {
110119
@Override public int size() {return cloud.size();}
111120

121+
@Override public boolean colors() {return true;}
122+
112123
@Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));}
113124

114125
@Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);}
@@ -121,6 +132,8 @@ static PointCloudReader wrapF64RGB( List<Point3dRgbI_F64> cloud ) {
121132
return new PointCloudReader() {
122133
@Override public int size() {return cloud.size();}
123134

135+
@Override public boolean colors() {return true;}
136+
124137
@Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);}
125138

126139
@Override public void get( int index, Point3D_F64 point ) {point.setTo(cloud.get(index));}
@@ -133,6 +146,8 @@ static PointCloudReader wrapF32( List<Point3D_F32> cloud, int[] rgb ) {
133146
return new PointCloudReader() {
134147
@Override public int size() {return cloud.size();}
135148

149+
@Override public boolean colors() {return true;}
150+
136151
@Override public void get( int index, Point3D_F32 point ) {point.setTo(cloud.get(index));}
137152

138153
@Override public void get( int index, Point3D_F64 point ) {convert(cloud.get(index), point);}
@@ -145,6 +160,8 @@ static PointCloudReader wrapF64( List<Point3D_F64> cloud, int[] rgb ) {
145160
return new PointCloudReader() {
146161
@Override public int size() {return cloud.size();}
147162

163+
@Override public boolean colors() {return true;}
164+
148165
@Override public void get( int index, Point3D_F32 point ) {convert(cloud.get(index), point);}
149166

150167
@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 ) {
159176

160177
@Override public int size() {return size;}
161178

179+
@Override public boolean colors() {return true;}
180+
162181
@Override public void get( int index, Point3D_F32 point ) {
163182
op.get(index, p);
164183
point.x = (float)p.x;

main/boofcv-geo/src/main/java/boofcv/alg/cloud/PointCloudWriter.java

+19
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ static PointCloudWriter wrapF64( DogArray<Point3D_F64> cloud ) {
128128
};
129129
}
130130

131+
static PointCloudWriter wrapF64( DogArray<Point3D_F64> cloud, DogArray_I32 colors ) {
132+
return new PointCloudWriter() {
133+
@Override public void initialize( int size, boolean hasColor ) {
134+
cloud.reserve(size);
135+
cloud.reset();
136+
colors.reserve(size);
137+
colors.reset();
138+
}
139+
140+
@Override public void startPoint() {}
141+
142+
@Override public void stopPoint() {}
143+
144+
@Override public void color( int rgb ) {colors.add(rgb);}
145+
146+
@Override public void location( double x, double y, double z ) {cloud.grow().setTo(x, y, z);}
147+
};
148+
}
149+
131150
static PointCloudWriter wrapF32RGB( DogArray<Point3dRgbI_F32> cloud ) {
132151
return new PointCloudWriter() {
133152
@Override public void initialize( int size, boolean hasColor ) {

main/boofcv-io/src/main/java/boofcv/io/points/PointCloudIO.java

+24
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.nio.ByteOrder;
3737
import java.nio.charset.StandardCharsets;
3838
import java.util.Locale;
39+
import java.util.Map;
3940

4041
/**
4142
* Code for reading different point cloud formats
@@ -95,6 +96,8 @@ public static void save3D( Format format,
9596

9697
@Override public int size() {return size;}
9798

99+
@Override public boolean colors() {return true;}
100+
98101
@Override public void get( int index, Point3D_F64 point ) {accessPoint.getPoint(index, point);}
99102

100103
@Override public int getRGB( int index ) {return accessColor.getRGB(index);}
@@ -148,6 +151,8 @@ public static void load( Format format, InputStream input, PointCloudWriter outp
148151
/**
149152
* Loads the mesh from a file. File type is determined by the file's extension.
150153
*
154+
* <p>For OBJ files, if there are multiple shapes defined then only the first one is returned.</p>
155+
*
151156
* @param file Which file it should load
152157
* @param mesh (Output) storage for the mesh
153158
*/
@@ -160,11 +165,30 @@ public static void load( File file, VertexMesh mesh ) throws IOException {
160165
default -> throw new RuntimeException("Unknown file type: " + extension);
161166
};
162167

168+
169+
// OBJ files are special. They need to read in multiple files to get the texture map image and
170+
// there can be multiple shapes defined. This will handle all those situations
171+
if (type == PointCloudIO.Format.OBJ) {
172+
var reader = new ObjLoadFromFiles();
173+
reader.load(file, mesh);
174+
return;
175+
}
176+
163177
try (var input = new FileInputStream(file)) {
164178
PointCloudIO.load(type, input, mesh);
165179
}
166180
}
167181

182+
/**
183+
* Loads a set of {@link VertexMesh} from an OBJ file. This can ready any type of OBJ file as it doesn't make
184+
* assumptions about what is contained inside of it
185+
*/
186+
public static Map<String, VertexMesh> loadObj( File file ) {
187+
var reader = new ObjLoadFromFiles();
188+
reader.load(file, null);
189+
return reader.getShapeToMesh();
190+
}
191+
168192
/**
169193
* Reads a 3D mesh from the input stream in the specified format and writes it to the output.
170194
*

main/boofcv-io/src/main/java/boofcv/io/points/impl/ObjFileCodec.java

+93-17
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import georegression.struct.point.Point2D_F32;
2525
import georegression.struct.point.Point3D_F32;
2626
import georegression.struct.point.Point3D_F64;
27+
import org.apache.commons.io.FilenameUtils;
2728
import org.ddogleg.struct.DogArray_I32;
2829

2930
import java.io.*;
@@ -44,30 +45,71 @@ public static void save( PointCloudReader cloud, Writer writer ) throws IOExcept
4445
var obj = new ObjFileWriter(writer);
4546
obj.addComment("Created by BoofCV");
4647

48+
boolean hasColor = cloud.colors();
49+
4750
var point = new Point3D_F64();
4851
int N = cloud.size();
4952
for (int i = 0; i < N; i++) {
5053
cloud.get(i, point);
51-
obj.addVertex(point.x, point.y, point.z);
54+
if (hasColor) {
55+
addRgbVertex(obj, point, cloud.getRGB(i));
56+
} else {
57+
obj.addVertex(point.x, point.y, point.z);
58+
}
5259
obj.addPoint(-1);
5360
}
5461
}
5562

63+
/**
64+
* Creates a MTL file. This is required if the mesh is textured mapped
65+
*
66+
* @param textureFile Path to texture mapped file
67+
* @param writer Where the MTL will be written to
68+
*/
69+
public static void saveMtl( String textureFile, Writer writer ) throws IOException {
70+
// Use hard coded values
71+
String text = """
72+
newmtl %s
73+
Ka 1.0 1.0 1.0
74+
Kd 1.0 1.0 1.0
75+
Ks 0.0 0.0 0.0
76+
d 1.0
77+
Ns 0.0
78+
illum 0
79+
map_Kd %s""";
80+
81+
String baseName = FilenameUtils.getBaseName(textureFile);
82+
writer.write(String.format(text, baseName, textureFile));
83+
}
84+
5685
public static void save( VertexMesh mesh, Writer writer ) throws IOException {
5786
var obj = new ObjFileWriter(writer);
5887
obj.addComment("Created by BoofCV");
5988

89+
// If there's a texture file, add a material
90+
if (!mesh.textureName.isEmpty()) {
91+
String baseName = FilenameUtils.getBaseName(mesh.textureName);
92+
obj.addLibrary(baseName + ".mtl");
93+
obj.addMaterial(baseName);
94+
}
95+
96+
boolean hasVertexColors = mesh.rgb.size > 0;
97+
6098
// First save the vertexes
6199
int N = mesh.vertexes.size();
62100
for (int i = 0; i < N; i++) {
63101
Point3D_F64 p = mesh.vertexes.getTemp(i);
64-
obj.addVertex(p.x, p.y, p.z);
102+
if (hasVertexColors) {
103+
addRgbVertex(obj, p, mesh.rgb.get(i));
104+
} else {
105+
obj.addVertex(p.x, p.y, p.z);
106+
}
65107
}
66108

67109
// Save vertex normals
68110
for (int i = 0; i < mesh.normals.size(); i++) {
69111
Point3D_F32 p = mesh.normals.getTemp(i);
70-
obj.addVertex(p.x, p.y, p.z);
112+
obj.addVertexNormal(p.x, p.y, p.z);
71113
}
72114

73115
// Save vertex textures
@@ -97,8 +139,20 @@ public static void save( VertexMesh mesh, Writer writer ) throws IOException {
97139
}
98140
}
99141

142+
private static void addRgbVertex( ObjFileWriter obj, Point3D_F64 p, int rgb ) throws IOException {
143+
// Convert to float format
144+
double red = ((rgb >> 16) & 0xFF)/255.0;
145+
double green = ((rgb >> 8) & 0xFF)/255.0;
146+
double blue = (rgb & 0xFF)/255.0;
147+
obj.addVertex(p.x, p.y, p.z, red, green, blue);
148+
}
149+
100150
public static void load( InputStream input, PointCloudWriter output ) throws IOException {
101151
var obj = new ObjFileReader() {
152+
@Override protected void addLibrary( String name ) {}
153+
154+
@Override protected void addMaterial( String name ) {}
155+
102156
@Override protected void addVertex( double x, double y, double z ) {
103157
output.startPoint();
104158
output.location(x, y, z);
@@ -107,10 +161,9 @@ public static void load( InputStream input, PointCloudWriter output ) throws IOE
107161

108162
@Override
109163
protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) {
110-
int rgb = ((int)(255*red)) << 16 | ((int)(255*green)) << 8 | ((int)(255*blue));
111164
output.startPoint();
112165
output.location(x, y, z);
113-
output.color(rgb);
166+
output.color(convertToInt(red, green, blue));
114167
output.stopPoint();
115168
}
116169

@@ -130,15 +183,20 @@ protected void addVertexWithColor( double x, double y, double z, double red, dou
130183
public static void load( InputStream input, VertexMesh output ) throws IOException {
131184
output.reset();
132185
var obj = new ObjFileReader() {
186+
// Library and material doesn't translate well a stream input and mesh output
187+
// A single obj file can define multiple objects with different texture maps
188+
@Override protected void addLibrary( String name ) {}
189+
190+
@Override protected void addMaterial( String name ) {}
191+
133192
@Override protected void addVertex( double x, double y, double z ) {
134193
output.vertexes.append(x, y, z);
135194
}
136195

137196
@Override
138197
protected void addVertexWithColor( double x, double y, double z, double red, double green, double blue ) {
139-
int rgb = ((int)(255*red)) << 16 | ((int)(255*green)) << 8 | ((int)(255*blue));
140198
output.vertexes.append(x, y, z);
141-
output.rgb.add(rgb);
199+
output.rgb.add(convertToInt(red, green, blue));
142200
}
143201

144202
@Override protected void addVertexNormal( double x, double y, double z ) {
@@ -154,18 +212,36 @@ protected void addVertexWithColor( double x, double y, double z, double red, dou
154212
@Override protected void addLine( DogArray_I32 vertexes ) {}
155213

156214
@Override protected void addFace( DogArray_I32 indexes, int vertexCount ) {
157-
int types = indexes.size/vertexCount;
158-
for (int idxVert = 0; idxVert < vertexCount; idxVert++) {
159-
output.faceVertexes.add(indexes.get(idxVert)/types);
160-
for (int i = 1; i < types; i++) {
161-
if (indexes.get(i - 1) != indexes.get(i))
162-
throw new RuntimeException("Only vertexes types with the same index supported");
163-
}
164-
}
165-
166-
output.faceOffsets.add(output.faceVertexes.size);
215+
addFactToMesh(indexes, vertexCount, output);
167216
}
168217
};
169218
obj.parse(new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)));
170219
}
220+
221+
static void addFactToMesh( DogArray_I32 indexes, int vertexCount, VertexMesh output ) {
222+
// order of that indexes are specifies is always vertex/texture/normal
223+
224+
boolean hasTexture = output.texture.size() > 0;
225+
boolean hasNormals = output.normals.size() > 0;
226+
227+
int typeCount = indexes.size/vertexCount;
228+
for (int idxVert = 0; idxVert < vertexCount; idxVert++) {
229+
int index = idxVert*typeCount;;
230+
output.faceVertexes.add(indexes.get(index++));
231+
232+
if (hasTexture) {
233+
output.faceVertexTextures.add(indexes.get(index++));
234+
}
235+
236+
if (hasNormals) {
237+
output.faceVertexNormals.add(indexes.get(index));
238+
}
239+
}
240+
241+
output.faceOffsets.add(output.faceVertexes.size);
242+
}
243+
244+
static int convertToInt( double red, double green, double blue ) {
245+
return ((int)(255*red + 0.5)) << 16 | ((int)(255*green + 0.5)) << 8 | ((int)(255*blue + 0.5));
246+
}
171247
}

0 commit comments

Comments
 (0)