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.scene.plugins.blender;
33
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.EmptyStackException;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.Stack;
41import java.util.logging.Level;
42import java.util.logging.Logger;
43
44import com.jme3.animation.Skeleton;
45import com.jme3.asset.AssetManager;
46import com.jme3.asset.BlenderKey;
47import com.jme3.material.Material;
48import com.jme3.math.ColorRGBA;
49import com.jme3.scene.plugins.blender.animations.BoneContext;
50import com.jme3.scene.plugins.blender.animations.Ipo;
51import com.jme3.scene.plugins.blender.constraints.Constraint;
52import com.jme3.scene.plugins.blender.file.BlenderInputStream;
53import com.jme3.scene.plugins.blender.file.DnaBlockData;
54import com.jme3.scene.plugins.blender.file.FileBlockHeader;
55import com.jme3.scene.plugins.blender.file.Structure;
56import com.jme3.scene.plugins.blender.materials.MaterialContext;
57import com.jme3.scene.plugins.blender.meshes.MeshContext;
58import com.jme3.scene.plugins.blender.modifiers.Modifier;
59import com.jme3.scene.plugins.ogre.AnimData;
60
61/**
62 * The class that stores temporary data and manages it during loading the belnd
63 * file. This class is intended to be used in a single loading thread. It holds
64 * the state of loading operations.
65 *
66 * @author Marcin Roguski (Kaelthas)
67 */
68public class BlenderContext {
69	private static final Logger					LOGGER					= Logger.getLogger(BlenderContext.class.getName());
70
71	/** The blender file version. */
72	private int									blenderVersion;
73	/** The blender key. */
74	private BlenderKey							blenderKey;
75	/** The header of the file block. */
76	private DnaBlockData						dnaBlockData;
77	/** The input stream of the blend file. */
78	private BlenderInputStream					inputStream;
79	/** The asset manager. */
80	private AssetManager						assetManager;
81	/**
82	 * A map containing the file block headers. The key is the old pointer
83	 * address.
84	 */
85	private Map<Long, FileBlockHeader>			fileBlockHeadersByOma	= new HashMap<Long, FileBlockHeader>();
86	/** A map containing the file block headers. The key is the block code. */
87	private Map<Integer, List<FileBlockHeader>>	fileBlockHeadersByCode	= new HashMap<Integer, List<FileBlockHeader>>();
88	/**
89	 * This map stores the loaded features by their old memory address. The
90	 * first object in the value table is the loaded structure and the second -
91	 * the structure already converted into proper data.
92	 */
93	private Map<Long, Object[]>					loadedFeatures			= new HashMap<Long, Object[]>();
94	/**
95	 * This map stores the loaded features by their name. Only features with ID
96	 * structure can be stored here. The first object in the value table is the
97	 * loaded structure and the second - the structure already converted into
98	 * proper data.
99	 */
100	private Map<String, Object[]>				loadedFeaturesByName	= new HashMap<String, Object[]>();
101	/** A stack that hold the parent structure of currently loaded feature. */
102	private Stack<Structure>					parentStack				= new Stack<Structure>();
103	/**
104	 * A map storing loaded ipos. The key is the ipo's owner old memory address
105	 * and the value is the ipo.
106	 */
107	private Map<Long, Ipo>						loadedIpos				= new HashMap<Long, Ipo>();
108	/** A list of modifiers for the specified object. */
109	protected Map<Long, List<Modifier>>			modifiers				= new HashMap<Long, List<Modifier>>();
110	/** A list of constraints for the specified object. */
111	protected Map<Long, List<Constraint>>		constraints				= new HashMap<Long, List<Constraint>>();
112	/** Anim data loaded for features. */
113	private Map<Long, AnimData>					animData				= new HashMap<Long, AnimData>();
114	/** Loaded skeletons. */
115	private Map<Long, Skeleton>					skeletons				= new HashMap<Long, Skeleton>();
116	/** A map of mesh contexts. */
117	protected Map<Long, MeshContext>			meshContexts			= new HashMap<Long, MeshContext>();
118	/** A map of bone contexts. */
119	protected Map<Long, BoneContext>			boneContexts			= new HashMap<Long, BoneContext>();
120	/** A map of material contexts. */
121	protected Map<Material, MaterialContext>	materialContexts		= new HashMap<Material, MaterialContext>();
122	/** A map og helpers that perform loading. */
123	private Map<String, AbstractBlenderHelper>	helpers					= new HashMap<String, AbstractBlenderHelper>();
124
125	/**
126	 * This method sets the blender file version.
127	 *
128	 * @param blenderVersion
129	 *            the blender file version
130	 */
131	public void setBlenderVersion(String blenderVersion) {
132		this.blenderVersion = Integer.parseInt(blenderVersion);
133	}
134
135	/**
136	 * @return the blender file version
137	 */
138	public int getBlenderVersion() {
139		return blenderVersion;
140	}
141
142	/**
143	 * This method sets the blender key.
144	 *
145	 * @param blenderKey
146	 *            the blender key
147	 */
148	public void setBlenderKey(BlenderKey blenderKey) {
149		this.blenderKey = blenderKey;
150	}
151
152	/**
153	 * This method returns the blender key.
154	 *
155	 * @return the blender key
156	 */
157	public BlenderKey getBlenderKey() {
158		return blenderKey;
159	}
160
161	/**
162	 * This method sets the dna block data.
163	 *
164	 * @param dnaBlockData
165	 *            the dna block data
166	 */
167	public void setBlockData(DnaBlockData dnaBlockData) {
168		this.dnaBlockData = dnaBlockData;
169	}
170
171	/**
172	 * This method returns the dna block data.
173	 *
174	 * @return the dna block data
175	 */
176	public DnaBlockData getDnaBlockData() {
177		return dnaBlockData;
178	}
179
180	/**
181	 * This method returns the asset manager.
182	 *
183	 * @return the asset manager
184	 */
185	public AssetManager getAssetManager() {
186		return assetManager;
187	}
188
189	/**
190	 * This method sets the asset manager.
191	 *
192	 * @param assetManager
193	 *            the asset manager
194	 */
195	public void setAssetManager(AssetManager assetManager) {
196		this.assetManager = assetManager;
197	}
198
199	/**
200	 * This method returns the input stream of the blend file.
201	 *
202	 * @return the input stream of the blend file
203	 */
204	public BlenderInputStream getInputStream() {
205		return inputStream;
206	}
207
208	/**
209	 * This method sets the input stream of the blend file.
210	 *
211	 * @param inputStream
212	 *            the input stream of the blend file
213	 */
214	public void setInputStream(BlenderInputStream inputStream) {
215		this.inputStream = inputStream;
216	}
217
218	/**
219	 * This method adds a file block header to the map. Its old memory address
220	 * is the key.
221	 *
222	 * @param oldMemoryAddress
223	 *            the address of the block header
224	 * @param fileBlockHeader
225	 *            the block header to store
226	 */
227	public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
228		fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
229		List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));
230		if (headers == null) {
231			headers = new ArrayList<FileBlockHeader>();
232			fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers);
233		}
234		headers.add(fileBlockHeader);
235	}
236
237	/**
238	 * This method returns the block header of a given memory address. If the
239	 * header is not present then null is returned.
240	 *
241	 * @param oldMemoryAddress
242	 *            the address of the block header
243	 * @return loaded header or null if it was not yet loaded
244	 */
245	public FileBlockHeader getFileBlock(Long oldMemoryAddress) {
246		return fileBlockHeadersByOma.get(oldMemoryAddress);
247	}
248
249	/**
250	 * This method returns a list of file blocks' headers of a specified code.
251	 *
252	 * @param code
253	 *            the code of file blocks
254	 * @return a list of file blocks' headers of a specified code
255	 */
256	public List<FileBlockHeader> getFileBlocks(Integer code) {
257		return fileBlockHeadersByCode.get(code);
258	}
259
260	/**
261	 * This method clears the saved block headers stored in the features map.
262	 */
263	public void clearFileBlocks() {
264		fileBlockHeadersByOma.clear();
265		fileBlockHeadersByCode.clear();
266	}
267
268	/**
269	 * This method adds a helper instance to the helpers' map.
270	 *
271	 * @param <T>
272	 *            the type of the helper
273	 * @param clazz
274	 *            helper's class definition
275	 * @param helper
276	 *            the helper instance
277	 */
278	public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) {
279		helpers.put(clazz.getSimpleName(), helper);
280	}
281
282	@SuppressWarnings("unchecked")
283	public <T> T getHelper(Class<?> clazz) {
284		return (T) helpers.get(clazz.getSimpleName());
285	}
286
287	/**
288	 * This method adds a loaded feature to the map. The key is its unique old
289	 * memory address.
290	 *
291	 * @param oldMemoryAddress
292	 *            the address of the feature
293	 * @param featureName
294	 *            the name of the feature
295	 * @param structure
296	 *            the filled structure of the feature
297	 * @param feature
298	 *            the feature we want to store
299	 */
300	public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) {
301		if (oldMemoryAddress == null || structure == null || feature == null) {
302			throw new IllegalArgumentException("One of the given arguments is null!");
303		}
304		Object[] storedData = new Object[] { structure, feature };
305		loadedFeatures.put(oldMemoryAddress, storedData);
306		if (featureName != null) {
307			loadedFeaturesByName.put(featureName, storedData);
308		}
309	}
310
311	/**
312	 * This method returns the feature of a given memory address. If the feature
313	 * is not yet loaded then null is returned.
314	 *
315	 * @param oldMemoryAddress
316	 *            the address of the feature
317	 * @param loadedFeatureDataType
318	 *            the type of data we want to retreive it can be either filled
319	 *            structure or already converted feature
320	 * @return loaded feature or null if it was not yet loaded
321	 */
322	public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) {
323		Object[] result = loadedFeatures.get(oldMemoryAddress);
324		if (result != null) {
325			return result[loadedFeatureDataType.getIndex()];
326		}
327		return null;
328	}
329
330	/**
331	 * This method returns the feature of a given name. If the feature is not
332	 * yet loaded then null is returned.
333	 *
334	 * @param featureName
335	 *            the name of the feature
336	 * @param loadedFeatureDataType
337	 *            the type of data we want to retreive it can be either filled
338	 *            structure or already converted feature
339	 * @return loaded feature or null if it was not yet loaded
340	 */
341	public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) {
342		Object[] result = loadedFeaturesByName.get(featureName);
343		if (result != null) {
344			return result[loadedFeatureDataType.getIndex()];
345		}
346		return null;
347	}
348
349	/**
350	 * This method clears the saved features stored in the features map.
351	 */
352	public void clearLoadedFeatures() {
353		loadedFeatures.clear();
354	}
355
356	/**
357	 * This method adds the structure to the parent stack.
358	 *
359	 * @param parent
360	 *            the structure to be added to the stack
361	 */
362	public void pushParent(Structure parent) {
363		parentStack.push(parent);
364	}
365
366	/**
367	 * This method removes the structure from the top of the parent's stack.
368	 *
369	 * @return the structure that was removed from the stack
370	 */
371	public Structure popParent() {
372		try {
373			return parentStack.pop();
374		} catch (EmptyStackException e) {
375			return null;
376		}
377	}
378
379	/**
380	 * This method retreives the structure at the top of the parent's stack but
381	 * does not remove it.
382	 *
383	 * @return the structure from the top of the stack
384	 */
385	public Structure peekParent() {
386		try {
387			return parentStack.peek();
388		} catch (EmptyStackException e) {
389			return null;
390		}
391	}
392
393	/**
394	 * This method adds new ipo curve for the feature.
395	 *
396	 * @param ownerOMA
397	 *            the OMA of blender feature that owns the ipo
398	 * @param ipo
399	 *            the ipo to be added
400	 */
401	public void addIpo(Long ownerOMA, Ipo ipo) {
402		loadedIpos.put(ownerOMA, ipo);
403	}
404
405	/**
406	 * This method removes the ipo curve from the feature.
407	 *
408	 * @param ownerOma
409	 *            the OMA of blender feature that owns the ipo
410	 */
411	public Ipo removeIpo(Long ownerOma) {
412		return loadedIpos.remove(ownerOma);
413	}
414
415	/**
416	 * This method returns the ipo curve of the feature.
417	 *
418	 * @param ownerOMA
419	 *            the OMA of blender feature that owns the ipo
420	 */
421	public Ipo getIpo(Long ownerOMA) {
422		return loadedIpos.get(ownerOMA);
423	}
424
425	/**
426	 * This method adds a new modifier to the list.
427	 *
428	 * @param ownerOMA
429	 *            the owner's old memory address
430	 * @param modifier
431	 *            the object's modifier
432	 */
433	public void addModifier(Long ownerOMA, Modifier modifier) {
434		List<Modifier> objectModifiers = this.modifiers.get(ownerOMA);
435		if (objectModifiers == null) {
436			objectModifiers = new ArrayList<Modifier>();
437			this.modifiers.put(ownerOMA, objectModifiers);
438		}
439		objectModifiers.add(modifier);
440	}
441
442	/**
443	 * This method returns modifiers for the object specified by its old memory
444	 * address and the modifier type. If no modifiers are found - empty list is
445	 * returned. If the type is null - all modifiers for the object are
446	 * returned.
447	 *
448	 * @param objectOMA
449	 *            object's old memory address
450	 * @param type
451	 *            the type of the modifier
452	 * @return the list of object's modifiers
453	 */
454	public List<Modifier> getModifiers(Long objectOMA, String type) {
455		List<Modifier> result = new ArrayList<Modifier>();
456		List<Modifier> readModifiers = modifiers.get(objectOMA);
457		if (readModifiers != null && readModifiers.size() > 0) {
458			for (Modifier modifier : readModifiers) {
459				if (type == null || type.isEmpty() || modifier.getType().equals(type)) {
460					result.add(modifier);
461				}
462			}
463		}
464		return result;
465	}
466
467	/**
468	 * This method adds a new modifier to the list.
469	 *
470	 * @param ownerOMA
471	 *            the owner's old memory address
472	 * @param constraints
473	 *            the object's constraints
474	 */
475	public void addConstraints(Long ownerOMA, List<Constraint> constraints) {
476		List<Constraint> objectConstraints = this.constraints.get(ownerOMA);
477		if (objectConstraints == null) {
478			objectConstraints = new ArrayList<Constraint>();
479			this.constraints.put(ownerOMA, objectConstraints);
480		}
481		objectConstraints.addAll(constraints);
482	}
483
484	/**
485	 * This method returns constraints for the object specified by its old
486	 * memory address. If no modifiers are found - <b>null</b> is returned.
487	 *
488	 * @param objectOMA
489	 *            object's old memory address
490	 * @return the list of object's modifiers or null
491	 */
492	public List<Constraint> getConstraints(Long objectOMA) {
493		return objectOMA == null ? null : constraints.get(objectOMA);
494	}
495
496	/**
497	 * This method sets the anim data for the specified OMA of its owner.
498	 *
499	 * @param ownerOMA
500	 *            the owner's old memory address
501	 * @param animData
502	 *            the animation data for the feature specified by ownerOMA
503	 */
504	public void setAnimData(Long ownerOMA, AnimData animData) {
505		this.animData.put(ownerOMA, animData);
506	}
507
508	/**
509	 * This method returns the animation data for the specified owner.
510	 *
511	 * @param ownerOMA
512	 *            the old memory address of the animation data owner
513	 * @return the animation data or null if none exists
514	 */
515	public AnimData getAnimData(Long ownerOMA) {
516		return this.animData.get(ownerOMA);
517	}
518
519	/**
520	 * This method sets the skeleton for the specified OMA of its owner.
521	 *
522	 * @param skeletonOMA
523	 *            the skeleton's old memory address
524	 * @param skeleton
525	 *            the skeleton specified by the given OMA
526	 */
527	public void setSkeleton(Long skeletonOMA, Skeleton skeleton) {
528		this.skeletons.put(skeletonOMA, skeleton);
529	}
530
531	/**
532	 * This method returns the skeleton for the specified OMA of its owner.
533	 *
534	 * @param skeletonOMA
535	 *            the skeleton's old memory address
536	 * @return the skeleton specified by the given OMA
537	 */
538	public Skeleton getSkeleton(Long skeletonOMA) {
539		return this.skeletons.get(skeletonOMA);
540	}
541
542	/**
543	 * This method sets the mesh context for the given mesh old memory address.
544	 * If the context is already set it will be replaced.
545	 *
546	 * @param meshOMA
547	 *            the mesh's old memory address
548	 * @param meshContext
549	 *            the mesh's context
550	 */
551	public void setMeshContext(Long meshOMA, MeshContext meshContext) {
552		this.meshContexts.put(meshOMA, meshContext);
553	}
554
555	/**
556	 * This method returns the mesh context for the given mesh old memory
557	 * address. If no context exists then <b>null</b> is returned.
558	 *
559	 * @param meshOMA
560	 *            the mesh's old memory address
561	 * @return mesh's context
562	 */
563	public MeshContext getMeshContext(Long meshOMA) {
564		return this.meshContexts.get(meshOMA);
565	}
566
567	/**
568	 * This method sets the bone context for the given bone old memory address.
569	 * If the context is already set it will be replaced.
570	 *
571	 * @param boneOMA
572	 *            the bone's old memory address
573	 * @param boneContext
574	 *            the bones's context
575	 */
576	public void setBoneContext(Long boneOMA, BoneContext boneContext) {
577		this.boneContexts.put(boneOMA, boneContext);
578	}
579
580	/**
581	 * This method returns the bone context for the given bone old memory
582	 * address. If no context exists then <b>null</b> is returned.
583	 *
584	 * @param boneOMA
585	 *            the bone's old memory address
586	 * @return bone's context
587	 */
588	public BoneContext getBoneContext(Long boneOMA) {
589		return boneContexts.get(boneOMA);
590	}
591
592	/**
593	 * This method sets the material context for the given material. If the
594	 * context is already set it will be replaced.
595	 *
596	 * @param material
597	 *            the material
598	 * @param materialContext
599	 *            the material's context
600	 */
601	public void setMaterialContext(Material material, MaterialContext materialContext) {
602		this.materialContexts.put(material, materialContext);
603	}
604
605	/**
606	 * This method returns the material context for the given material. If no
607	 * context exists then <b>null</b> is returned.
608	 *
609	 * @param material
610	 *            the material
611	 * @return material's context
612	 */
613	public MaterialContext getMaterialContext(Material material) {
614		return materialContexts.get(material);
615	}
616
617	/**
618	 * This metod returns the default material.
619	 *
620	 * @return the default material
621	 */
622	public synchronized Material getDefaultMaterial() {
623		if (blenderKey.getDefaultMaterial() == null) {
624			Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
625			defaultMaterial.setColor("Color", ColorRGBA.DarkGray);
626			blenderKey.setDefaultMaterial(defaultMaterial);
627		}
628		return blenderKey.getDefaultMaterial();
629	}
630
631	public void dispose() {
632		try {
633			inputStream.close();
634		} catch (IOException e) {
635			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
636		}
637		loadedFeatures.clear();
638		loadedFeaturesByName.clear();
639	}
640
641	/**
642	 * This enum defines what loaded data type user wants to retreive. It can be
643	 * either filled structure or already converted data.
644	 *
645	 * @author Marcin Roguski
646	 */
647	public static enum LoadedFeatureDataType {
648
649		LOADED_STRUCTURE(0), LOADED_FEATURE(1);
650		private int	index;
651
652		private LoadedFeatureDataType(int index) {
653			this.index = index;
654		}
655
656		public int getIndex() {
657			return index;
658		}
659	}
660}
661