package org.geogebra.common.io; import java.util.ArrayList; import java.util.List; import org.geogebra.common.awt.GColor; import org.geogebra.common.geogebra3D.kernel3D.algos.AlgoPolygon3D; import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D; import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolygon3D; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.opencsv.CSVException; import org.geogebra.common.util.opencsv.CSVParser; /** * * A handler for OFF file * * @author Shamshad Alam * */ public class OFFHandler { private final Construction construction; private int faceCount; private int vertexCount; private int edgeCount; /** vertices */ public List<Coords> vertices; /** faces */ public List<int[]> faces; /** colors of faces */ public GColor[] facesColor; private CSVParser parser; private static final String OFF = "OFF"; private static final String COMMENT_PREFIX = "#"; /** * * @param construction * construction */ public OFFHandler(Construction construction) { this.construction = construction; this.parser = new CSVParser(' '); } /** * Generate GGB objects from parsed data */ public void updateAfterParsing() { int vCount = getVertexCount(); GeoPointND[] vert = new GeoPointND[vCount]; List<Coords> allVertices = getVertices(); // Create vertices for (int i = 0; i < vCount; i++) { vert[i] = geoPoint(allVertices.get(i)); } // process all faces int index = 0; for (int[] fs : getFaces()) { int s = fs.length; GeoPointND[] geoPs = new GeoPointND[s]; // Create faces (Polygon) for (int i = 0; i < s; i++) { geoPs[i] = vert[fs[i]]; } // FIXME: better to add a method in AlgoDispatcher for this AlgoPolygon3D algo = new AlgoPolygon3D(construction, geoPs, false, null); GeoPolygon3D polygon = (GeoPolygon3D) algo.getOutput()[0]; boolean hasColor = hasColor(index); if (polygon.isDefined()) { polygon.setLabel(null); if (hasColor) { polygon.setObjColor(getFaceColor(index)); } } else { algo.remove(); // FIXME: It works only only if polygon is convex for (int i = 2; i < s; i++) { algo = new AlgoPolygon3D(construction, new GeoPointND[] { geoPs[0], geoPs[i - 1], geoPs[i] }, false, null); polygon = (GeoPolygon3D) algo.getOutput()[0]; polygon.setLabel(null); if (hasColor) { polygon.setObjColor(getFaceColor(index)); } } } index++; } } private GeoPoint3D geoPoint(Coords p3d) { GeoPoint3D p = new GeoPoint3D(construction); p.setCoords(p3d); p.setEuclidianVisible(false); p.setLabel(null); return p; } /** * * @param faceIndex * index of the face * @return color associated with the face */ private GColor getFaceColor(int faceIndex) { rangeCheck(faceIndex); return facesColor[faceIndex]; } private void rangeCheck(int faceIndex) { if (faceIndex < 0 || faceIndex >= faceCount) { throw new ArrayIndexOutOfBoundsException(faceIndex); } } /** * * @param faceIndex * face index * @return true if face color is specified in the file */ private boolean hasColor(int faceIndex) { rangeCheck(faceIndex); return facesColor[faceIndex] != null; } /** * * @return faceCount */ public int getFaceCount() { return faceCount; } /** * * @return edgeCount */ public int getEdgeCount() { return edgeCount; } /** * * @return vertexCount */ public int getVertexCount() { return vertexCount; } /** * * @return unmodifiable list of faces */ private List<int[]> getFaces() { return faces; } /** * * @return unmodifiable list of vertices */ private List<Coords> getVertices() { return vertices; } private void setCounts(int v, int f, int e) { vertexCount = v; faceCount = f; edgeCount = e; facesColor = new GColor[faceCount]; faces = new ArrayList<int[]>(faceCount); vertices = new ArrayList<Coords>(vertexCount); } private static boolean isCommentOrOffHeader(String line) { String l = line == null ? "" : line.trim(); return isComment1(l) || OFF.equalsIgnoreCase(l); } private static boolean isComment(String line) { String l = line == null ? "" : line.trim(); return isComment1(l); } private static boolean isComment1(String line) { return line.startsWith(COMMENT_PREFIX); } private static GColor tryReadColor(String[] in, int offset) { GColor color = null; try { if (in.length > offset && in[offset] != null && in[offset].indexOf('.') < 0) { int r = Integer.parseInt(in[offset]); int g = 0; int b = 0; int a = 0xff; if (in.length > offset + 1) { g = Integer.parseInt(in[offset + 1]); b = in.length > offset + 2 ? Integer.parseInt(in[offset + 2]) : 0; a = in.length > offset + 3 ? Integer.parseInt(in[offset + 3]) : 0xff; } else { a = r & 0xff; b = (r >>> 8) & 0xff; g = (r >>> 16) & 0xff; r = r >>> 24; } color = GColor.newColor(r, g, b, a); } else if (in.length > offset) { float h = Float.parseFloat(in[offset]); h = Math.min(1, h); if (in.length > offset + 1) { float s = Float.parseFloat(in[offset + 1]); float b = Float.parseFloat(in[offset + 2]); float a = 1.0f; if (in.length > offset + 3) { a = Float.parseFloat(in[offset + 3]); } s = Math.min(1.0f, s); b = Math.min(1.0f, b); a = Math.min(1.0f, a); color = GColor.newColor(h, s, b, a); } else { color = GColor.newColor(h, h, h); } } } catch (Exception ex) { Log.debug(ex.getMessage()); } return color; } private void addFaceLine(String line) throws CSVException { Log.debug(line); if (!OFFHandler.isComment(line)) { String[] aux = nonempty(parser.parseLine(line)); int vCount = Integer.parseInt(aux[0]); int[] v = new int[vCount]; for (int j = 0; j < vCount; j++) { v[j] = Integer.parseInt(aux[j + 1]); if (v[j] < 0 || v[j] >= getVertexCount()) { Log.error(v[j] + " out of range"); } } faces.add(v); // check whether face color is specified; facesColor[faces.size() - 1] = OFFHandler.tryReadColor(aux, vCount + 1); } } private void addVertexLine(String line) throws CSVException { if (!OFFHandler.isComment(line)) { String[] aux = nonempty(parser.parseLine(line)); vertices.add(new Coords(Double.parseDouble(aux[0]), Double.parseDouble(aux[1]), Double.parseDouble(aux[2]), 1.0)); } } /** * Filter non-empty lines * * @param parseLine * original lines * @return non-empty lines */ private static String[] nonempty(String[] parseLine) { String[] nonempty = new String[parseLine.length]; int j = 0; for (int i = 0; i < parseLine.length; i++) { if (parseLine[i].trim().length() > 0) { nonempty[j++] = parseLine[i].trim(); } } return nonempty; } /** * @param line * line to be parsed * @throws CSVException * when line is invalid */ public void addLine(String line) throws CSVException { if (OFFHandler.isCommentOrOffHeader(line)) { return; } if (vertexCount == 0) { String[] aux = nonempty(parser.parseLine(line)); setCounts(Integer.parseInt(aux[0]), Integer.parseInt(aux[1]), Integer.parseInt(aux[2])); return; } // read all vertices if (getVertices().size() < vertexCount) { addVertexLine(line); return; } if (getFaces().size() < faceCount) { addFaceLine(line); return; } } /** * Reeset internal state before parsing new file */ public void reset() { this.vertexCount = 0; } }