hu.kazocsaba.v3d.mesh.format.ply.PlyReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mesh-ply Show documentation
Show all versions of mesh-ply Show documentation
Data classes for 3D meshes.
The newest version!
package hu.kazocsaba.v3d.mesh.format.ply;
import java.awt.Color;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import hu.kazocsaba.math.matrix.MatrixFactory;
import hu.kazocsaba.math.matrix.Vector3;
import hu.kazocsaba.v3d.mesh.ColoredPointList;
import hu.kazocsaba.v3d.mesh.ColoredPointListImpl;
import hu.kazocsaba.v3d.mesh.IndexedTriangleMesh;
import hu.kazocsaba.v3d.mesh.IndexedTriangleMeshImpl;
import hu.kazocsaba.v3d.mesh.PointList;
import hu.kazocsaba.v3d.mesh.PointListImpl;
/**
* Class for reading meshes from files in PLY format.
* @author Kazó Csaba
*/
public final class PlyReader {
private final List elements;
private final Path file;
// null means ascii
private final ByteOrder fileFormat;
private Element vertexElement=null;
private int vertexXPropIndex=-1, vertexYPropIndex=-1, vertexZPropIndex=-1;
private int vertexRedPropIndex=-1, vertexGreenPropIndex=-1, vertexBluePropIndex=-1;
private Element faceElement=null;
private int vertexIndicesPropIndex=-1;
private final boolean hasVertices, hasVertexColors, hasFaces;
/**
* Creates a new instance that reads data from the specified file. The constructor parses the header of the PLY file,
* and the user should query its contents with the {@link #hasVertices()}, {@link #hasFaces()} etc. functions before
* calling the appropriate reader method.
* @param file the file to read from
* @throws InvalidPlyFormatException if the file format is incorrect
* @throws IOException if an I/O error occurs
*/
public PlyReader(Path file) throws IOException, InvalidPlyFormatException {
this.file=file;
try (Scanner scanner=new Scanner(file, "US-ASCII")) {
scanner.useLocale(Locale.ROOT);
String line=scanner.nextLine();
if (line==null || !line.equals("ply"))
throw new InvalidPlyFormatException("File is not in PLY format");
String format=null;
String version=null;
elements=new ArrayList<>();
{ // parse header
Element currentElement=null;
while (true) {
if (!scanner.hasNextLine()) {
throw new InvalidPlyFormatException("Unexpected end of file");
}
line=scanner.nextLine();
Scanner wordScanner=new Scanner(line);
String keyword=wordScanner.next();
if ("format".equals(keyword)) {
format=wordScanner.next();
version=wordScanner.next();
if (wordScanner.hasNext()) throw new InvalidPlyFormatException("Invalid file format");
} else if ("comment".equals(keyword))
continue;
else if ("element".equals(keyword)) {
String name=wordScanner.next();
int count=wordScanner.nextInt();
if (count<0) throw new InvalidPlyFormatException("Element "+name+" has negative instances");
if (wordScanner.hasNext()) throw new InvalidPlyFormatException("Invalid file format");
currentElement=new Element(name, count);
elements.add(currentElement);
} else if ("property".equals(keyword)) {
if (currentElement==null) throw new InvalidPlyFormatException("Property without element");
Property property;
String type=wordScanner.next();
if ("list".equals(type)) {
Type countType=parse(wordScanner.next());
if (countType==Type.FLOAT || countType==Type.DOUBLE) throw new InvalidPlyFormatException("List element count type must be integral");
Type elemType=parse(wordScanner.next());
String name=wordScanner.next();
if (wordScanner.hasNext()) throw new InvalidPlyFormatException("Invalid file format");
property=new ListProperty(name, countType, elemType);
} else {
String name=wordScanner.next();
Type scalarType=parse(type);
property=new ScalarProperty(name, scalarType);
}
currentElement.properties.add(property);
} else if ("obj_info".equals(keyword)) {
// ignore
} else if ("end_header".equals(keyword))
break;
else
throw new InvalidPlyFormatException("Unrecognized keyword in header: "+keyword);
}
}
if (format==null) throw new InvalidPlyFormatException("No format specification found in header");
if (!"1.0".equals(version)) throw new InvalidPlyFormatException("Unknown format version: "+version);
if ("ascii".equals(format)) {
fileFormat=null;
} else {
if ("binary_big_endian".equals(format))
fileFormat=ByteOrder.BIG_ENDIAN;
else if ("binary_little_endian".equals(format))
fileFormat=ByteOrder.LITTLE_ENDIAN;
else
throw new InvalidPlyFormatException("Invalid format: "+format);
}
}
for (Element e: elements) {
if ("vertex".equals(e.name)) {
if (vertexElement!=null) throw new InvalidPlyFormatException("Multiple vertex elements");
vertexElement=e;
for (int pi=0; pi0;
if (hasFaces) {
if (!hasVertices) throw new InvalidPlyFormatException("Faces without vertices");
if (vertexIndicesPropIndex==-1) throw new InvalidPlyFormatException("No face.vertex_indices property found");
}
}
/**
* Returns whether the PLY file contains vertex data.
* @return {@code true} if the file contains vertices
*/
public boolean hasVertices() {
return hasVertices;
}
/**
* Returns whether the PLY file contains colored vertex data.
* @return {@code true} if the file contains vertices along with vertex colors
*/
public boolean hasVertexColors() {
return hasVertexColors;
}
/**
* Returns whether the PLY file contains a mesh.
* @return {@code true} if the file contains mesh data (vertices and faces)
*/
public boolean hasFaces() {
return hasFaces;
}
private Input getInput() throws IOException {
if (fileFormat==null) {
return new AsciiInput(Files.newInputStream(file));
} else {
return new BinaryInput(Files.newByteChannel(file, StandardOpenOption.READ), fileFormat);
}
}
/**
* Reads vertices from the file.
* @return the vertices defined by this file as a point list
* @throws IOException if an I/O error occurs
* @throws InvalidPlyFormatException if the file format is incorrect
* @throws IllegalStateException if the file does not contain vertex data
*/
public PointList readVertices() throws IOException, InvalidPlyFormatException {
if (!hasVertices) throw new IllegalStateException("No vertices");
if (hasVertexColors) return readColoredVertices();
Input input=getInput();
List vertices=new ArrayList<>(vertexElement.count);
for (Element currentElement: elements) {
if (currentElement==vertexElement) {
/* Parse vertices */
for (int elemIndex=0; elemIndex vertices=new ArrayList<>(vertexElement.count);
List colors=new ArrayList<>(vertexElement.count);
try (Input input=getInput()) {
for (Element currentElement: elements) {
if (currentElement==vertexElement) {
/* Parse vertices */
int red=-1, green=-1, blue=-1;
for (int elemIndex=0; elemIndex vertices=new ArrayList<>(vertexElement.count);
List triangles=new ArrayList<>(faceElement.count);
try (Input input=getInput()) {
for (Element currentElement: elements) {
if (currentElement==vertexElement) {
/* Parse vertices */
for (int elemIndex=0; elemIndex=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v1.longValue());
v2=input.read(lp.elemType);
if (v2.longValue()<0 || v2.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v2.longValue());
v3=input.read(lp.elemType);
if (v3.longValue()<0 || v3.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v3.longValue());
triangles.add(new int[]{v1.intValue(), v2.intValue(), v3.intValue()});
break;
case 4:
v1=input.read(lp.elemType);
if (v1.longValue()<0 || v1.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v1.longValue());
v2=input.read(lp.elemType);
if (v2.longValue()<0 || v2.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v2.longValue());
v3=input.read(lp.elemType);
if (v3.longValue()<0 || v3.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v3.longValue());
v4=input.read(lp.elemType);
if (v4.longValue()<0 || v4.longValue()>=vertexElement.count) throw new InvalidPlyFormatException("Invalid vertex index: "+v4.longValue());
triangles.add(new int[]{v1.intValue(), v2.intValue(), v3.intValue()});
triangles.add(new int[]{v1.intValue(), v3.intValue(), v4.intValue()});
break;
default:
throw new InvalidPlyFormatException("Cannot handle faces with more than 4 vertices");
}
} else if (prop instanceof ListProperty) {
int count=input.read(((ListProperty)prop).countType).intValue();
if (count<0) throw new InvalidPlyFormatException("List with negative number of elements");
for (int i=0; ibuffer.capacity()-20) {
buffer.compact();
limit=limit-position;
position=0;
}
buffer.limit(buffer.capacity());
buffer.position(limit);
int read=channel.read(buffer);
if (read==-1) throw new InvalidPlyFormatException("Unexpected end of file");
if (read==0) throw new AssertionError();
buffer.limit(limit+read);
buffer.position(position);
}
}
@Override
public void needEnd() throws IOException {
if (buffer.remaining()!=0) throw new InvalidPlyFormatException("Expected end of file");
buffer.position(0);
buffer.limit(1);
if (channel.read(buffer)!=-1) throw new InvalidPlyFormatException("Expected end of file");
}
@Override
public void close() throws IOException {
channel.close();
}
}
/**
* Reads a mesh from a PLY file.
* @param file the file to read from
* @return the mesh contained in the file
* @throws IOException if an I/O error occurs
* @throws InvalidPlyFormatException if the format of the file is incorrect or no mesh is found
*/
public static IndexedTriangleMesh readMesh(File file) throws IOException, InvalidPlyFormatException {
PlyReader reader=new PlyReader(file.toPath());
if (!reader.hasFaces()) throw new InvalidPlyFormatException("No faces found");
return reader.readMesh();
}
private static Type parse(String type) throws InvalidPlyFormatException {
if (type.equals("char")) return Type.CHAR;
if (type.equals("uchar")) return Type.UCHAR;
if (type.equals("short")) return Type.SHORT;
if (type.equals("ushort")) return Type.USHORT;
if (type.equals("int")) return Type.INT;
if (type.equals("uint")) return Type.UINT;
if (type.equals("float")) return Type.FLOAT;
if (type.equals("double")) return Type.DOUBLE;
throw new InvalidPlyFormatException("Unrecognized type: "+type);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy