Task 468: .OBJ File Format
Task 468: .OBJ File Format
1. Properties of the .OBJ File Format Intrinsic to Its File System
The .OBJ file format, also known as Wavefront OBJ, is a text-based (ASCII) format for representing 3D geometry. It lacks binary headers or magic numbers, relying instead on keyword-prefixed lines to define elements. Comments are denoted by lines starting with "#". The format supports references to external .MTL files for material definitions. Below is a comprehensive list of intrinsic properties or elements, derived from the format's specification:
- Geometric Vertices (v): Define 3D points with coordinates (x, y, z, [w]), where w is optional (defaults to 1.0). Optional color values (r, g, b) may follow in some extensions.
- Texture Coordinates (vt): Specify UV(W) mapping for textures, with values (u, [v, [w]]), where v and w default to 0.
- Vertex Normals (vn): Provide surface normals (x, y, z) for shading.
- Parameter Space Vertices (vp): Used for free-form curves/surfaces, with (u, [v, [w]]).
- Polygonal Faces (f): Define polygons via lists of vertex indices, optionally including texture and normal indices (e.g., v/vt/vn).
- Line Elements (l): Specify polylines using vertex indices.
- Material Library Reference (mtllib): Links to external .MTL files containing material definitions.
- Material Usage (usemtl): Applies a named material from the referenced .MTL to subsequent elements.
- Object Name (o): Assigns a name to a geometric object.
- Group Name (g): Groups polygons under a named category.
- Smoothing Group (s): Controls smooth shading across polygons, with group numbers or "off".
- Free-Form Curve/Surface Attributes: Support for higher-order surfaces like NURBS, B-splines, or Taylor interpolation, including curve/surface body statements and connectivity.
- Comments (#): Allow for annotations, such as scale information.
- Overall Structure Properties: Line-based parsing with space-separated values; indices start at 1 (absolute) or use negative values (relative); no inherent units or animation support.
These elements constitute the core data structure, enabling the representation of meshes, curves, and surfaces without dependencies on specific file system metadata beyond the .obj extension.
2. Direct Download Links for .OBJ Files
- https://graphics.stanford.edu/courses/cs148-10-summer/as3/code/as3/teapot.obj (Utah Teapot model)
- https://chuck.stanford.edu/chugl/examples/data/models/suzanne.obj (Suzanne monkey head model)
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .OBJ File Dumping
The following is a self-contained HTML snippet with embedded JavaScript that can be inserted into a Ghost blog post. It creates a drag-and-drop area where users can upload a .OBJ file. Upon upload, the script parses the file and displays the extracted properties (e.g., counts of vertices, faces, and other elements) in a designated output area on the page.
4. Python Class for .OBJ File Handling
The following Python class can open, read, decode (parse), write, and print the properties of a .OBJ file to the console.
import sys
class OBJHandler:
def __init__(self, filepath=None):
self.filepath = filepath
self.vertices = []
self.texture_coords = []
self.normals = []
self.param_vertices = []
self.faces = []
self.lines = []
self.objects = []
self.groups = []
self.smoothing_groups = []
self.materials = []
self.mtllib = []
self.comments = []
if filepath:
self.read()
def read(self):
with open(self.filepath, 'r') as f:
for line in f:
trimmed = line.strip()
parts = trimmed.split()
if not parts:
continue
keyword = parts[0]
if keyword == '#':
self.comments.append(trimmed)
elif keyword == 'v':
self.vertices.append(parts[1:])
elif keyword == 'vt':
self.texture_coords.append(parts[1:])
elif keyword == 'vn':
self.normals.append(parts[1:])
elif keyword == 'vp':
self.param_vertices.append(parts[1:])
elif keyword == 'f':
self.faces.append(parts[1:])
elif keyword == 'l':
self.lines.append(parts[1:])
elif keyword == 'o':
self.objects.append(parts[1:])
elif keyword == 'g':
self.groups.append(parts[1:])
elif keyword == 's':
self.smoothing_groups.append(parts[1:])
elif keyword == 'usemtl':
self.materials.append(parts[1:])
elif keyword == 'mtllib':
self.mtllib.append(parts[1:])
def write(self, output_path):
with open(output_path, 'w') as f:
for comment in self.comments:
f.write(f"{comment}\n")
for mtllib in self.mtllib:
f.write(f"mtllib {' '.join(mtllib)}\n")
for obj in self.objects:
f.write(f"o {' '.join(obj)}\n")
for group in self.groups:
f.write(f"g {' '.join(group)}\n")
for smooth in self.smoothing_groups:
f.write(f"s {' '.join(smooth)}\n")
for mat in self.materials:
f.write(f"usemtl {' '.join(mat)}\n")
for vert in self.vertices:
f.write(f"v {' '.join(vert)}\n")
for tc in self.texture_coords:
f.write(f"vt {' '.join(tc)}\n")
for norm in self.normals:
f.write(f"vn {' '.join(norm)}\n")
for pv in self.param_vertices:
f.write(f"vp {' '.join(pv)}\n")
for face in self.faces:
f.write(f"f {' '.join(face)}\n")
for line in self.lines:
f.write(f"l {' '.join(line)}\n")
def print_properties(self):
print("Vertices:", len(self.vertices))
print("Texture Coordinates:", len(self.texture_coords))
print("Normals:", len(self.normals))
print("Parameter Vertices:", len(self.param_vertices))
print("Faces:", len(self.faces))
print("Lines:", len(self.lines))
print("Objects:", len(self.objects))
print("Groups:", len(self.groups))
print("Smoothing Groups:", len(self.smoothing_groups))
print("Materials:", self.materials)
print("Material Libraries:", self.mtllib)
print("Comments:", len(self.comments))
# Example usage:
# handler = OBJHandler('example.obj')
# handler.print_properties()
# handler.write('output.obj')
5. Java Class for .OBJ File Handling
The following Java class can open, read, decode (parse), write, and print the properties of a .OBJ file to the console.
import java.io.*;
import java.util.*;
public class OBJHandler {
private String filepath;
private List<List<String>> vertices = new ArrayList<>();
private List<List<String>> textureCoords = new ArrayList<>();
private List<List<String>> normals = new ArrayList<>();
private List<List<String>> paramVertices = new ArrayList<>();
private List<List<String>> faces = new ArrayList<>();
private List<List<String>> lines = new ArrayList<>();
private List<List<String>> objects = new ArrayList<>();
private List<List<String>> groups = new ArrayList<>();
private List<List<String>> smoothingGroups = new ArrayList<>();
private List<List<String>> materials = new ArrayList<>();
private List<List<String>> mtllib = new ArrayList<>();
private List<String> comments = new ArrayList<>();
public OBJHandler(String filepath) {
this.filepath = filepath;
read();
}
public void read() {
try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = br.readLine()) != null) {
String trimmed = line.trim();
String[] parts = trimmed.split("\\s+");
if (parts.length == 0) continue;
String keyword = parts[0];
if (keyword.equals("#")) {
comments.add(trimmed);
} else if (keyword.equals("v")) {
vertices.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("vt")) {
textureCoords.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("vn")) {
normals.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("vp")) {
paramVertices.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("f")) {
faces.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("l")) {
lines.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("o")) {
objects.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("g")) {
groups.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("s")) {
smoothingGroups.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("usemtl")) {
materials.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
} else if (keyword.equals("mtllib")) {
mtllib.add(Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length)));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void write(String outputPath) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath))) {
for (String comment : comments) {
bw.write(comment + "\n");
}
for (List<String> lib : mtllib) {
bw.write("mtllib " + String.join(" ", lib) + "\n");
}
for (List<String> obj : objects) {
bw.write("o " + String.join(" ", obj) + "\n");
}
for (List<String> group : groups) {
bw.write("g " + String.join(" ", group) + "\n");
}
for (List<String> smooth : smoothingGroups) {
bw.write("s " + String.join(" ", smooth) + "\n");
}
for (List<String> mat : materials) {
bw.write("usemtl " + String.join(" ", mat) + "\n");
}
for (List<String> vert : vertices) {
bw.write("v " + String.join(" ", vert) + "\n");
}
for (List<String> tc : textureCoords) {
bw.write("vt " + String.join(" ", tc) + "\n");
}
for (List<String> norm : normals) {
bw.write("vn " + String.join(" ", norm) + "\n");
}
for (List<String> pv : paramVertices) {
bw.write("vp " + String.join(" ", pv) + "\n");
}
for (List<String> face : faces) {
bw.write("f " + String.join(" ", face) + "\n");
}
for (List<String> ln : lines) {
bw.write("l " + String.join(" ", ln) + "\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
System.out.println("Vertices: " + vertices.size());
System.out.println("Texture Coordinates: " + textureCoords.size());
System.out.println("Normals: " + normals.size());
System.out.println("Parameter Vertices: " + paramVertices.size());
System.out.println("Faces: " + faces.size());
System.out.println("Lines: " + lines.size());
System.out.println("Objects: " + objects.size());
System.out.println("Groups: " + groups.size());
System.out.println("Smoothing Groups: " + smoothingGroups.size());
System.out.println("Materials: " + materials);
System.out.println("Material Libraries: " + mtllib);
System.out.println("Comments: " + comments.size());
}
// Example usage:
// public static void main(String[] args) {
// OBJHandler handler = new OBJHandler("example.obj");
// handler.printProperties();
// handler.write("output.obj");
// }
}
6. JavaScript Class for .OBJ File Handling
The following JavaScript class (for Node.js environment) can open, read, decode (parse), write, and print the properties of a .OBJ file to the console. It requires the 'fs' module.
const fs = require('fs');
class OBJHandler {
constructor(filepath = null) {
this.filepath = filepath;
this.vertices = [];
this.textureCoords = [];
this.normals = [];
this.paramVertices = [];
this.faces = [];
this.lines = [];
this.objects = [];
this.groups = [];
this.smoothingGroups = [];
this.materials = [];
this.mtllib = [];
this.comments = [];
if (filepath) {
this.read();
}
}
read() {
const content = fs.readFileSync(this.filepath, 'utf-8');
const lines = content.split('\n');
lines.forEach(line => {
const trimmed = line.trim();
const parts = trimmed.split(/\s+/);
if (parts.length === 0) return;
const keyword = parts[0];
if (keyword === '#') {
this.comments.push(trimmed);
} else if (keyword === 'v') {
this.vertices.push(parts.slice(1));
} else if (keyword === 'vt') {
this.textureCoords.push(parts.slice(1));
} else if (keyword === 'vn') {
this.normals.push(parts.slice(1));
} else if (keyword === 'vp') {
this.paramVertices.push(parts.slice(1));
} else if (keyword === 'f') {
this.faces.push(parts.slice(1));
} else if (keyword === 'l') {
this.lines.push(parts.slice(1));
} else if (keyword === 'o') {
this.objects.push(parts.slice(1));
} else if (keyword === 'g') {
this.groups.push(parts.slice(1));
} else if (keyword === 's') {
this.smoothingGroups.push(parts.slice(1));
} else if (keyword === 'usemtl') {
this.materials.push(parts.slice(1));
} else if (keyword === 'mtllib') {
this.mtllib.push(parts.slice(1));
}
});
}
write(outputPath) {
let output = '';
this.comments.forEach(comment => output += `${comment}\n`);
this.mtllib.forEach(lib => output += `mtllib ${lib.join(' ')}\n`);
this.objects.forEach(obj => output += `o ${obj.join(' ')}\n`);
this.groups.forEach(group => output += `g ${group.join(' ')}\n`);
this.smoothingGroups.forEach(smooth => output += `s ${smooth.join(' ')}\n`);
this.materials.forEach(mat => output += `usemtl ${mat.join(' ')}\n`);
this.vertices.forEach(vert => output += `v ${vert.join(' ')}\n`);
this.textureCoords.forEach(tc => output += `vt ${tc.join(' ')}\n`);
this.normals.forEach(norm => output += `vn ${norm.join(' ')}\n`);
this.paramVertices.forEach(pv => output += `vp ${pv.join(' ')}\n`);
this.faces.forEach(face => output += `f ${face.join(' ')}\n`);
this.lines.forEach(ln => output += `l ${ln.join(' ')}\n`);
fs.writeFileSync(outputPath, output);
}
printProperties() {
console.log('Vertices:', this.vertices.length);
console.log('Texture Coordinates:', this.textureCoords.length);
console.log('Normals:', this.normals.length);
console.log('Parameter Vertices:', this.paramVertices.length);
console.log('Faces:', this.faces.length);
console.log('Lines:', this.lines.length);
console.log('Objects:', this.objects.length);
console.log('Groups:', this.groups.length);
console.log('Smoothing Groups:', this.smoothingGroups.length);
console.log('Materials:', this.materials);
console.log('Material Libraries:', this.mtllib);
console.log('Comments:', this.comments.length);
}
}
// Example usage:
// const handler = new OBJHandler('example.obj');
// handler.printProperties();
// handler.write('output.obj');
7. C++ Class for .OBJ File Handling
The following C++ class can open, read, decode (parse), write, and print the properties of a .OBJ file to the console. It uses standard library components for file I/O.
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
class OBJHandler {
private:
std::string filepath;
std::vector<std::vector<std::string>> vertices;
std::vector<std::vector<std::string>> texture_coords;
std::vector<std::vector<std::string>> normals;
std::vector<std::vector<std::string>> param_vertices;
std::vector<std::vector<std::string>> faces;
std::vector<std::vector<std::string>> lines;
std::vector<std::vector<std::string>> objects;
std::vector<std::vector<std::string>> groups;
std::vector<std::vector<std::string>> smoothing_groups;
std::vector<std::vector<std::string>> materials;
std::vector<std::vector<std::string>> mtllib;
std::vector<std::string> comments;
public:
OBJHandler(const std::string& fp) : filepath(fp) {
read();
}
void read() {
std::ifstream file(filepath);
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string keyword;
iss >> keyword;
if (keyword.empty()) continue;
std::vector<std::string> parts;
std::string part;
while (iss >> part) {
parts.push_back(part);
}
if (keyword == "#") {
comments.push_back(line);
} else if (keyword == "v") {
vertices.push_back(parts);
} else if (keyword == "vt") {
texture_coords.push_back(parts);
} else if (keyword == "vn") {
normals.push_back(parts);
} else if (keyword == "vp") {
param_vertices.push_back(parts);
} else if (keyword == "f") {
faces.push_back(parts);
} else if (keyword == "l") {
lines.push_back(parts);
} else if (keyword == "o") {
objects.push_back(parts);
} else if (keyword == "g") {
groups.push_back(parts);
} else if (keyword == "s") {
smoothing_groups.push_back(parts);
} else if (keyword == "usemtl") {
materials.push_back(parts);
} else if (keyword == "mtllib") {
mtllib.push_back(parts);
}
}
file.close();
}
void write(const std::string& output_path) {
std::ofstream file(output_path);
for (const auto& comment : comments) {
file << comment << "\n";
}
for (const auto& lib : mtllib) {
file << "mtllib";
for (const auto& p : lib) file << " " << p;
file << "\n";
}
for (const auto& obj : objects) {
file << "o";
for (const auto& p : obj) file << " " << p;
file << "\n";
}
for (const auto& group : groups) {
file << "g";
for (const auto& p : group) file << " " << p;
file << "\n";
}
for (const auto& smooth : smoothing_groups) {
file << "s";
for (const auto& p : smooth) file << " " << p;
file << "\n";
}
for (const auto& mat : materials) {
file << "usemtl";
for (const auto& p : mat) file << " " << p;
file << "\n";
}
for (const auto& vert : vertices) {
file << "v";
for (const auto& p : vert) file << " " << p;
file << "\n";
}
for (const auto& tc : texture_coords) {
file << "vt";
for (const auto& p : tc) file << " " << p;
file << "\n";
}
for (const auto& norm : normals) {
file << "vn";
for (const auto& p : norm) file << " " << p;
file << "\n";
}
for (const auto& pv : param_vertices) {
file << "vp";
for (const auto& p : pv) file << " " << p;
file << "\n";
}
for (const auto& face : faces) {
file << "f";
for (const auto& p : face) file << " " << p;
file << "\n";
}
for (const auto& ln : lines) {
file << "l";
for (const auto& p : ln) file << " " << p;
file << "\n";
}
file.close();
}
void print_properties() {
std::cout << "Vertices: " << vertices.size() << std::endl;
std::cout << "Texture Coordinates: " << texture_coords.size() << std::endl;
std::cout << "Normals: " << normals.size() << std::endl;
std::cout << "Parameter Vertices: " << param_vertices.size() << std::endl;
std::cout << "Faces: " << faces.size() << std::endl;
std::cout << "Lines: " << lines.size() << std::endl;
std::cout << "Objects: " << objects.size() << std::endl;
std::cout << "Groups: " << groups.size() << std::endl;
std::cout << "Smoothing Groups: " << smoothing_groups.size() << std::endl;
std::cout << "Materials: ";
for (const auto& mat : materials) {
for (const auto& p : mat) std::cout << p << " ";
}
std::cout << std::endl;
std::cout << "Material Libraries: ";
for (const auto& lib : mtllib) {
for (const auto& p : lib) std::cout << p << " ";
}
std::cout << std::endl;
std::cout << "Comments: " << comments.size() << std::endl;
}
};
// Example usage:
// int main() {
// OBJHandler handler("example.obj");
// handler.print_properties();
// handler.write("output.obj");
// return 0;
// }