/**
* Copyright 2012 Jason Sorensen (sorensenj@smert.net)
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package net.smert.frameworkgl.opengl.model.obj;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.smert.frameworkgl.Files;
import net.smert.frameworkgl.Fw;
import net.smert.frameworkgl.opengl.mesh.Mesh;
import net.smert.frameworkgl.opengl.model.ModelReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* http://paulbourke.net/dataformats/mtl/
*
* @author Jason Sorensen <sorensenj@smert.net>
*/
public class MaterialReader implements ModelReader {
private final static Logger log = LoggerFactory.getLogger(MaterialReader.class);
private final List<Material> materials;
private final List<String> comments;
private Material currentMaterial;
private String materialName;
public MaterialReader() {
materials = new ArrayList<>();
comments = new ArrayList<>();
reset();
}
private void addComment(StringTokenizer tokenizer) {
String comment = getRemainingTokens(tokenizer);
if (comment.length() <= 0) {
return;
}
comments.add(comment);
}
private Color createColor(StringTokenizer tokenizer) {
int index = 0;
Color color = new Color();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (token) {
case "spectral":
log.warn("Skipping color. Don't support spectral definitions.");
return null;
case "xyz":
log.warn("Skipping color. Don't support xyz definitions.");
return null;
}
color.set(index++, Float.parseFloat(token));
}
assert (index == 3);
return color;
}
private void createNewMaterial(StringTokenizer tokenizer) {
materialName = getNextTokenOnly(tokenizer);
if (currentMaterial != null) {
materials.add(currentMaterial);
}
currentMaterial = new Material(materialName);
log.debug("Creating a new material: {}", materialName);
}
private String getNextTokenOnly(StringTokenizer tokenizer) {
if (!tokenizer.hasMoreTokens()) {
return "";
}
return tokenizer.nextToken();
}
private String getRemainingTokens(StringTokenizer tokenizer) {
StringBuilder remainingTokens = new StringBuilder(64);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
remainingTokens.append(token).append(" ");
}
if (remainingTokens.length() > 0) {
int count = Character.charCount(remainingTokens.codePointAt(remainingTokens.length() - 1));
while (count > 0) {
remainingTokens.deleteCharAt(remainingTokens.length() - 1);
count--;
}
}
return remainingTokens.toString();
}
private void parse(String line) {
StringTokenizer tokenizer = new StringTokenizer(line);
if (!tokenizer.hasMoreTokens()) {
return;
}
int totalTokens = tokenizer.countTokens();
String token = tokenizer.nextToken();
switch (token) {
case "#":
// Ex: "# Some random comment"
addComment(tokenizer);
break;
case "d":
// Ex: "d factor"
setDissolve(tokenizer);
break;
case "illum":
// Ex: "illum illum_#"
setIlluminationMode(tokenizer);
break;
case "Ka":
// Ex: "Ka 0.000000 0.000000 0.000000"
if (totalTokens == 4) {
setAmbient(tokenizer);
} else {
log.warn("Invalid ambient definition: {}", line);
}
break;
case "Kd":
// Ex: "Kd 0.8 0.8 0.8"
if (totalTokens == 4) {
setDiffuse(tokenizer);
} else {
log.warn("Invalid diffuse definition: {}", line);
}
break;
case "Ks":
// Ex: "Ks 0.8 0.8 0.8"
if (totalTokens == 4) {
setSpecular(tokenizer);
} else {
log.warn("Invalid specular definition: {}", line);
}
break;
case "map_Ka":
if (totalTokens == 2) {
setAmbientMap(tokenizer);
} else {
log.warn("Don't support optional arguments. Skipping ambient map definition: {}", line);
}
break;
case "map_Kd":
if (totalTokens == 2) {
setDiffuseMap(tokenizer);
} else {
log.warn("Don't support optional arguments. Skipping diffuse map definition: {}", line);
}
break;
case "map_Ks":
if (totalTokens == 2) {
setSpecularMap(tokenizer);
} else {
log.warn("Don't support optional arguments. Skipping specular map definition: {}", line);
}
break;
case "map_Ns":
if (totalTokens == 2) {
setSpecularExponentMap(tokenizer);
} else {
log.warn("Don't support optional arguments. Skipping specular exponent map definition: {}", line);
}
break;
case "newmtl":
// Ex: "newmtl my_mtl"
createNewMaterial(tokenizer);
break;
case "Ni":
// Ex: "Ni optical_density"
setOpticalDensity(tokenizer);
break;
case "Ns":
// Ex: "Ns exponent"
setSpecularExponent(tokenizer);
break;
default:
log.warn("Skipped line with an unsupported token: " + line);
}
}
private void read(String filename) throws IOException {
Files.FileAsset fileAsset = Fw.files.getMesh(filename);
try (InputStream is = fileAsset.openStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr)) {
String line;
while ((line = reader.readLine()) != null) {
parse(line);
}
}
}
private void setAmbient(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping ambient.");
return;
}
Color color = createColor(tokenizer);
if (color != null) {
currentMaterial.setAmbient(color);
}
}
private void setAmbientMap(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping ambient map.");
return;
}
String ambientMapFilename = getNextTokenOnly(tokenizer);
currentMaterial.setAmbientMapFilename(ambientMapFilename);
}
private void setDiffuse(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping diffuse.");
return;
}
Color color = createColor(tokenizer);
if (color != null) {
currentMaterial.setDiffuse(color);
}
}
private void setDiffuseMap(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping diffuse map.");
return;
}
String diffuseMapFilename = getNextTokenOnly(tokenizer);
currentMaterial.setDiffuseMapFilename(diffuseMapFilename);
}
private void setDissolve(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping dissolve.");
return;
}
String dissolve = getNextTokenOnly(tokenizer);
currentMaterial.setDissolve(dissolve);
}
private void setIlluminationMode(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping illumination mode.");
return;
}
String illuminationMode = getNextTokenOnly(tokenizer);
currentMaterial.setIlluminationMode(illuminationMode);
}
private void setOpticalDensity(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping optical density.");
return;
}
String opticalDensity = getNextTokenOnly(tokenizer);
currentMaterial.setOpticalDensity(opticalDensity);
}
private void setSpecular(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping specular.");
return;
}
Color color = createColor(tokenizer);
if (color != null) {
currentMaterial.setSpecular(color);
}
}
private void setSpecularExponent(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping specular exponent.");
return;
}
String exponent = getNextTokenOnly(tokenizer);
currentMaterial.setSpecularExponent(exponent);
}
private void setSpecularExponentMap(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping specular exponent map.");
return;
}
String specularExponentMapFilename = getNextTokenOnly(tokenizer);
currentMaterial.setSpecularExponentMapFilename(specularExponentMapFilename);
}
private void setSpecularMap(StringTokenizer tokenizer) {
if (currentMaterial == null) {
log.warn("Current material was NULL. Skipping specular map.");
return;
}
String specularMapFilename = getNextTokenOnly(tokenizer);
currentMaterial.setSpecularMapFilename(specularMapFilename);
}
public List<Material> getMaterials() {
return materials;
}
public List<String> getComments() {
return comments;
}
public String getMaterialName() {
return materialName;
}
public final void reset() {
materials.clear();
comments.clear();
currentMaterial = null;
materialName = "";
}
@Override
public void load(String filename, Mesh mesh) throws IOException {
log.info("Loading MTL definition: {}", filename);
reset();
read(filename);
// The last material is not added until here
if (currentMaterial != null) {
materials.add(currentMaterial);
}
}
public static class Color {
private boolean hasBeenSet;
private float r;
private float g;
private float b;
private Color() {
hasBeenSet = false;
r = 0f;
g = 0f;
b = 0f;
}
public float getR() {
return r;
}
public float getG() {
return g;
}
public float getB() {
return b;
}
public boolean hasBeenSet() {
return hasBeenSet;
}
public void set(int index, float value) {
hasBeenSet = true;
if (index == 0) {
r = value;
} else if (index == 1) {
g = value;
} else if (index == 2) {
b = value;
} else {
throw new IllegalArgumentException("Unknown index: " + index);
}
}
public void set(Color color) {
hasBeenSet = true;
r = color.r;
g = color.g;
b = color.b;
}
}
public static class Material {
private float dissolve;
private float opticalDensity;
private float specularExponent;
private int illuminationMode;
private final Color ambient;
private final Color diffuse;
private final Color specular;
private String ambientMapFilename;
private String diffuseMapFilename;
private final String materialName;
private String specularMapFilename;
private String specularExponentMapFilename;
private Material(String materialName) {
dissolve = 1f;
opticalDensity = 1f;
specularExponent = 0;
illuminationMode = 0;
ambient = new Color();
diffuse = new Color();
specular = new Color();
ambientMapFilename = "";
diffuseMapFilename = "";
this.materialName = materialName;
specularMapFilename = "";
specularExponentMapFilename = "";
}
public int convertSpecularExponent() {
float percent = specularExponent / 1000f; // Max is 1000
return (int) (128f * percent); // OpenGL max shininess 128
}
public float getDissolve() {
return dissolve;
}
public void setDissolve(String dissolve) {
this.dissolve = Float.parseFloat(dissolve);
}
public float getOpticalDensity() {
return opticalDensity;
}
public void setOpticalDensity(String opticalDensity) {
this.opticalDensity = Float.parseFloat(opticalDensity);
}
public float getSpecularExponent() {
return specularExponent;
}
public void setSpecularExponent(String specularExponent) {
this.specularExponent = Float.parseFloat(specularExponent);
}
public int getIlluminationMode() {
return illuminationMode;
}
public void setIlluminationMode(String illuminationMode) {
this.illuminationMode = Integer.parseInt(illuminationMode);
}
public Color getAmbient() {
return ambient;
}
public void setAmbient(Color ambient) {
this.ambient.set(ambient);
}
public Color getDiffuse() {
return diffuse;
}
public void setDiffuse(Color diffuse) {
this.diffuse.set(diffuse);
}
public Color getSpecular() {
return specular;
}
public void setSpecular(Color specular) {
this.specular.set(specular);
}
public String getAmbientMapFilename() {
return ambientMapFilename;
}
public void setAmbientMapFilename(String ambientMapFilename) {
this.ambientMapFilename = ambientMapFilename;
}
public String getDiffuseMapFilename() {
return diffuseMapFilename;
}
public void setDiffuseMapFilename(String diffuseMapFilename) {
this.diffuseMapFilename = diffuseMapFilename;
}
public String getMaterialName() {
return materialName;
}
public String getSpecularMapFilename() {
return specularMapFilename;
}
public void setSpecularMapFilename(String specularMapFilename) {
this.specularMapFilename = specularMapFilename;
}
public String getSpecularExponentMapFilename() {
return specularExponentMapFilename;
}
public void setSpecularExponentMapFilename(String specularExponentMapFilename) {
this.specularExponentMapFilename = specularExponentMapFilename;
}
}
}