Task 096: .CNOFF File Format
Task 096: .CNOFF File Format
1. List of all the properties of this file format intrinsic to its file system
The .CNOFF file format is a variant of the OFF (Object File Format) for 3D geometry, specifically including both per-vertex colors (C) and per-vertex normals (N). It is used to represent collections of planar polygons (faces) with shared vertices, suitable for polyhedra or 3D models. The format can be ASCII or binary. Properties are as follows:
- Header: A string keyword "CNOFF" (optional in some implementations, but recommended; may include additional prefixes like "ST" for texture coordinates, but standard CNOFF assumes no textures unless specified).
- Number of vertices (NVertices): An integer indicating the total number of vertices in the model.
- Number of faces (NFaces): An integer indicating the total number of faces (polygons) in the model.
- Number of edges (NEdges): An integer (often ignored or set to 0, as it's not strictly used, but must be present for compatibility).
- Vertices: A list of vertex data, one per vertex (total NVertices). Each vertex includes:
- Position: 3 floating-point values (x, y, z coordinates).
- Normal: 3 floating-point values (nx, ny, nz for the vertex normal vector).
- Color: 4 floating-point values (r, g, b, a for red, green, blue, alpha; typically in range 0-1).
- Faces: A list of face data, one per face (total NFaces). Each face includes:
- Number of vertices in face (Nv): An integer indicating how many vertices define the polygon.
- Vertex indices: A list of Nv integers (0-based indices referencing the vertex list).
- Optional face color: 0 to 4 floating-point or integer values (e.g., RGB[A]); if present, overrides per-vertex colors for the face; can be index into colormap, 3/4 ints (0-255), or 3/4 floats (0-1).
- Binary flag (if binary format): If the header is followed by "BINARY", the data after the first newline is in binary (32-bit ints for counts/indices, 32-bit floats for positions/normals/colors).
- Dimension modifiers (optional): May include "4" for 4D vertices (adds homogeneous coordinate) or "n" for custom dimension Ndim (specified after header).
The format assumes planar, concave polygons without holes. Edges are implicitly defined by faces.
2. Two direct download links for files of format .CNOFF
- https://people.sc.fsu.edu/~jburkardt/data/off/cube.off (a cube model with colors; extension .off but format includes color data, compatible with CNOFF variant when normals are added or assumed).
- https://wdune.ourproject.org/examples/test.off (a test model referenced in CGAL discussions as CNOFF format with colors and normals; extension .off but internal structure matches CNOFF).
3. Ghost blog embedded html javascript that allows a user to drag n drop a file of format .CNOFF and it will dump to screen all these properties
Drag and drop .CNOFF file here
4. Python class that can open any file of format .CNOFF and decode read and write and print to console all the properties from the above list
import struct
import sys
class CNOFFParser:
def __init__(self, filepath=None):
self.header = ''
self.n_vertices = 0
self.n_faces = 0
self.n_edges = 0
self.vertices = [] # list of dicts: {'position': [x,y,z], 'normal': [nx,ny,nz], 'color': [r,g,b,a]}
self.faces = [] # list of dicts: {'nv': int, 'indices': [ints], 'face_color': [floats] or None}
if filepath:
self.read(filepath)
def read(self, filepath):
with open(filepath, 'r') as f:
content = f.read().strip()
lines = content.splitlines()
index = 0
self.header = lines[index].strip()
index += 1
if 'BINARY' in self.header:
self._read_binary(filepath)
return
nums = list(map(int, lines[index].strip().split()))
self.n_vertices, self.n_faces, self.n_edges = nums
index += 1
for _ in range(self.n_vertices):
parts = list(map(float, lines[index].strip().split()))
self.vertices.append({
'position': parts[0:3],
'normal': parts[3:6],
'color': parts[6:10]
})
index += 1
for _ in range(self.n_faces):
parts = list(map(float, lines[index].strip().split()))
nv = int(parts[0])
indices = list(map(int, parts[1:1+nv]))
face_color = parts[1+nv:] if len(parts) > 1+nv else None
self.faces.append({'nv': nv, 'indices': indices, 'face_color': face_color})
index += 1
def _read_binary(self, filepath):
with open(filepath, 'rb') as f:
# Skip header line
f.readline()
data = f.read()
offset = 0
self.n_vertices, = struct.unpack_from('i', data, offset)
offset += 4
self.n_faces, = struct.unpack_from('i', data, offset)
offset += 4
self.n_edges, = struct.unpack_from('i', data, offset)
offset += 4
for _ in range(self.n_vertices):
pos = struct.unpack_from('fff', data, offset)
offset += 12
normal = struct.unpack_from('fff', data, offset)
offset += 12
color = struct.unpack_from('ffff', data, offset)
offset += 16
self.vertices.append({'position': list(pos), 'normal': list(normal), 'color': list(color)})
for _ in range(self.n_faces):
nv, = struct.unpack_from('i', data, offset)
offset += 4
indices = []
for __ in range(nv):
idx, = struct.unpack_from('i', data, offset)
offset += 4
indices.append(idx)
color_count, = struct.unpack_from('i', data, offset)
offset += 4
face_color = None
if color_count > 0:
face_color = struct.unpack_from('f' * color_count, data, offset)
offset += 4 * color_count
self.faces.append({'nv': nv, 'indices': indices, 'face_color': list(face_color) if face_color else None})
def write(self, filepath, binary=False):
with open(filepath, 'w' if not binary else 'wb') as f:
if binary:
f.write((self.header + ' BINARY\n').encode())
f.write(struct.pack('iii', self.n_vertices, self.n_faces, self.n_edges))
for v in self.vertices:
f.write(struct.pack('fff', *v['position']))
f.write(struct.pack('fff', *v['normal']))
f.write(struct.pack('ffff', *v['color']))
for face in self.faces:
f.write(struct.pack('i', face['nv']))
for idx in face['indices']:
f.write(struct.pack('i', idx))
color = face['face_color'] or []
f.write(struct.pack('i', len(color)))
for c in color:
f.write(struct.pack('f', c))
else:
f.write(self.header + '\n')
f.write(f"{self.n_vertices} {self.n_faces} {self.n_edges}\n")
for v in self.vertices:
line = ' '.join(map(str, v['position'] + v['normal'] + v['color']))
f.write(line + '\n')
for face in self.faces:
line = [str(face['nv'])] + list(map(str, face['indices']))
if face['face_color']:
line += list(map(str, face['face_color']))
f.write(' '.join(line) + '\n')
def print_properties(self):
print(f"Header: {self.header}")
print(f"Number of vertices: {self.n_vertices}")
print(f"Number of faces: {self.n_faces}")
print(f"Number of edges: {self.n_edges}")
print("Vertices:")
for i, v in enumerate(self.vertices):
print(f" Vertex {i}: Position={v['position']}, Normal={v['normal']}, Color={v['color']}")
print("Faces:")
for i, face in enumerate(self.faces):
print(f" Face {i}: Nv={face['nv']}, Indices={face['indices']}, Face Color={face['face_color']}")
# Example usage
if __name__ == "__main__":
if len(sys.argv) > 1:
parser = CNOFFParser(sys.argv[1])
parser.print_properties()
5. Java class that can open any file of format .CNOFF and decode read and write and print to console all the properties from the above list
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
public class CNOFFParser {
private String header;
private int nVertices;
private int nFaces;
private int nEdges;
private List<Map<String, float[]>> vertices = new ArrayList<>(); // Each: "position", "normal", "color"
private List<Map<String, Object>> faces = new ArrayList<>(); // Each: "nv" int, "indices" List<Integer>, "face_color" float[] or null
public CNOFFParser(String filepath) throws IOException {
read(filepath);
}
public void read(String filepath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
header = reader.readLine().trim();
if (header.contains("BINARY")) {
readBinary(filepath);
return;
}
String[] nums = reader.readLine().trim().split("\\s+");
nVertices = Integer.parseInt(nums[0]);
nFaces = Integer.parseInt(nums[1]);
nEdges = Integer.parseInt(nums[2]);
for (int i = 0; i < nVertices; i++) {
String[] parts = reader.readLine().trim().split("\\s+");
float[] pos = new float[3];
float[] normal = new float[3];
float[] color = new float[4];
for (int j = 0; j < 3; j++) pos[j] = Float.parseFloat(parts[j]);
for (int j = 0; j < 3; j++) normal[j] = Float.parseFloat(parts[j+3]);
for (int j = 0; j < 4; j++) color[j] = Float.parseFloat(parts[j+6]);
Map<String, float[]> v = new HashMap<>();
v.put("position", pos);
v.put("normal", normal);
v.put("color", color);
vertices.add(v);
}
for (int i = 0; i < nFaces; i++) {
String[] parts = reader.readLine().trim().split("\\s+");
int nv = Integer.parseInt(parts[0]);
List<Integer> indices = new ArrayList<>();
for (int j = 1; j < 1 + nv; j++) indices.add(Integer.parseInt(parts[j]));
float[] faceColor = null;
if (parts.length > 1 + nv) {
faceColor = new float[parts.length - 1 - nv];
for (int j = 0; j < faceColor.length; j++) faceColor[j] = Float.parseFloat(parts[1 + nv + j]);
}
Map<String, Object> f = new HashMap<>();
f.put("nv", nv);
f.put("indices", indices);
f.put("face_color", faceColor);
faces.add(f);
}
}
}
private void readBinary(String filepath) throws IOException {
try (FileInputStream fis = new FileInputStream(filepath)) {
// Skip header line
while (fis.read() != '\n');
byte[] data = fis.readAllBytes();
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN); // Assume big-endian
nVertices = bb.getInt();
nFaces = bb.getInt();
nEdges = bb.getInt();
for (int i = 0; i < nVertices; i++) {
float[] pos = new float[3];
float[] normal = new float[3];
float[] color = new float[4];
for (int j = 0; j < 3; j++) pos[j] = bb.getFloat();
for (int j = 0; j < 3; j++) normal[j] = bb.getFloat();
for (int j = 0; j < 4; j++) color[j] = bb.getFloat();
Map<String, float[]> v = new HashMap<>();
v.put("position", pos);
v.put("normal", normal);
v.put("color", color);
vertices.add(v);
}
for (int i = 0; i < nFaces; i++) {
int nv = bb.getInt();
List<Integer> indices = new ArrayList<>();
for (int j = 0; j < nv; j++) indices.add(bb.getInt());
int colorCount = bb.getInt();
float[] faceColor = null;
if (colorCount > 0) {
faceColor = new float[colorCount];
for (int j = 0; j < colorCount; j++) faceColor[j] = bb.getFloat();
}
Map<String, Object> f = new HashMap<>();
f.put("nv", nv);
f.put("indices", indices);
f.put("face_color", faceColor);
faces.add(f);
}
}
}
public void write(String filepath, boolean binary) throws IOException {
if (binary) {
try (FileOutputStream fos = new FileOutputStream(filepath)) {
fos.write((header + " BINARY\n").getBytes());
ByteBuffer bb = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.BIG_ENDIAN); // Buffer
bb.putInt(nVertices).putInt(nFaces).putInt(nEdges);
for (Map<String, float[]> v : vertices) {
for (float p : v.get("position")) bb.putFloat(p);
for (float n : v.get("normal")) bb.putFloat(n);
for (float c : v.get("color")) bb.putFloat(c);
}
for (Map<String, Object> f : faces) {
bb.putInt((int) f.get("nv"));
@SuppressWarnings("unchecked")
List<Integer> indices = (List<Integer>) f.get("indices");
for (int idx : indices) bb.putInt(idx);
float[] color = (float[]) f.get("face_color");
bb.putInt(color != null ? color.length : 0);
if (color != null) for (float c : color) bb.putFloat(c);
}
fos.write(bb.array(), 0, bb.position());
}
} else {
try (PrintWriter writer = new PrintWriter(filepath)) {
writer.println(header);
writer.println(nVertices + " " + nFaces + " " + nEdges);
for (Map<String, float[]> v : vertices) {
StringBuilder line = new StringBuilder();
for (float p : v.get("position")) line.append(p).append(" ");
for (float n : v.get("normal")) line.append(n).append(" ");
for (float c : v.get("color")) line.append(c).append(" ");
writer.println(line.toString().trim());
}
for (Map<String, Object> f : faces) {
StringBuilder line = new StringBuilder();
line.append(f.get("nv")).append(" ");
@SuppressWarnings("unchecked")
List<Integer> indices = (List<Integer>) f.get("indices");
for (int idx : indices) line.append(idx).append(" ");
float[] color = (float[]) f.get("face_color");
if (color != null) for (float c : color) line.append(c).append(" ");
writer.println(line.toString().trim());
}
}
}
}
public void printProperties() {
System.out.println("Header: " + header);
System.out.println("Number of vertices: " + nVertices);
System.out.println("Number of faces: " + nFaces);
System.out.println("Number of edges: " + nEdges);
System.out.println("Vertices:");
for (int i = 0; i < vertices.size(); i++) {
Map<String, float[]> v = vertices.get(i);
System.out.printf(" Vertex %d: Position=%s, Normal=%s, Color=%s%n",
i, Arrays.toString(v.get("position")), Arrays.toString(v.get("normal")), Arrays.toString(v.get("color")));
}
System.out.println("Faces:");
for (int i = 0; i < faces.size(); i++) {
Map<String, Object> f = faces.get(i);
System.out.printf(" Face %d: Nv=%s, Indices=%s, Face Color=%s%n",
i, f.get("nv"), f.get("indices"), Arrays.toString((float[]) f.get("face_color")));
}
}
public static void main(String[] args) throws IOException {
if (args.length > 0) {
CNOFFParser parser = new CNOFFParser(args[0]);
parser.printProperties();
}
}
}
6. Javascript class that can open any file of format .CNOFF and decode read and write and print to console all the properties from the above list
const fs = require('fs'); // For Node.js environment
class CNOFFParser {
constructor(filepath = null) {
this.header = '';
this.nVertices = 0;
this.nFaces = 0;
this.nEdges = 0;
this.vertices = []; // array of {position: [x,y,z], normal: [nx,ny,nz], color: [r,g,b,a]}
this.faces = []; // array of {nv: number, indices: [numbers], faceColor: [numbers] or null}
if (filepath) {
this.read(filepath);
}
}
read(filepath) {
const content = fs.readFileSync(filepath, 'utf8').trim();
const lines = content.split(/\n/);
let index = 0;
this.header = lines[index++].trim();
if (this.header.includes('BINARY')) {
this.readBinary(filepath);
return;
}
const nums = lines[index++].trim().split(/\s+/).map(Number);
this.nVertices = nums[0];
this.nFaces = nums[1];
this.nEdges = nums[2];
for (let i = 0; i < this.nVertices; i++) {
const parts = lines[index++].trim().split(/\s+/).map(Number);
this.vertices.push({
position: parts.slice(0, 3),
normal: parts.slice(3, 6),
color: parts.slice(6, 10)
});
}
for (let i = 0; i < this.nFaces; i++) {
const parts = lines[index++].trim().split(/\s+/).map(Number);
const nv = parts[0];
const indices = parts.slice(1, 1 + nv);
const faceColor = parts.slice(1 + nv).length ? parts.slice(1 + nv) : null;
this.faces.push({ nv, indices, faceColor });
}
}
readBinary(filepath) {
const data = fs.readFileSync(filepath);
let offset = data.indexOf('\n') + 1; // Skip header
const view = new DataView(data.buffer, offset);
let pos = 0;
this.nVertices = view.getInt32(pos, false); pos += 4;
this.nFaces = view.getInt32(pos, false); pos += 4;
this.nEdges = view.getInt32(pos, false); pos += 4;
for (let i = 0; i < this.nVertices; i++) {
const position = [view.getFloat32(pos, false), view.getFloat32(pos+4, false), view.getFloat32(pos+8, false)]; pos += 12;
const normal = [view.getFloat32(pos, false), view.getFloat32(pos+4, false), view.getFloat32(pos+8, false)]; pos += 12;
const color = [view.getFloat32(pos, false), view.getFloat32(pos+4, false), view.getFloat32(pos+8, false), view.getFloat32(pos+12, false)]; pos += 16;
this.vertices.push({ position, normal, color });
}
for (let i = 0; i < this.nFaces; i++) {
const nv = view.getInt32(pos, false); pos += 4;
const indices = [];
for (let j = 0; j < nv; j++) {
indices.push(view.getInt32(pos, false)); pos += 4;
}
const colorCount = view.getInt32(pos, false); pos += 4;
let faceColor = null;
if (colorCount > 0) {
faceColor = [];
for (let j = 0; j < colorCount; j++) {
faceColor.push(view.getFloat32(pos, false)); pos += 4;
}
}
this.faces.push({ nv, indices, faceColor });
}
}
write(filepath, binary = false) {
let content = '';
if (binary) {
const buffer = new ArrayBuffer(1024 * 1024); // Large enough
const view = new DataView(buffer);
let pos = 0;
fs.writeFileSync(filepath, this.header + ' BINARY\n');
view.setInt32(pos, this.nVertices, false); pos += 4;
view.setInt32(pos, this.nFaces, false); pos += 4;
view.setInt32(pos, this.nEdges, false); pos += 4;
for (const v of this.vertices) {
for (const p of v.position) { view.setFloat32(pos, p, false); pos += 4; }
for (const n of v.normal) { view.setFloat32(pos, n, false); pos += 4; }
for (const c of v.color) { view.setFloat32(pos, c, false); pos += 4; }
}
for (const f of this.faces) {
view.setInt32(pos, f.nv, false); pos += 4;
for (const idx of f.indices) { view.setInt32(pos, idx, false); pos += 4; }
const color = f.faceColor || [];
view.setInt32(pos, color.length, false); pos += 4;
for (const c of color) { view.setFloat32(pos, c, false); pos += 4; }
}
fs.appendFileSync(filepath, Buffer.from(buffer, 0, pos));
} else {
content += this.header + '\n';
content += `${this.nVertices} ${this.nFaces} ${this.nEdges}\n`;
for (const v of this.vertices) {
content += [...v.position, ...v.normal, ...v.color].join(' ') + '\n';
}
for (const f of this.faces) {
let line = [f.nv, ...f.indices];
if (f.faceColor) line = [...line, ...f.faceColor];
content += line.join(' ') + '\n';
}
fs.writeFileSync(filepath, content);
}
}
printProperties() {
console.log(`Header: ${this.header}`);
console.log(`Number of vertices: ${this.nVertices}`);
console.log(`Number of faces: ${this.nFaces}`);
console.log(`Number of edges: ${this.nEdges}`);
console.log('Vertices:');
this.vertices.forEach((v, i) => {
console.log(` Vertex ${i}: Position=${v.position}, Normal=${v.normal}, Color=${v.color}`);
});
console.log('Faces:');
this.faces.forEach((f, i) => {
console.log(` Face ${i}: Nv=${f.nv}, Indices=${f.indices}, Face Color=${f.faceColor}`);
});
}
}
// Example usage in Node.js
if (process.argv.length > 2) {
const parser = new CNOFFParser(process.argv[2]);
parser.printProperties();
}
7. C class that can open any file of format .CNOFF and decode read and write and print to console all the properties from the above list
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef struct {
float position[3];
float normal[3];
float color[4];
} Vertex;
typedef struct {
int nv;
int *indices;
float *face_color; // NULL if none
int color_count;
} Face;
typedef struct {
char header[256];
int n_vertices;
int n_faces;
int n_edges;
Vertex *vertices;
Face *faces;
bool is_binary;
} CNOFF;
CNOFF* cnoff_init() {
CNOFF* c = malloc(sizeof(CNOFF));
memset(c, 0, sizeof(CNOFF));
return c;
}
void cnoff_free(CNOFF* c) {
if (c->vertices) free(c->vertices);
for (int i = 0; i < c->n_faces; i++) {
if (c->faces[i].indices) free(c->faces[i].indices);
if (c->faces[i].face_color) free(c->faces[i].face_color);
}
if (c->faces) free(c->faces);
free(c);
}
int cnoff_read(CNOFF* c, const char* filepath) {
FILE* f = fopen(filepath, "r");
if (!f) return 1;
fgets(c->header, 256, f);
c->header[strcspn(c->header, "\n")] = 0;
if (strstr(c->header, "BINARY")) {
fclose(f);
return cnoff_read_binary(c, filepath);
}
fscanf(f, "%d %d %d", &c->n_vertices, &c->n_faces, &c->n_edges);
c->vertices = malloc(c->n_vertices * sizeof(Vertex));
for (int i = 0; i < c->n_vertices; i++) {
Vertex* v = &c->vertices[i];
fscanf(f, "%f %f %f %f %f %f %f %f %f %f", &v->position[0], &v->position[1], &v->position[2],
&v->normal[0], &v->normal[1], &v->normal[2],
&v->color[0], &v->color[1], &v->color[2], &v->color[3]);
}
c->faces = malloc(c->n_faces * sizeof(Face));
char line[1024];
for (int i = 0; i < c->n_faces; i++) {
fgets(line, 1024, f); // To handle variable face colors
Face* face = &c->faces[i];
char* token = strtok(line, " ");
face->nv = atoi(token);
face->indices = malloc(face->nv * sizeof(int));
for (int j = 0; j < face->nv; j++) {
token = strtok(NULL, " ");
face->indices[j] = atoi(token);
}
face->color_count = 0;
face->face_color = NULL;
token = strtok(NULL, " ");
while (token) {
face->color_count++;
face->face_color = realloc(face->face_color, face->color_count * sizeof(float));
face->face_color[face->color_count - 1] = atof(token);
token = strtok(NULL, " ");
}
}
fclose(f);
return 0;
}
int cnoff_read_binary(CNOFF* c, const char* filepath) {
FILE* f = fopen(filepath, "rb");
if (!f) return 1;
// Skip header
char ch;
while ((ch = fgetc(f)) != '\n');
fread(&c->n_vertices, sizeof(int), 1, f);
fread(&c->n_faces, sizeof(int), 1, f);
fread(&c->n_edges, sizeof(int), 1, f);
c->vertices = malloc(c->n_vertices * sizeof(Vertex));
for (int i = 0; i < c->n_vertices; i++) {
Vertex* v = &c->vertices[i];
fread(v->position, sizeof(float), 3, f);
fread(v->normal, sizeof(float), 3, f);
fread(v->color, sizeof(float), 4, f);
}
c->faces = malloc(c->n_faces * sizeof(Face));
for (int i = 0; i < c->n_faces; i++) {
Face* face = &c->faces[i];
fread(&face->nv, sizeof(int), 1, f);
face->indices = malloc(face->nv * sizeof(int));
fread(face->indices, sizeof(int), face->nv, f);
fread(&face->color_count, sizeof(int), 1, f);
if (face->color_count > 0) {
face->face_color = malloc(face->color_count * sizeof(float));
fread(face->face_color, sizeof(float), face->color_count, f);
} else {
face->face_color = NULL;
}
}
fclose(f);
c->is_binary = true;
return 0;
}
int cnoff_write(CNOFF* c, const char* filepath, bool binary) {
FILE* f = fopen(filepath, binary ? "wb" : "w");
if (!f) return 1;
if (binary) {
fprintf(f, "%s BINARY\n", c->header);
fwrite(&c->n_vertices, sizeof(int), 1, f);
fwrite(&c->n_faces, sizeof(int), 1, f);
fwrite(&c->n_edges, sizeof(int), 1, f);
for (int i = 0; i < c->n_vertices; i++) {
Vertex v = c->vertices[i];
fwrite(v.position, sizeof(float), 3, f);
fwrite(v.normal, sizeof(float), 3, f);
fwrite(v.color, sizeof(float), 4, f);
}
for (int i = 0; i < c->n_faces; i++) {
Face face = c->faces[i];
fwrite(&face.nv, sizeof(int), 1, f);
fwrite(face.indices, sizeof(int), face.nv, f);
fwrite(&face.color_count, sizeof(int), 1, f);
if (face.color_count > 0) {
fwrite(face.face_color, sizeof(float), face.color_count, f);
}
}
} else {
fprintf(f, "%s\n", c->header);
fprintf(f, "%d %d %d\n", c->n_vertices, c->n_faces, c->n_edges);
for (int i = 0; i < c->n_vertices; i++) {
Vertex v = c->vertices[i];
fprintf(f, "%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f\n",
v.position[0], v.position[1], v.position[2],
v.normal[0], v.normal[1], v.normal[2],
v.color[0], v.color[1], v.color[2], v.color[3]);
}
for (int i = 0; i < c->n_faces; i++) {
Face face = c->faces[i];
fprintf(f, "%d", face.nv);
for (int j = 0; j < face.nv; j++) {
fprintf(f, " %d", face.indices[j]);
}
for (int j = 0; j < face.color_count; j++) {
fprintf(f, " %.6f", face.face_color[j]);
}
fprintf(f, "\n");
}
}
fclose(f);
return 0;
}
void cnoff_print_properties(CNOFF* c) {
printf("Header: %s\n", c->header);
printf("Number of vertices: %d\n", c->n_vertices);
printf("Number of faces: %d\n", c->n_faces);
printf("Number of edges: %d\n", c->n_edges);
printf("Vertices:\n");
for (int i = 0; i < c->n_vertices; i++) {
Vertex v = c->vertices[i];
printf(" Vertex %d: Position=[%.2f, %.2f, %.2f], Normal=[%.2f, %.2f, %.2f], Color=[%.2f, %.2f, %.2f, %.2f]\n",
i, v.position[0], v.position[1], v.position[2],
v.normal[0], v.normal[1], v.normal[2],
v.color[0], v.color[1], v.color[2], v.color[3]);
}
printf("Faces:\n");
for (int i = 0; i < c->n_faces; i++) {
Face face = c->faces[i];
printf(" Face %d: Nv=%d, Indices=[", i, face.nv);
for (int j = 0; j < face.nv; j++) {
printf("%d%s", face.indices[j], j < face.nv - 1 ? ", " : "");
}
printf("], Face Color=");
if (face.color_count > 0) {
printf("[");
for (int j = 0; j < face.color_count; j++) {
printf("%.2f%s", face.face_color[j], j < face.color_count - 1 ? ", " : "");
}
printf("]");
} else {
printf("null");
}
printf("\n");
}
}
int main(int argc, char** argv) {
if (argc < 2) return 1;
CNOFF* c = cnoff_init();
if (cnoff_read(c, argv[1]) == 0) {
cnoff_print_properties(c);
}
cnoff_free(c);
return 0;
}