xxxxxxxxxx
195
// model from: https://free3d.com/3d-model/low-poly-tree-18385.html
//
// Click and drag the mouse to view the scene from different angles.
let shape;
// Load the file and create a p5.Geometry object.
// Normalize the geometry's size to fit the canvas.
function preload() {
shape = loadModel('Tree_low.obj',true);
}
let input;
function setup() {
createCanvas(400, 400, WEBGL);
input = createFileInput(handleFileInput);
input.elt.accept = '.obj';
input.position(5, 5);
}
function draw() {
background(200);
// Enable orbiting with the mouse.
orbitControl();
// Lights
ambientLight(120,120,140);
spotLight(255, 255, 255, 0, -height / 2, 200, 0, 0.5, -1, 30);
// Draw the shape.
push();
rotateX(PI);
rotateY(frameCount/120);
model(shape);
pop();
/*
push();
translate(100,-50,-10);
scale(.5);
model(shape);
pop();*/
}
function handleFileInput(file) {
let base64Str = file.data.split(",")[1];
//console.log(file.data );
// DEBUG console.log( atob( base64Str ) );
let modelLines = atob( base64Str );
shape.reset();
shape = new p5.Geometry();
parseObj(shape, modelLines.split('\n'));
console.log("loaded",file.name);
input.hide();
}
// from original P5 repository
function parseObj(model, lines, materials= {}) {
// OBJ allows a face to specify an index for a vertex (in the above example),
// but it also allows you to specify a custom combination of vertex, UV
// coordinate, and vertex normal. So, "3/4/3" would mean, "use vertex 3 with
// UV coordinate 4 and vertex normal 3". In WebGL, every vertex with different
// parameters must be a different vertex, so loadedVerts is used to
// temporarily store the parsed vertices, normals, etc., and indexedVerts is
// used to map a specific combination (keyed on, for example, the string
// "3/4/3"), to the actual index of the newly created vertex in the final
// object.
const loadedVerts = {
v: [],
vt: [],
vn: []
};
// Map from source index → Map of material → destination index
const usedVerts = {}; // Track colored vertices
let currentMaterial = null;
const coloredVerts = new Set(); //unique vertices with color
let hasColoredVertices = false;
let hasColorlessVertices = false;
for (let line = 0; line < lines.length; ++line) {
// Each line is a separate object (vertex, face, vertex normal, etc)
// For each line, split it into tokens on whitespace. The first token
// describes the type.
const tokens = lines[line].trim().split(/\b\s+/);
if (tokens.length > 0) {
if (tokens[0] === 'usemtl') {
// Switch to a new material
currentMaterial = tokens[1];
}else if (tokens[0] === 'v' || tokens[0] === 'vn') {
// Check if this line describes a vertex or vertex normal.
// It will have three numeric parameters.
const vertex = new p5.Vector(
parseFloat(tokens[1]),
parseFloat(tokens[2]),
parseFloat(tokens[3])
);
loadedVerts[tokens[0]].push(vertex);
} else if (tokens[0] === 'vt') {
// Check if this line describes a texture coordinate.
// It will have two numeric parameters U and V (W is omitted).
// Because of WebGL texture coordinates rendering behaviour, the V
// coordinate is inversed.
const texVertex = [parseFloat(tokens[1]), 1 - parseFloat(tokens[2])];
loadedVerts[tokens[0]].push(texVertex);
} else if (tokens[0] === 'f') {
// Check if this line describes a face.
// OBJ faces can have more than three points. Triangulate points.
for (let tri = 3; tri < tokens.length; ++tri) {
const face = [];
const vertexTokens = [1, tri - 1, tri];
for (let tokenInd = 0; tokenInd < vertexTokens.length; ++tokenInd) {
// Now, convert the given token into an index
const vertString = tokens[vertexTokens[tokenInd]];
let vertParts=vertString.split('/');
// TODO: Faces can technically use negative numbers to refer to the
// previous nth vertex. I haven't seen this used in practice, but
// it might be good to implement this in the future.
for (let i = 0; i < vertParts.length; i++) {
vertParts[i] = parseInt(vertParts[i]) - 1;
}
if (!usedVerts[vertString]) {
usedVerts[vertString] = {};
}
if (usedVerts[vertString][currentMaterial] === undefined) {
const vertIndex = model.vertices.length;
model.vertices.push(loadedVerts.v[vertParts[0]].copy());
model.uvs.push(loadedVerts.vt[vertParts[1]] ?
loadedVerts.vt[vertParts[1]].slice() : [0, 0]);
model.vertexNormals.push(loadedVerts.vn[vertParts[2]] ?
loadedVerts.vn[vertParts[2]].copy() : new p5.Vector());
usedVerts[vertString][currentMaterial] = vertIndex;
face.push(vertIndex);
if (currentMaterial
&& materials[currentMaterial]
&& materials[currentMaterial].diffuseColor) {
// Mark this vertex as colored
coloredVerts.add(loadedVerts.v[vertParts[0]]); //since a set would only push unique values
}
} else {
face.push(usedVerts[vertString][currentMaterial]);
}
}
if (
face[0] !== face[1] &&
face[0] !== face[2] &&
face[1] !== face[2]
) {
model.faces.push(face);
//same material for all vertices in a particular face
if (currentMaterial
&& materials[currentMaterial]
&& materials[currentMaterial].diffuseColor) {
hasColoredVertices=true;
//flag to track color or no color model
hasColoredVertices = true;
const materialDiffuseColor =
materials[currentMaterial].diffuseColor;
for (let i = 0; i < face.length; i++) {
model.vertexColors.push(materialDiffuseColor[0]);
model.vertexColors.push(materialDiffuseColor[1]);
model.vertexColors.push(materialDiffuseColor[2]);
}
}else{
hasColorlessVertices=true;
}
}
}
}
}
}
// If the model doesn't have normals, compute the normals
if (model.vertexNormals.length === 0) {
model.computeNormals();
}
if (hasColoredVertices === hasColorlessVertices) {
// If both are true or both are false, throw an error because the model is inconsistent
throw new Error('Model coloring is inconsistent. Either all vertices should have colors or none should.');
}
return model;
}