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
33package com.jme3.scene.plugins.ogre;
34
35import com.jme3.asset.*;
36import com.jme3.material.Material;
37import com.jme3.material.MaterialList;
38import com.jme3.material.RenderState;
39import com.jme3.math.ColorRGBA;
40import com.jme3.scene.plugins.ogre.matext.MaterialExtensionLoader;
41import com.jme3.scene.plugins.ogre.matext.MaterialExtensionSet;
42import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
43import com.jme3.texture.Texture;
44import com.jme3.texture.Texture.WrapMode;
45import com.jme3.texture.Texture2D;
46import com.jme3.util.PlaceholderAssets;
47import com.jme3.util.blockparser.BlockLanguageParser;
48import com.jme3.util.blockparser.Statement;
49import java.io.IOException;
50import java.io.InputStream;
51import java.util.Arrays;
52import java.util.List;
53import java.util.Scanner;
54import java.util.logging.Level;
55import java.util.logging.Logger;
56
57public class MaterialLoader implements AssetLoader {
58
59    private static final Logger logger = Logger.getLogger(MaterialLoader.class.getName());
60
61    private String folderName;
62    private AssetManager assetManager;
63    private ColorRGBA ambient, diffuse, specular, emissive;
64    private Texture[] textures = new Texture[4];
65    private String texName;
66    private String matName;
67    private float shinines;
68    private boolean vcolor = false;
69    private boolean blend = false;
70    private boolean twoSide = false;
71    private boolean noLight = false;
72    private boolean separateTexCoord = false;
73    private int texUnit = 0;
74
75    private ColorRGBA readColor(String content){
76        String[] split = content.split("\\s");
77
78        ColorRGBA color = new ColorRGBA();
79        color.r = Float.parseFloat(split[0]);
80        color.g = Float.parseFloat(split[1]);
81        color.b = Float.parseFloat(split[2]);
82        if (split.length >= 4){
83            color.a = Float.parseFloat(split[3]);
84        }
85        return color;
86    }
87
88    private void readTextureImage(String content){
89        // texture image def
90        String path = null;
91
92        // find extension
93        int extStart = content.lastIndexOf(".");
94        for (int i = extStart; i < content.length(); i++){
95            char c = content.charAt(i);
96            if (Character.isWhitespace(c)){
97                // extension ends here
98                path = content.substring(0, i).trim();
99                content   = content.substring(i+1).trim();
100                break;
101            }
102        }
103        if (path == null){
104            path = content.trim();
105            content = "";
106        }
107
108        Scanner lnScan = new Scanner(content);
109        String mips = null;
110        String type = null;
111        if (lnScan.hasNext()){
112            // more params
113            type = lnScan.next();
114//            if (!lnScan.hasNext("\n") && lnScan.hasNext()){
115//                mips = lnScan.next();
116//                if (lnScan.hasNext()){
117                    // even more params..
118                    // will have to ignore
119//                }
120//            }
121        }
122
123        boolean genMips = true;
124        boolean cubic = false;
125        if (type != null && type.equals("0"))
126            genMips = false;
127
128        if (type != null && type.equals("cubic")){
129            cubic = true;
130        }
131
132        TextureKey texKey = new TextureKey(folderName + path, false);
133        texKey.setGenerateMips(genMips);
134        texKey.setAsCube(cubic);
135
136        try {
137            Texture loadedTexture = assetManager.loadTexture(texKey);
138
139            textures[texUnit].setImage(loadedTexture.getImage());
140            textures[texUnit].setMinFilter(loadedTexture.getMinFilter());
141            textures[texUnit].setKey(loadedTexture.getKey());
142
143            // XXX: Is this really neccessary?
144            textures[texUnit].setWrap(WrapMode.Repeat);
145            if (texName != null){
146                textures[texUnit].setName(texName);
147                texName = null;
148            }else{
149                textures[texUnit].setName(texKey.getName());
150            }
151        } catch (AssetNotFoundException ex){
152            logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, matName});
153            textures[texUnit].setImage(PlaceholderAssets.getPlaceholderImage());
154        }
155    }
156
157    private void readTextureUnitStatement(Statement statement){
158        String[] split = statement.getLine().split(" ", 2);
159        String keyword = split[0];
160        if (keyword.equals("texture")){
161            readTextureImage(split[1]);
162        }else if (keyword.equals("tex_address_mode")){
163            String mode = split[1];
164            if (mode.equals("wrap")){
165                textures[texUnit].setWrap(WrapMode.Repeat);
166            }else if (mode.equals("clamp")){
167                textures[texUnit].setWrap(WrapMode.Clamp);
168            }else if (mode.equals("mirror")){
169                textures[texUnit].setWrap(WrapMode.MirroredRepeat);
170            }else if (mode.equals("border")){
171                textures[texUnit].setWrap(WrapMode.BorderClamp);
172            }
173        }else if (keyword.equals("filtering")){
174            // ignored.. only anisotropy is considered
175        }else if (keyword.equals("tex_coord_set")){
176            int texCoord = Integer.parseInt(split[1]);
177            if (texCoord == 1){
178                separateTexCoord = true;
179            }
180        }else if (keyword.equals("max_anisotropy")){
181            int amount = Integer.parseInt(split[1]);
182            textures[texUnit].setAnisotropicFilter(amount);
183        }else{
184            logger.log(Level.WARNING, "Unsupported texture_unit directive: {0}", keyword);
185        }
186    }
187
188    private void readTextureUnit(Statement statement){
189        String[] split = statement.getLine().split(" ", 2);
190        // name is optional
191        if (split.length == 2){
192            texName = split[1];
193        }else{
194            texName = null;
195        }
196
197        textures[texUnit] = new Texture2D();
198        for (Statement texUnitStat : statement.getContents()){
199            readTextureUnitStatement(texUnitStat);
200        }
201        if (textures[texUnit].getImage() != null){
202            texUnit++;
203        }else{
204            // no image was loaded, ignore
205            textures[texUnit] = null;
206        }
207    }
208
209    private void readPassStatement(Statement statement){
210        // read until newline
211        String[] split = statement.getLine().split(" ", 2);
212        String keyword = split[0];
213        if (keyword.equals("diffuse")){
214            if (split[1].equals("vertexcolour")){
215                // use vertex colors
216                diffuse = ColorRGBA.White;
217                vcolor = true;
218            }else{
219                diffuse = readColor(split[1]);
220            }
221        }else if(keyword.equals("ambient")) {
222           if (split[1].equals("vertexcolour")){
223                // use vertex colors
224               ambient = ColorRGBA.White;
225            }else{
226               ambient = readColor(split[1]);
227            }
228        }else if (keyword.equals("emissive")){
229            emissive = readColor(split[1]);
230        }else if (keyword.equals("specular")){
231            String[] subsplit = split[1].split("\\s");
232            specular = new ColorRGBA();
233            specular.r = Float.parseFloat(subsplit[0]);
234            specular.g = Float.parseFloat(subsplit[1]);
235            specular.b = Float.parseFloat(subsplit[2]);
236            float unknown = Float.parseFloat(subsplit[3]);
237            if (subsplit.length >= 5){
238                // using 5 float values
239                specular.a = unknown;
240                shinines = Float.parseFloat(subsplit[4]);
241            }else{
242                // using 4 float values
243                specular.a = 1f;
244                shinines = unknown;
245            }
246        }else if (keyword.equals("texture_unit")){
247            readTextureUnit(statement);
248        }else if (keyword.equals("scene_blend")){
249            String mode = split[1];
250            if (mode.equals("alpha_blend")){
251                blend = true;
252            }
253        }else if (keyword.equals("cull_hardware")){
254            String mode = split[1];
255            if (mode.equals("none")){
256                twoSide = true;
257            }
258        }else if (keyword.equals("cull_software")){
259            // ignore
260        }else if (keyword.equals("lighting")){
261            String isOn = split[1];
262            if (isOn.equals("on")){
263                noLight = false;
264            }else if (isOn.equals("off")){
265                noLight = true;
266            }
267        }else{
268            logger.log(Level.WARNING, "Unsupported pass directive: {0}", keyword);
269        }
270    }
271
272    private void readPass(Statement statement){
273        String name;
274        String[] split = statement.getLine().split(" ", 2);
275        if (split.length == 1){
276            // no name
277            name = null;
278        }else{
279            name = split[1];
280        }
281
282        for (Statement passStat : statement.getContents()){
283            readPassStatement(passStat);
284        }
285
286        texUnit = 0;
287    }
288
289    private void readTechnique(Statement statement){
290        String[] split = statement.getLine().split(" ", 2);
291        String name;
292        if (split.length == 1){
293            // no name
294            name = null;
295        }else{
296            name = split[1];
297        }
298        for (Statement techStat : statement.getContents()){
299            readPass(techStat);
300        }
301    }
302
303    private void readMaterialStatement(Statement statement){
304        if (statement.getLine().startsWith("technique")){
305            readTechnique(statement);
306        }else if (statement.getLine().startsWith("receive_shadows")){
307            String isOn = statement.getLine().split("\\s")[1];
308            if (isOn != null && isOn.equals("true")){
309            }
310        }
311    }
312
313    @SuppressWarnings("empty-statement")
314    private void readMaterial(Statement statement){
315        for (Statement materialStat : statement.getContents()){
316            readMaterialStatement(materialStat);
317        }
318    }
319
320    private Material compileMaterial(){
321        Material mat;
322        if (noLight){
323           mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
324        }else{
325           mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
326        }
327        if (blend){
328            RenderState rs = mat.getAdditionalRenderState();
329            rs.setAlphaTest(true);
330            rs.setAlphaFallOff(0.01f);
331            rs.setBlendMode(RenderState.BlendMode.Alpha);
332
333            if (twoSide){
334                rs.setFaceCullMode(RenderState.FaceCullMode.Off);
335            }
336
337//            rs.setDepthWrite(false);
338            mat.setTransparent(true);
339            if (!noLight){
340                mat.setBoolean("UseAlpha", true);
341            }
342        }else{
343            if (twoSide){
344                RenderState rs = mat.getAdditionalRenderState();
345                rs.setFaceCullMode(RenderState.FaceCullMode.Off);
346            }
347        }
348
349        if (!noLight){
350            if (shinines > 0f) {
351                mat.setFloat("Shininess", shinines);
352            } else {
353                mat.setFloat("Shininess", 16f); // set shininess to some value anyway..
354            }
355
356            if (vcolor)
357                mat.setBoolean("UseVertexColor", true);
358
359            if (textures[0] != null)
360                mat.setTexture("DiffuseMap", textures[0]);
361
362            mat.setBoolean("UseMaterialColors", true);
363            if(diffuse != null){
364                mat.setColor("Diffuse",  diffuse);
365            }else{
366                mat.setColor("Diffuse", ColorRGBA.White);
367            }
368
369            if(ambient != null){
370                mat.setColor("Ambient",  ambient);
371            }else{
372                mat.setColor("Ambient", ColorRGBA.DarkGray);
373            }
374
375            if(specular != null){
376                mat.setColor("Specular", specular);
377            }else{
378                mat.setColor("Specular", ColorRGBA.Black);
379            }
380
381            if (emissive != null){
382                mat.setColor("GlowColor", emissive);
383            }
384        }else{
385            if (vcolor) {
386                mat.setBoolean("VertexColor", true);
387            }
388
389            if (textures[0] != null && textures[1] == null){
390                if (separateTexCoord){
391                    mat.setTexture("LightMap", textures[0]);
392                    mat.setBoolean("SeparateTexCoord", true);
393                }else{
394                    mat.setTexture("ColorMap", textures[0]);
395                }
396            }else if (textures[1] != null){
397                mat.setTexture("ColorMap", textures[0]);
398                mat.setTexture("LightMap", textures[1]);
399                if (separateTexCoord){
400                    mat.setBoolean("SeparateTexCoord", true);
401                }
402            }
403
404            if(diffuse != null){
405                mat.setColor("Color", diffuse);
406            }
407
408            if (emissive != null){
409                mat.setColor("GlowColor", emissive);
410            }
411        }
412
413        noLight = false;
414        Arrays.fill(textures, null);
415        diffuse = null;
416        specular = null;
417        shinines = 0f;
418        vcolor = false;
419        blend = false;
420        texUnit = 0;
421        separateTexCoord = false;
422        return mat;
423    }
424
425    private MaterialList load(AssetManager assetManager, AssetKey key, InputStream in) throws IOException{
426        folderName = key.getFolder();
427        this.assetManager = assetManager;
428
429        MaterialList list = null;
430        List<Statement> statements = BlockLanguageParser.parse(in);
431
432        for (Statement statement : statements){
433            if (statement.getLine().startsWith("import")){
434                MaterialExtensionSet matExts = null;
435                if (key instanceof OgreMaterialKey){
436                     matExts = ((OgreMaterialKey)key).getMaterialExtensionSet();
437                }
438
439                if (matExts == null){
440                    throw new IOException("Must specify MaterialExtensionSet when loading\n"+
441                                          "Ogre3D materials with extended materials");
442                }
443
444                list = new MaterialExtensionLoader().load(assetManager, key, matExts, statements);
445                break;
446            }else if (statement.getLine().startsWith("material")){
447                if (list == null){
448                    list = new MaterialList();
449                }
450                String[] split = statement.getLine().split(" ", 2);
451                matName = split[1].trim();
452                readMaterial(statement);
453                Material mat = compileMaterial();
454                list.put(matName, mat);
455            }
456        }
457
458        return list;
459    }
460
461    public Object load(AssetInfo info) throws IOException {
462        InputStream in = null;
463        try {
464            in = info.openStream();
465            return load(info.getManager(), info.getKey(), in);
466        } finally {
467            if (in != null){
468                in.close();
469            }
470        }
471    }
472
473}
474