1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.material.plugins; 34 35 import com.jme3.asset.*; 36 import com.jme3.material.RenderState.BlendMode; 37 import com.jme3.material.RenderState.FaceCullMode; 38 import com.jme3.material.*; 39 import com.jme3.material.TechniqueDef.LightMode; 40 import com.jme3.material.TechniqueDef.ShadowMode; 41 import com.jme3.math.ColorRGBA; 42 import com.jme3.math.Vector2f; 43 import com.jme3.math.Vector3f; 44 import com.jme3.shader.VarType; 45 import com.jme3.texture.Texture; 46 import com.jme3.texture.Texture.WrapMode; 47 import com.jme3.texture.Texture2D; 48 import com.jme3.util.PlaceholderAssets; 49 import com.jme3.util.blockparser.BlockLanguageParser; 50 import com.jme3.util.blockparser.Statement; 51 import java.io.IOException; 52 import java.io.InputStream; 53 import java.util.List; 54 import java.util.logging.Level; 55 import java.util.logging.Logger; 56 57 public class J3MLoader implements AssetLoader { 58 59 private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); 60 61 private AssetManager assetManager; 62 private AssetKey key; 63 64 private MaterialDef materialDef; 65 private Material material; 66 private TechniqueDef technique; 67 private RenderState renderState; 68 69 private String shaderLang; 70 private String vertName; 71 private String fragName; 72 73 private static final String whitespacePattern = "\\p{javaWhitespace}+"; 74 75 public J3MLoader(){ 76 } 77 78 private void throwIfNequal(String expected, String got) throws IOException { 79 if (expected == null) 80 throw new IOException("Expected a statement, got '"+got+"'!"); 81 82 if (!expected.equals(got)) 83 throw new IOException("Expected '"+expected+"', got '"+got+"'!"); 84 } 85 86 // <TYPE> <LANG> : <SOURCE> 87 private void readShaderStatement(String statement) throws IOException { 88 String[] split = statement.split(":"); 89 if (split.length != 2){ 90 throw new IOException("Shader statement syntax incorrect" + statement); 91 } 92 String[] typeAndLang = split[0].split(whitespacePattern); 93 if (typeAndLang.length != 2){ 94 throw new IOException("Shader statement syntax incorrect: " + statement); 95 } 96 shaderLang = typeAndLang[1]; 97 if (typeAndLang[0].equals("VertexShader")){ 98 vertName = split[1].trim(); 99 }else if (typeAndLang[0].equals("FragmentShader")){ 100 fragName = split[1].trim(); 101 } 102 } 103 104 // LightMode <MODE> 105 private void readLightMode(String statement) throws IOException{ 106 String[] split = statement.split(whitespacePattern); 107 if (split.length != 2){ 108 throw new IOException("LightMode statement syntax incorrect"); 109 } 110 LightMode lm = LightMode.valueOf(split[1]); 111 technique.setLightMode(lm); 112 } 113 114 // ShadowMode <MODE> 115 private void readShadowMode(String statement) throws IOException{ 116 String[] split = statement.split(whitespacePattern); 117 if (split.length != 2){ 118 throw new IOException("ShadowMode statement syntax incorrect"); 119 } 120 ShadowMode sm = ShadowMode.valueOf(split[1]); 121 technique.setShadowMode(sm); 122 } 123 124 private Object readValue(VarType type, String value) throws IOException{ 125 if (type.isTextureType()){ 126 // String texturePath = readString("[\n;(//)(\\})]"); 127 String texturePath = value.trim(); 128 boolean flipY = false; 129 boolean repeat = false; 130 if (texturePath.startsWith("Flip Repeat ")){ 131 texturePath = texturePath.substring(12).trim(); 132 flipY = true; 133 repeat = true; 134 }else if (texturePath.startsWith("Flip ")){ 135 texturePath = texturePath.substring(5).trim(); 136 flipY = true; 137 }else if (texturePath.startsWith("Repeat ")){ 138 texturePath = texturePath.substring(7).trim(); 139 repeat = true; 140 } 141 142 TextureKey texKey = new TextureKey(texturePath, flipY); 143 texKey.setAsCube(type == VarType.TextureCubeMap); 144 texKey.setGenerateMips(true); 145 146 Texture tex; 147 try { 148 tex = assetManager.loadTexture(texKey); 149 } catch (AssetNotFoundException ex){ 150 logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); 151 tex = null; 152 } 153 if (tex != null){ 154 if (repeat){ 155 tex.setWrap(WrapMode.Repeat); 156 } 157 }else{ 158 tex = new Texture2D(PlaceholderAssets.getPlaceholderImage()); 159 } 160 return tex; 161 }else{ 162 String[] split = value.trim().split(whitespacePattern); 163 switch (type){ 164 case Float: 165 if (split.length != 1){ 166 throw new IOException("Float value parameter must have 1 entry: " + value); 167 } 168 return Float.parseFloat(split[0]); 169 case Vector2: 170 if (split.length != 2){ 171 throw new IOException("Vector2 value parameter must have 2 entries: " + value); 172 } 173 return new Vector2f(Float.parseFloat(split[0]), 174 Float.parseFloat(split[1])); 175 case Vector3: 176 if (split.length != 3){ 177 throw new IOException("Vector3 value parameter must have 3 entries: " + value); 178 } 179 return new Vector3f(Float.parseFloat(split[0]), 180 Float.parseFloat(split[1]), 181 Float.parseFloat(split[2])); 182 case Vector4: 183 if (split.length != 4){ 184 throw new IOException("Vector4 value parameter must have 4 entries: " + value); 185 } 186 return new ColorRGBA(Float.parseFloat(split[0]), 187 Float.parseFloat(split[1]), 188 Float.parseFloat(split[2]), 189 Float.parseFloat(split[3])); 190 case Int: 191 if (split.length != 1){ 192 throw new IOException("Int value parameter must have 1 entry: " + value); 193 } 194 return Integer.parseInt(split[0]); 195 case Boolean: 196 if (split.length != 1){ 197 throw new IOException("Boolean value parameter must have 1 entry: " + value); 198 } 199 return Boolean.parseBoolean(split[0]); 200 default: 201 throw new UnsupportedOperationException("Unknown type: "+type); 202 } 203 } 204 } 205 206 // <TYPE> <NAME> [ "(" <FFBINDING> ")" ] [ ":" <DEFAULTVAL> ] 207 private void readParam(String statement) throws IOException{ 208 String name; 209 String defaultVal = null; 210 FixedFuncBinding ffBinding = null; 211 212 String[] split = statement.split(":"); 213 214 // Parse default val 215 if (split.length == 1){ 216 // Doesn't contain default value 217 }else{ 218 if (split.length != 2){ 219 throw new IOException("Parameter statement syntax incorrect"); 220 } 221 statement = split[0].trim(); 222 defaultVal = split[1].trim(); 223 } 224 225 // Parse ffbinding 226 int startParen = statement.indexOf("("); 227 if (startParen != -1){ 228 // get content inside parentheses 229 int endParen = statement.indexOf(")", startParen); 230 String bindingStr = statement.substring(startParen+1, endParen).trim(); 231 try { 232 ffBinding = FixedFuncBinding.valueOf(bindingStr); 233 } catch (IllegalArgumentException ex){ 234 throw new IOException("FixedFuncBinding '" + 235 split[1] + "' does not exist!"); 236 } 237 statement = statement.substring(0, startParen); 238 } 239 240 // Parse type + name 241 split = statement.split(whitespacePattern); 242 if (split.length != 2){ 243 throw new IOException("Parameter statement syntax incorrect"); 244 } 245 246 VarType type; 247 if (split[0].equals("Color")){ 248 type = VarType.Vector4; 249 }else{ 250 type = VarType.valueOf(split[0]); 251 } 252 253 name = split[1]; 254 255 Object defaultValObj = null; 256 if (defaultVal != null){ 257 defaultValObj = readValue(type, defaultVal); 258 } 259 260 materialDef.addMaterialParam(type, name, defaultValObj, ffBinding); 261 } 262 263 private void readValueParam(String statement) throws IOException{ 264 // Use limit=1 incase filename contains colons 265 String[] split = statement.split(":", 2); 266 if (split.length != 2){ 267 throw new IOException("Value parameter statement syntax incorrect"); 268 } 269 String name = split[0].trim(); 270 271 // parse value 272 MatParam p = material.getMaterialDef().getMaterialParam(name); 273 if (p == null){ 274 throw new IOException("The material parameter: "+name+" is undefined."); 275 } 276 277 Object valueObj = readValue(p.getVarType(), split[1]); 278 if (p.getVarType().isTextureType()){ 279 material.setTextureParam(name, p.getVarType(), (Texture) valueObj); 280 }else{ 281 material.setParam(name, p.getVarType(), valueObj); 282 } 283 } 284 285 private void readMaterialParams(List<Statement> paramsList) throws IOException{ 286 for (Statement statement : paramsList){ 287 readParam(statement.getLine()); 288 } 289 } 290 291 private void readExtendingMaterialParams(List<Statement> paramsList) throws IOException{ 292 for (Statement statement : paramsList){ 293 readValueParam(statement.getLine()); 294 } 295 } 296 297 private void readWorldParams(List<Statement> worldParams) throws IOException{ 298 for (Statement statement : worldParams){ 299 technique.addWorldParam(statement.getLine()); 300 } 301 } 302 303 private boolean parseBoolean(String word){ 304 return word != null && word.equals("On"); 305 } 306 307 private void readRenderStateStatement(String statement) throws IOException{ 308 String[] split = statement.split(whitespacePattern); 309 if (split[0].equals("Wireframe")){ 310 renderState.setWireframe(parseBoolean(split[1])); 311 }else if (split[0].equals("FaceCull")){ 312 renderState.setFaceCullMode(FaceCullMode.valueOf(split[1])); 313 }else if (split[0].equals("DepthWrite")){ 314 renderState.setDepthWrite(parseBoolean(split[1])); 315 }else if (split[0].equals("DepthTest")){ 316 renderState.setDepthTest(parseBoolean(split[1])); 317 }else if (split[0].equals("Blend")){ 318 renderState.setBlendMode(BlendMode.valueOf(split[1])); 319 }else if (split[0].equals("AlphaTestFalloff")){ 320 renderState.setAlphaTest(true); 321 renderState.setAlphaFallOff(Float.parseFloat(split[1])); 322 }else if (split[0].equals("PolyOffset")){ 323 float factor = Float.parseFloat(split[1]); 324 float units = Float.parseFloat(split[2]); 325 renderState.setPolyOffset(factor, units); 326 }else if (split[0].equals("ColorWrite")){ 327 renderState.setColorWrite(parseBoolean(split[1])); 328 }else if (split[0].equals("PointSprite")){ 329 renderState.setPointSprite(parseBoolean(split[1])); 330 }else{ 331 throwIfNequal(null, split[0]); 332 } 333 } 334 335 private void readAdditionalRenderState(List<Statement> renderStates) throws IOException{ 336 renderState = material.getAdditionalRenderState(); 337 for (Statement statement : renderStates){ 338 readRenderStateStatement(statement.getLine()); 339 } 340 renderState = null; 341 } 342 343 private void readRenderState(List<Statement> renderStates) throws IOException{ 344 renderState = new RenderState(); 345 for (Statement statement : renderStates){ 346 readRenderStateStatement(statement.getLine()); 347 } 348 technique.setRenderState(renderState); 349 renderState = null; 350 } 351 352 // <DEFINENAME> [ ":" <PARAMNAME> ] 353 private void readDefine(String statement) throws IOException{ 354 String[] split = statement.split(":"); 355 if (split.length == 1){ 356 // add preset define 357 technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true); 358 }else if (split.length == 2){ 359 technique.addShaderParamDefine(split[1].trim(), split[0].trim()); 360 }else{ 361 throw new IOException("Define syntax incorrect"); 362 } 363 } 364 365 private void readDefines(List<Statement> defineList) throws IOException{ 366 for (Statement statement : defineList){ 367 readDefine(statement.getLine()); 368 } 369 370 } 371 372 private void readTechniqueStatement(Statement statement) throws IOException{ 373 String[] split = statement.getLine().split("[ \\{]"); 374 if (split[0].equals("VertexShader") || 375 split[0].equals("FragmentShader")){ 376 readShaderStatement(statement.getLine()); 377 }else if (split[0].equals("LightMode")){ 378 readLightMode(statement.getLine()); 379 }else if (split[0].equals("ShadowMode")){ 380 readShadowMode(statement.getLine()); 381 }else if (split[0].equals("WorldParameters")){ 382 readWorldParams(statement.getContents()); 383 }else if (split[0].equals("RenderState")){ 384 readRenderState(statement.getContents()); 385 }else if (split[0].equals("Defines")){ 386 readDefines(statement.getContents()); 387 }else{ 388 throwIfNequal(null, split[0]); 389 } 390 } 391 392 private void readTransparentStatement(String statement) throws IOException{ 393 String[] split = statement.split(whitespacePattern); 394 if (split.length != 2){ 395 throw new IOException("Transparent statement syntax incorrect"); 396 } 397 material.setTransparent(parseBoolean(split[1])); 398 } 399 400 private void readTechnique(Statement techStat) throws IOException{ 401 String[] split = techStat.getLine().split(whitespacePattern); 402 if (split.length == 1){ 403 technique = new TechniqueDef(null); 404 }else if (split.length == 2){ 405 technique = new TechniqueDef(split[1]); 406 }else{ 407 throw new IOException("Technique statement syntax incorrect"); 408 } 409 410 for (Statement statement : techStat.getContents()){ 411 readTechniqueStatement(statement); 412 } 413 414 if (vertName != null && fragName != null){ 415 technique.setShaderFile(vertName, fragName, shaderLang); 416 } 417 418 materialDef.addTechniqueDef(technique); 419 technique = null; 420 vertName = null; 421 fragName = null; 422 shaderLang = null; 423 } 424 425 private void loadFromRoot(List<Statement> roots) throws IOException{ 426 if (roots.size() == 2){ 427 Statement exception = roots.get(0); 428 String line = exception.getLine(); 429 if (line.startsWith("Exception")){ 430 throw new AssetLoadException(line.substring("Exception ".length())); 431 }else{ 432 throw new IOException("In multiroot material, expected first statement to be 'Exception'"); 433 } 434 }else if (roots.size() != 1){ 435 throw new IOException("Too many roots in J3M/J3MD file"); 436 } 437 438 boolean extending = false; 439 Statement materialStat = roots.get(0); 440 String materialName = materialStat.getLine(); 441 if (materialName.startsWith("MaterialDef")){ 442 materialName = materialName.substring("MaterialDef ".length()).trim(); 443 extending = false; 444 }else if (materialName.startsWith("Material")){ 445 materialName = materialName.substring("Material ".length()).trim(); 446 extending = true; 447 }else{ 448 throw new IOException("Specified file is not a Material file"); 449 } 450 451 String[] split = materialName.split(":", 2); 452 453 if (materialName.equals("")){ 454 throw new IOException("Material name cannot be empty"); 455 } 456 457 if (split.length == 2){ 458 if (!extending){ 459 throw new IOException("Must use 'Material' when extending."); 460 } 461 462 String extendedMat = split[1].trim(); 463 464 MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat)); 465 if (def == null) 466 throw new IOException("Extended material "+extendedMat+" cannot be found."); 467 468 material = new Material(def); 469 material.setKey(key); 470 // material.setAssetName(fileName); 471 }else if (split.length == 1){ 472 if (extending){ 473 throw new IOException("Expected ':', got '{'"); 474 } 475 materialDef = new MaterialDef(assetManager, materialName); 476 // NOTE: pass file name for defs so they can be loaded later 477 materialDef.setAssetName(key.getName()); 478 }else{ 479 throw new IOException("Cannot use colon in material name/path"); 480 } 481 482 for (Statement statement : materialStat.getContents()){ 483 split = statement.getLine().split("[ \\{]"); 484 String statType = split[0]; 485 if (extending){ 486 if (statType.equals("MaterialParameters")){ 487 readExtendingMaterialParams(statement.getContents()); 488 }else if (statType.equals("AdditionalRenderState")){ 489 readAdditionalRenderState(statement.getContents()); 490 }else if (statType.equals("Transparent")){ 491 readTransparentStatement(statement.getLine()); 492 } 493 }else{ 494 if (statType.equals("Technique")){ 495 readTechnique(statement); 496 }else if (statType.equals("MaterialParameters")){ 497 readMaterialParams(statement.getContents()); 498 }else{ 499 throw new IOException("Expected material statement, got '"+statType+"'"); 500 } 501 } 502 } 503 } 504 505 public Object load(AssetInfo info) throws IOException { 506 this.assetManager = info.getManager(); 507 508 InputStream in = info.openStream(); 509 try { 510 key = info.getKey(); 511 loadFromRoot(BlockLanguageParser.parse(in)); 512 } finally { 513 if (in != null){ 514 in.close(); 515 } 516 } 517 518 if (material != null){ 519 if (!(info.getKey() instanceof MaterialKey)){ 520 throw new IOException("Material instances must be loaded via MaterialKey"); 521 } 522 // material implementation 523 return material; 524 }else{ 525 // material definition 526 return materialDef; 527 } 528 } 529 530 } 531