1/*
2 * Copyright (c) 2009-2012 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 */
32package com.jme3.asset;
33
34import com.jme3.bounding.BoundingVolume;
35import com.jme3.collision.Collidable;
36import com.jme3.collision.CollisionResults;
37import com.jme3.collision.UnsupportedCollisionException;
38import com.jme3.export.InputCapsule;
39import com.jme3.export.JmeExporter;
40import com.jme3.export.JmeImporter;
41import com.jme3.export.OutputCapsule;
42import com.jme3.light.AmbientLight;
43import com.jme3.light.Light;
44import com.jme3.material.Material;
45import com.jme3.material.RenderState.FaceCullMode;
46import com.jme3.renderer.Camera;
47import com.jme3.scene.Node;
48import com.jme3.scene.SceneGraphVisitor;
49import com.jme3.scene.Spatial;
50import com.jme3.scene.plugins.ogre.AnimData;
51import com.jme3.texture.Texture;
52import java.io.IOException;
53import java.util.ArrayList;
54import java.util.List;
55import java.util.Queue;
56
57/**
58 * Blender key. Contains path of the blender file and its loading properties.
59 * @author Marcin Roguski (Kaelthas)
60 */
61public class BlenderKey extends ModelKey {
62
63	protected static final int					DEFAULT_FPS				= 25;
64	/**
65	 * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
66	 * between the frames.
67	 */
68	protected int								fps						= DEFAULT_FPS;
69	/** Width of generated textures (in pixels). */
70	protected int								generatedTextureWidth	= 60;
71	/** Height of generated textures (in pixels). */
72	protected int								generatedTextureHeight	= 60;
73	/** Depth of generated textures (in pixels). */
74	protected int								generatedTextureDepth	= 60;
75	/**
76	 * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
77	 */
78	protected int								featuresToLoad			= FeaturesToLoad.ALL;
79	/** This variable determines if assets that are not linked to the objects should be loaded. */
80	protected boolean							loadUnlinkedAssets;
81	/** The root path for all the assets. */
82	protected String							assetRootPath;
83	/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
84	protected boolean							fixUpAxis				= true;
85	/**
86	 * The name of world settings that the importer will use. If not set or specified name does not occur in the file
87	 * then the first world settings in the file will be used.
88	 */
89	protected String							usedWorld;
90	/**
91	 * User's default material that is set fo objects that have no material definition in blender. The default value is
92	 * null. If the value is null the importer will use its own default material (gray color - like in blender).
93	 */
94	protected Material							defaultMaterial;
95	/** Face cull mode. By default it is disabled. */
96	protected FaceCullMode						faceCullMode			= FaceCullMode.Off;
97	/**
98	 * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
99	 * If set to -1 then the current layer will be loaded.
100	 */
101	protected int								layersToLoad			= -1;
102
103	/**
104	 * Constructor used by serialization mechanisms.
105	 */
106	public BlenderKey() {}
107
108	/**
109	 * Constructor. Creates a key for the given file name.
110	 * @param name
111	 *        the name (path) of a file
112	 */
113	public BlenderKey(String name) {
114		super(name);
115	}
116
117	/**
118	 * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25.
119	 * @return the frames per second amount
120	 */
121	public int getFps() {
122		return fps;
123	}
124
125	/**
126	 * This method sets frames per second amount.
127	 * @param fps
128	 *        the frames per second amount
129	 */
130	public void setFps(int fps) {
131		this.fps = fps;
132	}
133
134	/**
135	 * This method sets the width of generated texture (in pixels). By default the value is 140 px.
136	 * @param generatedTextureWidth
137	 *        the width of generated texture
138	 */
139	public void setGeneratedTextureWidth(int generatedTextureWidth) {
140		this.generatedTextureWidth = generatedTextureWidth;
141	}
142
143	/**
144	 * This method returns the width of generated texture (in pixels). By default the value is 140 px.
145	 * @return the width of generated texture
146	 */
147	public int getGeneratedTextureWidth() {
148		return generatedTextureWidth;
149	}
150
151	/**
152	 * This method sets the height of generated texture (in pixels). By default the value is 20 px.
153	 * @param generatedTextureHeight
154	 *        the height of generated texture
155	 */
156	public void setGeneratedTextureHeight(int generatedTextureHeight) {
157		this.generatedTextureHeight = generatedTextureHeight;
158	}
159
160	/**
161	 * This method returns the height of generated texture (in pixels). By default the value is 20 px.
162	 * @return the height of generated texture
163	 */
164	public int getGeneratedTextureHeight() {
165		return generatedTextureHeight;
166	}
167
168	/**
169	 * This method sets the depth of generated texture (in pixels). By default the value is 20 px.
170	 * @param generatedTextureDepth
171	 *        the depth of generated texture
172	 */
173	public void setGeneratedTextureDepth(int generatedTextureDepth) {
174		this.generatedTextureDepth = generatedTextureDepth;
175	}
176
177	/**
178	 * This method returns the depth of generated texture (in pixels). By default the value is 20 px.
179	 * @return the depth of generated texture
180	 */
181	public int getGeneratedTextureDepth() {
182		return generatedTextureDepth;
183	}
184
185	/**
186	 * This method returns the face cull mode.
187	 * @return the face cull mode
188	 */
189	public FaceCullMode getFaceCullMode() {
190		return faceCullMode;
191	}
192
193	/**
194	 * This method sets the face cull mode.
195	 * @param faceCullMode
196	 *        the face cull mode
197	 */
198	public void setFaceCullMode(FaceCullMode faceCullMode) {
199		this.faceCullMode = faceCullMode;
200	}
201
202	/**
203	 * This method sets layers to be loaded.
204	 * @param layersToLoad
205	 *        layers to be loaded
206	 */
207	public void setLayersToLoad(int layersToLoad) {
208		this.layersToLoad = layersToLoad;
209	}
210
211	/**
212	 * This method returns layers to be loaded.
213	 * @return layers to be loaded
214	 */
215	public int getLayersToLoad() {
216		return layersToLoad;
217	}
218
219	/**
220	 * This method sets the asset root path.
221	 * @param assetRootPath
222	 *        the assets root path
223	 */
224	public void setAssetRootPath(String assetRootPath) {
225		this.assetRootPath = assetRootPath;
226	}
227
228	/**
229	 * This method returns the asset root path.
230	 * @return the asset root path
231	 */
232	public String getAssetRootPath() {
233		return assetRootPath;
234	}
235
236	/**
237	 * This method adds features to be loaded.
238	 * @param featuresToLoad
239	 *        bitwise flag of FeaturesToLoad interface values
240	 */
241	public void includeInLoading(int featuresToLoad) {
242		this.featuresToLoad |= featuresToLoad;
243	}
244
245	/**
246	 * This method removes features from being loaded.
247	 * @param featuresNotToLoad
248	 *        bitwise flag of FeaturesToLoad interface values
249	 */
250	public void excludeFromLoading(int featuresNotToLoad) {
251		this.featuresToLoad &= ~featuresNotToLoad;
252	}
253
254	/**
255	 * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by
256	 * the blender file loader.
257	 * @return features that will be loaded by the blender file loader
258	 */
259	public int getFeaturesToLoad() {
260		return featuresToLoad;
261	}
262
263	/**
264	 * This method determines if unlinked assets should be loaded.
265	 * If not then only objects on selected layers will be loaded and their assets if required.
266	 * If yes then all assets will be loaded even if they are on inactive layers or are not linked
267	 * to anything.
268	 * @return <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
269	 */
270	public boolean isLoadUnlinkedAssets() {
271		return loadUnlinkedAssets;
272	}
273
274	/**
275	 * This method sets if unlinked assets should be loaded.
276	 * If not then only objects on selected layers will be loaded and their assets if required.
277	 * If yes then all assets will be loaded even if they are on inactive layers or are not linked
278	 * to anything.
279	 * @param loadUnlinkedAssets
280	 *        <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
281	 */
282	public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) {
283		this.loadUnlinkedAssets = loadUnlinkedAssets;
284	}
285
286	/**
287	 * This method creates an object where loading results will be stores. Only those features will be allowed to store
288	 * that were specified by features-to-load flag.
289	 * @return an object to store loading results
290	 */
291	public LoadingResults prepareLoadingResults() {
292		return new LoadingResults(featuresToLoad);
293	}
294
295	/**
296	 * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
297	 * is up axis.
298	 * @param fixUpAxis
299	 *        the up axis state variable
300	 */
301	public void setFixUpAxis(boolean fixUpAxis) {
302		this.fixUpAxis = fixUpAxis;
303	}
304
305	/**
306	 * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By
307	 * default Y is up axis.
308	 * @return the up axis state variable
309	 */
310	public boolean isFixUpAxis() {
311		return fixUpAxis;
312	}
313
314	/**
315	 * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
316	 * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
317	 * during loading (assumin any exists in the file).
318	 * @param usedWorld
319	 *        the name of the WORLD block used during loading
320	 */
321	public void setUsedWorld(String usedWorld) {
322		this.usedWorld = usedWorld;
323	}
324
325	/**
326	 * This mehtod returns the name of the WORLD data block taht should be used during file loading.
327	 * @return the name of the WORLD block used during loading
328	 */
329	public String getUsedWorld() {
330		return usedWorld;
331	}
332
333	/**
334	 * This method sets the default material for objects.
335	 * @param defaultMaterial
336	 *        the default material
337	 */
338	public void setDefaultMaterial(Material defaultMaterial) {
339		this.defaultMaterial = defaultMaterial;
340	}
341
342	/**
343	 * This method returns the default material.
344	 * @return the default material
345	 */
346	public Material getDefaultMaterial() {
347		return defaultMaterial;
348	}
349
350	@Override
351	public void write(JmeExporter e) throws IOException {
352		super.write(e);
353		OutputCapsule oc = e.getCapsule(this);
354		oc.write(fps, "fps", DEFAULT_FPS);
355		oc.write(generatedTextureWidth, "generated-texture-width", 20);
356		oc.write(generatedTextureHeight, "generated-texture-height", 20);
357		oc.write(generatedTextureDepth, "generated-texture-depth", 20);
358		oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);
359		oc.write(loadUnlinkedAssets, "load-unlinked-assets", false);
360		oc.write(assetRootPath, "asset-root-path", null);
361		oc.write(fixUpAxis, "fix-up-axis", true);
362		oc.write(usedWorld, "used-world", null);
363		oc.write(defaultMaterial, "default-material", null);
364		oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
365		oc.write(layersToLoad, "layers-to-load", -1);
366	}
367
368	@Override
369	public void read(JmeImporter e) throws IOException {
370		super.read(e);
371		InputCapsule ic = e.getCapsule(this);
372		fps = ic.readInt("fps", DEFAULT_FPS);
373		generatedTextureWidth = ic.readInt("generated-texture-width", 20);
374		generatedTextureHeight = ic.readInt("generated-texture-height", 20);
375		generatedTextureDepth = ic.readInt("generated-texture-depth", 20);
376		featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);
377		loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false);
378		assetRootPath = ic.readString("asset-root-path", null);
379		fixUpAxis = ic.readBoolean("fix-up-axis", true);
380		usedWorld = ic.readString("used-world", null);
381		defaultMaterial = (Material) ic.readSavable("default-material", null);
382		faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
383		layersToLoad = ic.readInt("layers-to=load", -1);
384	}
385
386	@Override
387	public int hashCode() {
388		final int prime = 31;
389		int result = super.hashCode();
390		result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
391		result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
392		result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
393		result = prime * result + featuresToLoad;
394		result = prime * result + (fixUpAxis ? 1231 : 1237);
395		result = prime * result + fps;
396		result = prime * result + generatedTextureDepth;
397		result = prime * result + generatedTextureHeight;
398		result = prime * result + generatedTextureWidth;
399		result = prime * result + layersToLoad;
400		result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
401		result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
402		return result;
403	}
404
405	@Override
406	public boolean equals(Object obj) {
407		if (this == obj) {
408			return true;
409		}
410		if (!super.equals(obj)) {
411			return false;
412		}
413		if (this.getClass() != obj.getClass()) {
414			return false;
415		}
416		BlenderKey other = (BlenderKey) obj;
417		if (assetRootPath == null) {
418			if (other.assetRootPath != null) {
419				return false;
420			}
421		} else if (!assetRootPath.equals(other.assetRootPath)) {
422			return false;
423		}
424		if (defaultMaterial == null) {
425			if (other.defaultMaterial != null) {
426				return false;
427			}
428		} else if (!defaultMaterial.equals(other.defaultMaterial)) {
429			return false;
430		}
431		if (faceCullMode != other.faceCullMode) {
432			return false;
433		}
434		if (featuresToLoad != other.featuresToLoad) {
435			return false;
436		}
437		if (fixUpAxis != other.fixUpAxis) {
438			return false;
439		}
440		if (fps != other.fps) {
441			return false;
442		}
443		if (generatedTextureDepth != other.generatedTextureDepth) {
444			return false;
445		}
446		if (generatedTextureHeight != other.generatedTextureHeight) {
447			return false;
448		}
449		if (generatedTextureWidth != other.generatedTextureWidth) {
450			return false;
451		}
452		if (layersToLoad != other.layersToLoad) {
453			return false;
454		}
455		if (loadUnlinkedAssets != other.loadUnlinkedAssets) {
456			return false;
457		}
458		if (usedWorld == null) {
459			if (other.usedWorld != null) {
460				return false;
461			}
462		} else if (!usedWorld.equals(other.usedWorld)) {
463			return false;
464		}
465		return true;
466	}
467
468	/**
469	 * This interface describes the features of the scene that are to be loaded.
470	 * @author Marcin Roguski (Kaelthas)
471	 */
472	public static interface FeaturesToLoad {
473
474		int	SCENES		= 0x0000FFFF;
475		int	OBJECTS		= 0x0000000B;
476		int	ANIMATIONS	= 0x00000004;
477		int	MATERIALS	= 0x00000003;
478		int	TEXTURES	= 0x00000001;
479		int	CAMERAS		= 0x00000020;
480		int	LIGHTS		= 0x00000010;
481		int	ALL			= 0xFFFFFFFF;
482	}
483
484	/**
485	 * This class holds the loading results according to the given loading flag.
486	 * @author Marcin Roguski (Kaelthas)
487	 */
488	public static class LoadingResults extends Spatial {
489
490		/** Bitwise mask of features that are to be loaded. */
491		private final int		featuresToLoad;
492		/** The scenes from the file. */
493		private List<Node>		scenes;
494		/** Objects from all scenes. */
495		private List<Node>		objects;
496		/** Materials from all objects. */
497		private List<Material>	materials;
498		/** Textures from all objects. */
499		private List<Texture>	textures;
500		/** Animations of all objects. */
501		private List<AnimData>	animations;
502		/** All cameras from the file. */
503		private List<Camera>	cameras;
504		/** All lights from the file. */
505		private List<Light>		lights;
506
507		/**
508		 * Private constructor prevents users to create an instance of this class from outside the
509		 * @param featuresToLoad
510		 *        bitwise mask of features that are to be loaded
511		 * @see FeaturesToLoad FeaturesToLoad
512		 */
513		private LoadingResults(int featuresToLoad) {
514			this.featuresToLoad = featuresToLoad;
515			if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) {
516				scenes = new ArrayList<Node>();
517			}
518			if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
519				objects = new ArrayList<Node>();
520				if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
521					materials = new ArrayList<Material>();
522					if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
523						textures = new ArrayList<Texture>();
524					}
525				}
526				if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
527					animations = new ArrayList<AnimData>();
528				}
529			}
530			if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
531				cameras = new ArrayList<Camera>();
532			}
533			if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
534				lights = new ArrayList<Light>();
535			}
536		}
537
538		/**
539		 * This method returns a bitwise flag describing what features of the blend file will be included in the result.
540		 * @return bitwise mask of features that are to be loaded
541		 * @see FeaturesToLoad FeaturesToLoad
542		 */
543		public int getLoadedFeatures() {
544			return featuresToLoad;
545		}
546
547		/**
548		 * This method adds a scene to the result set.
549		 * @param scene
550		 *        scene to be added to the result set
551		 */
552		public void addScene(Node scene) {
553			if (scenes != null) {
554				scenes.add(scene);
555			}
556		}
557
558		/**
559		 * This method adds an object to the result set.
560		 * @param object
561		 *        object to be added to the result set
562		 */
563		public void addObject(Node object) {
564			if (objects != null) {
565				objects.add(object);
566			}
567		}
568
569		/**
570		 * This method adds a material to the result set.
571		 * @param material
572		 *        material to be added to the result set
573		 */
574		public void addMaterial(Material material) {
575			if (materials != null) {
576				materials.add(material);
577			}
578		}
579
580		/**
581		 * This method adds a texture to the result set.
582		 * @param texture
583		 *        texture to be added to the result set
584		 */
585		public void addTexture(Texture texture) {
586			if (textures != null) {
587				textures.add(texture);
588			}
589		}
590
591		/**
592		 * This method adds a camera to the result set.
593		 * @param camera
594		 *        camera to be added to the result set
595		 */
596		public void addCamera(Camera camera) {
597			if (cameras != null) {
598				cameras.add(camera);
599			}
600		}
601
602		/**
603		 * This method adds a light to the result set.
604		 * @param light
605		 *        light to be added to the result set
606		 */
607		@Override
608		public void addLight(Light light) {
609			if (lights != null) {
610				lights.add(light);
611			}
612		}
613
614		/**
615		 * This method returns all loaded scenes.
616		 * @return all loaded scenes
617		 */
618		public List<Node> getScenes() {
619			return scenes;
620		}
621
622		/**
623		 * This method returns all loaded objects.
624		 * @return all loaded objects
625		 */
626		public List<Node> getObjects() {
627			return objects;
628		}
629
630		/**
631		 * This method returns all loaded materials.
632		 * @return all loaded materials
633		 */
634		public List<Material> getMaterials() {
635			return materials;
636		}
637
638		/**
639		 * This method returns all loaded textures.
640		 * @return all loaded textures
641		 */
642		public List<Texture> getTextures() {
643			return textures;
644		}
645
646		/**
647		 * This method returns all loaded animations.
648		 * @return all loaded animations
649		 */
650		public List<AnimData> getAnimations() {
651			return animations;
652		}
653
654		/**
655		 * This method returns all loaded cameras.
656		 * @return all loaded cameras
657		 */
658		public List<Camera> getCameras() {
659			return cameras;
660		}
661
662		/**
663		 * This method returns all loaded lights.
664		 * @return all loaded lights
665		 */
666		public List<Light> getLights() {
667			return lights;
668		}
669
670		@Override
671		public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
672			return 0;
673		}
674
675		@Override
676		public void updateModelBound() {}
677
678		@Override
679		public void setModelBound(BoundingVolume modelBound) {}
680
681		@Override
682		public int getVertexCount() {
683			return 0;
684		}
685
686		@Override
687		public int getTriangleCount() {
688			return 0;
689		}
690
691		@Override
692		public Spatial deepClone() {
693			return null;
694		}
695
696		@Override
697		public void depthFirstTraversal(SceneGraphVisitor visitor) {}
698
699		@Override
700		protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {}
701	}
702
703	/**
704	 * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient
705	 * light.
706	 * @author Marcin Roguski (Kaelthas)
707	 */
708	public static class WorldData {
709
710		/** The ambient light. */
711		private AmbientLight	ambientLight;
712
713		/**
714		 * This method returns the world's ambient light.
715		 * @return the world's ambient light
716		 */
717		public AmbientLight getAmbientLight() {
718			return ambientLight;
719		}
720
721		/**
722		 * This method sets the world's ambient light.
723		 * @param ambientLight
724		 *        the world's ambient light
725		 */
726		public void setAmbientLight(AmbientLight ambientLight) {
727			this.ambientLight = ambientLight;
728		}
729	}
730}
731