/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
import com.jme3.asset.*;
import com.jme3.material.Material;
import com.jme3.material.MaterialList;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.util.PlaceholderAssets;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MTLLoader implements AssetLoader {
private static final Logger logger = Logger.getLogger(MTLLoader.class.getName());
protected Scanner scan;
protected MaterialList matList;
//protected Material material;
protected AssetManager assetManager;
protected String folderName;
protected AssetKey key;
protected Texture diffuseMap, normalMap, specularMap, alphaMap;
protected ColorRGBA ambient = new ColorRGBA();
protected ColorRGBA diffuse = new ColorRGBA();
protected ColorRGBA specular = new ColorRGBA();
protected float shininess = 16;
protected boolean shadeless;
protected String matName;
protected float alpha = 1;
protected boolean transparent = false;
protected boolean disallowAmbient = false;
protected boolean disallowSpecular = false;
public void reset(){
scan = null;
matList = null;
// material = null;
resetMaterial();
}
protected ColorRGBA readColor(){
ColorRGBA v = new ColorRGBA();
v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f);
return v;
}
protected String nextStatement(){
scan.useDelimiter("\n");
String result = scan.next();
scan.useDelimiter("\\p{javaWhitespace}+");
return result;
}
protected boolean skipLine(){
try {
scan.skip(".*\r{0,1}\n");
return true;
} catch (NoSuchElementException ex){
// EOF
return false;
}
}
protected void resetMaterial(){
ambient.set(ColorRGBA.DarkGray);
diffuse.set(ColorRGBA.LightGray);
specular.set(ColorRGBA.Black);
shininess = 16;
disallowAmbient = false;
disallowSpecular = false;
shadeless = false;
transparent = false;
matName = null;
diffuseMap = null;
specularMap = null;
normalMap = null;
alphaMap = null;
alpha = 1;
}
protected void createMaterial(){
Material material;
if (alpha < 1f && transparent){
diffuse.a = alpha;
}
if (shadeless){
material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
material.setColor("Color", diffuse.clone());
material.setTexture("ColorMap", diffuseMap);
// TODO: Add handling for alpha map?
}else{
material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
material.setBoolean("UseMaterialColors", true);
material.setColor("Ambient", ambient.clone());
material.setColor("Diffuse", diffuse.clone());
material.setColor("Specular", specular.clone());
material.setFloat("Shininess", shininess); // prevents "premature culling" bug
if (diffuseMap != null) material.setTexture("DiffuseMap", diffuseMap);
if (specularMap != null) material.setTexture("SpecularMap", specularMap);
if (normalMap != null) material.setTexture("NormalMap", normalMap);
if (alphaMap != null) material.setTexture("AlphaMap", alphaMap);
}
if (transparent){
material.setTransparent(true);
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
material.getAdditionalRenderState().setAlphaTest(true);
material.getAdditionalRenderState().setAlphaFallOff(0.01f);
}
matList.put(matName, material);
}
protected void startMaterial(String name){
if (matName != null){
// material is already in cache, generate it
createMaterial();
}
// now, reset the params and set the name to start a new material
resetMaterial();
matName = name;
}
protected Texture loadTexture(String path){
String[] split = path.trim().split("\\p{javaWhitespace}+");
// will crash if path is an empty string
path = split[split.length-1];
String name = new File(path).getName();
TextureKey texKey = new TextureKey(folderName + name);
texKey.setGenerateMips(true);
Texture texture;
try {
texture = assetManager.loadTexture(texKey);
texture.setWrap(WrapMode.Repeat);
} catch (AssetNotFoundException ex){
logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
texture = new Texture2D(PlaceholderAssets.getPlaceholderImage());
texture.setWrap(WrapMode.Repeat);
texture.setKey(key);
}
return texture;
}
protected boolean readLine(){
if (!scan.hasNext()){
return false;
}
String cmd = scan.next().toLowerCase();
if (cmd.startsWith("#")){
// skip entire comment until next line
return skipLine();
}else if (cmd.equals("newmtl")){
String name = scan.next();
startMaterial(name);
}else if (cmd.equals("ka")){
ambient.set(readColor());
}else if (cmd.equals("kd")){
diffuse.set(readColor());
}else if (cmd.equals("ks")){
specular.set(readColor());
}else if (cmd.equals("ns")){
float shiny = scan.nextFloat();
if (shiny >= 1){
shininess = shiny; /* (128f / 1000f)*/
if (specular.equals(ColorRGBA.Black)){
specular.set(ColorRGBA.White);
}
}else{
// For some reason blender likes to export Ns 0 statements
// Ignore Ns 0 instead of setting it
}
}else if (cmd.equals("d") || cmd.equals("tr")){
float tempAlpha = scan.nextFloat();
if (tempAlpha != 0){
alpha = tempAlpha;
transparent = true;
}
}else if (cmd.equals("map_ka")){
// ignore it for now
return skipLine();
}else if (cmd.equals("map_kd")){
String path = nextStatement();
diffuseMap = loadTexture(path);
}else if (cmd.equals("map_bump") || cmd.equals("bump")){
if (normalMap == null){
String path = nextStatement();
normalMap = loadTexture(path);
}
}else if (cmd.equals("map_ks")){
String path = nextStatement();
specularMap = loadTexture(path);
if (specularMap != null){
// NOTE: since specular color is modulated with specmap
// make sure we have it set
if (specular.equals(ColorRGBA.Black)){
specular.set(ColorRGBA.White);
}
}
}else if (cmd.equals("map_d")){
String path = scan.next();
alphaMap = loadTexture(path);
transparent = true;
}else if (cmd.equals("illum")){
int mode = scan.nextInt();
switch (mode){
case 0:
// no lighting
shadeless = true;
break;
case 1:
disallowSpecular = true;
break;
case 2:
case 3:
case 5:
case 8:
break;
case 4:
case 6:
case 7:
case 9:
// Enable transparency
// Works best if diffuse map has an alpha channel
transparent = true;
break;
}
}else if (cmd.equals("ke") || cmd.equals("ni")){
// Ni: index of refraction - unsupported in jME
// Ke: emission color
return skipLine();
}else{
logger.log(Level.WARNING, "Unknown statement in MTL! {0}", cmd);
return skipLine();
}
return true;
}
@SuppressWarnings("empty-statement")
public Object load(AssetInfo info) throws IOException{
reset();
this.key = info.getKey();
this.assetManager = info.getManager();
folderName = info.getKey().getFolder();
matList = new MaterialList();
InputStream in = null;
try {
in = info.openStream();
scan = new Scanner(in);
scan.useLocale(Locale.US);
while (readLine());
} finally {
if (in != null){
in.close();
}
}
if (matName != null){
// still have a material in the vars
createMaterial();
resetMaterial();
}
MaterialList list = matList;
return list;
}
}