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 */
32package com.jme3.scene.plugins.blender;
33
34import com.jme3.asset.AssetInfo;
35import com.jme3.asset.BlenderKey;
36import com.jme3.asset.BlenderKey.FeaturesToLoad;
37import com.jme3.asset.BlenderKey.LoadingResults;
38import com.jme3.asset.BlenderKey.WorldData;
39import com.jme3.asset.ModelKey;
40import com.jme3.light.Light;
41import com.jme3.renderer.Camera;
42import com.jme3.scene.Node;
43import com.jme3.scene.Spatial;
44import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
45import com.jme3.scene.plugins.blender.animations.IpoHelper;
46import com.jme3.scene.plugins.blender.cameras.CameraHelper;
47import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
48import com.jme3.scene.plugins.blender.curves.CurvesHelper;
49import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
50import com.jme3.scene.plugins.blender.file.BlenderInputStream;
51import com.jme3.scene.plugins.blender.file.FileBlockHeader;
52import com.jme3.scene.plugins.blender.file.Structure;
53import com.jme3.scene.plugins.blender.lights.LightHelper;
54import com.jme3.scene.plugins.blender.materials.MaterialHelper;
55import com.jme3.scene.plugins.blender.meshes.MeshHelper;
56import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
57import com.jme3.scene.plugins.blender.objects.ObjectHelper;
58import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
59import com.jme3.scene.plugins.blender.textures.TextureHelper;
60import java.io.IOException;
61import java.util.ArrayList;
62import java.util.List;
63import java.util.logging.Level;
64import java.util.logging.Logger;
65
66/**
67 * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
68 * @author Marcin Roguski (Kaelthas)
69 */
70public class BlenderLoader extends AbstractBlenderLoader {
71
72	private static final Logger		LOGGER	= Logger.getLogger(BlenderLoader.class.getName());
73
74	/** The blocks read from the file. */
75	protected List<FileBlockHeader>	blocks;
76
77	@Override
78	public Spatial load(AssetInfo assetInfo) throws IOException {
79		try {
80			this.setup(assetInfo);
81
82			BlenderKey blenderKey = blenderContext.getBlenderKey();
83			LoadingResults loadingResults = blenderKey.prepareLoadingResults();
84			WorldData worldData = null;// a set of data used in different scene aspects
85			for (FileBlockHeader block : blocks) {
86				switch (block.getCode()) {
87					case FileBlockHeader.BLOCK_OB00:// Object
88						Object object = this.toObject(block.getStructure(blenderContext));
89						if (object instanceof Node) {
90							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
91								LOGGER.log(Level.INFO, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
92								if (this.isRootObject(loadingResults, (Node)object)) {
93									loadingResults.addObject((Node) object);
94								}
95							}
96						} else if (object instanceof Camera) {
97							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) {
98								loadingResults.addCamera((Camera) object);
99							}
100						} else if (object instanceof Light) {
101							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
102								loadingResults.addLight((Light) object);
103							}
104						}
105						break;
106					case FileBlockHeader.BLOCK_MA00:// Material
107						if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
108							loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));
109						}
110						break;
111					case FileBlockHeader.BLOCK_SC00:// Scene
112						if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) {
113							loadingResults.addScene(this.toScene(block.getStructure(blenderContext)));
114						}
115						break;
116					case FileBlockHeader.BLOCK_WO00:// World
117						if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used
118							Structure worldStructure = block.getStructure(blenderContext);
119							String worldName = worldStructure.getName();
120							if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
121								worldData = this.toWorldData(worldStructure);
122								if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
123									loadingResults.addLight(worldData.getAmbientLight());
124								}
125							}
126						}
127						break;
128				}
129			}
130			blenderContext.dispose();
131			return loadingResults;
132		} catch (BlenderFileException e) {
133			LOGGER.log(Level.SEVERE, e.getMessage(), e);
134		}
135		return null;
136	}
137
138	/**
139	 * This method indicates if the given spatial is a root object. It means it
140	 * has no parent or is directly attached to one of the already loaded scene
141	 * nodes.
142	 *
143	 * @param loadingResults
144	 *        loading results containing the scene nodes
145	 * @param spatial
146	 *        spatial object
147	 * @return <b>true</b> if the given spatial is a root object and
148	 *         <b>false</b> otherwise
149	 */
150	protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) {
151		if(spatial.getParent() == null) {
152			return true;
153		}
154		for(Node scene : loadingResults.getScenes()) {
155			if(spatial.getParent().equals(scene)) {
156				return true;
157			}
158		}
159		return false;
160	}
161
162	/**
163	 * This method sets up the loader.
164	 * @param assetInfo
165	 *        the asset info
166	 * @throws BlenderFileException
167	 *         an exception is throw when something wrong happens with blender file
168	 */
169	protected void setup(AssetInfo assetInfo) throws BlenderFileException {
170		// registering loaders
171		ModelKey modelKey = (ModelKey) assetInfo.getKey();
172		BlenderKey blenderKey;
173		if (modelKey instanceof BlenderKey) {
174			blenderKey = (BlenderKey) modelKey;
175		} else {
176			blenderKey = new BlenderKey(modelKey.getName());
177			blenderKey.setAssetRootPath(modelKey.getFolder());
178		}
179
180		// opening stream
181		BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager());
182
183		// reading blocks
184		blocks = new ArrayList<FileBlockHeader>();
185		FileBlockHeader fileBlock;
186		blenderContext = new BlenderContext();
187		blenderContext.setBlenderVersion(inputStream.getVersionNumber());
188		blenderContext.setAssetManager(assetInfo.getManager());
189		blenderContext.setInputStream(inputStream);
190		blenderContext.setBlenderKey(blenderKey);
191
192		// creating helpers
193		blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
194		blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
195		blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
196		blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
197		blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
198		blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
199		blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
200		blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
201		blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
202		blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis()));
203		blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
204		blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
205
206		// setting additional data to helpers
207		MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
208		materialHelper.setFaceCullMode(blenderKey.getFaceCullMode());
209
210		// reading the blocks (dna block is automatically saved in the blender context when found)//TODO: zmienić to
211		FileBlockHeader sceneFileBlock = null;
212		do {
213			fileBlock = new FileBlockHeader(inputStream, blenderContext);
214			if (!fileBlock.isDnaBlock()) {
215				blocks.add(fileBlock);
216				// save the scene's file block
217				if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00 && blenderKey.getLayersToLoad() < 0) {
218					sceneFileBlock = fileBlock;
219				}
220			}
221		} while (!fileBlock.isLastBlock());
222		// VERIFY LAYERS TO BE LOADED BEFORE LOADING FEATURES
223		if (sceneFileBlock != null) {
224			int lay = ((Number) sceneFileBlock.getStructure(blenderContext).getFieldValue("lay")).intValue();
225			blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer
226		}
227	}
228}
229