aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs')
-rw-r--r--OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs657
1 files changed, 657 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs
new file mode 100644
index 0000000..d8f5563
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs
@@ -0,0 +1,657 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Reflection;
32using System.Security.Cryptography; // for computing md5 hash
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36
37using OpenMetaverse;
38using OpenMetaverse.StructuredData;
39
40using OpenSim.Framework;
41using OpenSim.Framework.Servers;
42using OpenSim.Framework.Servers.HttpServer;
43using OpenSim.Region.Framework.Interfaces;
44using OpenSim.Region.Framework.Scenes;
45
46using Ionic.Zlib;
47
48// You will need to uncomment these lines if you are adding a region module to some other assembly which does not already
49// specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans
50// the available DLLs
51//[assembly: Addin("MaterialsDemoModule", "1.0")]
52//[assembly: AddinDependency("OpenSim", "0.5")]
53
54namespace OpenSim.Region.OptionalModules.MaterialsDemoModule
55{
56 /// <summary>
57 ///
58 // # # ## ##### # # # # # ####
59 // # # # # # # ## # # ## # # #
60 // # # # # # # # # # # # # # #
61 // # ## # ###### ##### # # # # # # # # ###
62 // ## ## # # # # # ## # # ## # #
63 // # # # # # # # # # # # ####
64 //
65 // THIS MODULE IS FOR EXPERIMENTAL USE ONLY AND MAY CAUSE REGION OR ASSET CORRUPTION!
66 //
67 ////////////// WARNING //////////////////////////////////////////////////////////////////
68 /// This is an *Experimental* module for developing support for materials-capable viewers
69 /// This module should NOT be used in a production environment! It may cause data corruption and
70 /// viewer crashes. It should be only used to evaluate implementations of materials.
71 ///
72 /// Materials are persisted via SceneObjectPart.dynattrs. This is a relatively new feature
73 /// of OpenSimulator and is not field proven at the time this module was written. Persistence
74 /// may fail or become corrupt and this could cause viewer crashes due to erroneous materials
75 /// data being sent to viewers. Materials descriptions might survive IAR, OAR, or other means
76 /// of archiving however the texture resources used by these materials probably will not as they
77 /// may not be adequately referenced to ensure proper archiving.
78 ///
79 ///
80 ///
81 /// To enable this module, add this string at the bottom of OpenSim.ini:
82 /// [MaterialsDemoModule]
83 ///
84 /// </summary>
85 ///
86
87 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsDemoModule")]
88 public class MaterialsDemoModule : INonSharedRegionModule
89 {
90 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
91
92 public string Name { get { return "MaterialsDemoModule"; } }
93
94 public Type ReplaceableInterface { get { return null; } }
95
96 private Scene m_scene = null;
97 private bool m_enabled = false;
98
99 public Dictionary<UUID, OSDMap> m_knownMaterials = new Dictionary<UUID, OSDMap>();
100
101 public void Initialise(IConfigSource source)
102 {
103 m_enabled = (source.Configs["MaterialsDemoModule"] != null);
104 if (!m_enabled)
105 return;
106
107 m_log.DebugFormat("[MaterialsDemoModule]: INITIALIZED MODULE");
108 }
109
110 public void Close()
111 {
112 if (!m_enabled)
113 return;
114
115 m_log.DebugFormat("[MaterialsDemoModule]: CLOSED MODULE");
116 }
117
118 public void AddRegion(Scene scene)
119 {
120 if (!m_enabled)
121 return;
122
123 m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName);
124
125 m_scene = scene;
126 m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
127 m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene;
128// m_scene.EventManager.OnGatherUuids += GatherMaterialsUuids;
129 }
130
131 void EventManager_OnObjectAddedToScene(SceneObjectGroup obj)
132 {
133 foreach (var part in obj.Parts)
134 if (part != null)
135 GetStoredMaterialsForPart(part);
136 }
137
138 void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps)
139 {
140 string capsBase = "/CAPS/" + caps.CapsObjectPath;
141
142 IRequestHandler renderMaterialsPostHandler
143 = new RestStreamHandler("POST", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null);
144 caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler);
145
146 // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET
147 // and POST handlers, (at least at the time this was originally written), so we first set up a POST
148 // handler normally and then add a GET handler via MainServer
149
150 IRequestHandler renderMaterialsGetHandler
151 = new RestStreamHandler("GET", capsBase + "/", RenderMaterialsGetCap, "RenderMaterials", null);
152 MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler);
153
154 // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well
155 IRequestHandler renderMaterialsPutHandler
156 = new RestStreamHandler("PUT", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null);
157 MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler);
158 }
159
160 public void RemoveRegion(Scene scene)
161 {
162 if (!m_enabled)
163 return;
164
165 m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
166 m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene;
167// m_scene.EventManager.OnGatherUuids -= GatherMaterialsUuids;
168
169 m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
170 }
171
172 public void RegionLoaded(Scene scene)
173 {
174 }
175
176 OSDMap GetMaterial(UUID id)
177 {
178 OSDMap map = null;
179 lock (m_knownMaterials)
180 {
181 if (m_knownMaterials.ContainsKey(id))
182 {
183 map = new OSDMap();
184 map["ID"] = OSD.FromBinary(id.GetBytes());
185 map["Material"] = m_knownMaterials[id];
186 }
187 }
188 return map;
189 }
190
191 void GetStoredMaterialsForPart(SceneObjectPart part)
192 {
193 OSD OSMaterials = null;
194 OSDArray matsArr = null;
195
196 if (part.DynAttrs == null)
197 {
198 m_log.Warn("[MaterialsDemoModule]: NULL DYNATTRS :( ");
199 }
200
201 lock (part.DynAttrs)
202 {
203 if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
204 {
205 OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
206
207 if (materialsStore == null)
208 return;
209
210 materialsStore.TryGetValue("Materials", out OSMaterials);
211 }
212
213 if (OSMaterials != null && OSMaterials is OSDArray)
214 matsArr = OSMaterials as OSDArray;
215 else
216 return;
217 }
218
219 m_log.Info("[MaterialsDemoModule]: OSMaterials: " + OSDParser.SerializeJsonString(OSMaterials));
220
221 if (matsArr == null)
222 {
223 m_log.Info("[MaterialsDemoModule]: matsArr is null :( ");
224 return;
225 }
226
227 foreach (OSD elemOsd in matsArr)
228 {
229 if (elemOsd != null && elemOsd is OSDMap)
230 {
231 OSDMap matMap = elemOsd as OSDMap;
232 if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material"))
233 {
234 try
235 {
236 lock (m_knownMaterials)
237 m_knownMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"];
238 }
239 catch (Exception e)
240 {
241 m_log.Warn("[MaterialsDemoModule]: exception decoding persisted material: " + e.ToString());
242 }
243 }
244 }
245 }
246 }
247
248 void StoreMaterialsForPart(SceneObjectPart part)
249 {
250 try
251 {
252 if (part == null || part.Shape == null)
253 return;
254
255 Dictionary<UUID, OSDMap> mats = new Dictionary<UUID, OSDMap>();
256
257 Primitive.TextureEntry te = part.Shape.Textures;
258
259 if (te.DefaultTexture != null)
260 {
261 lock (m_knownMaterials)
262 {
263 if (m_knownMaterials.ContainsKey(te.DefaultTexture.MaterialID))
264 mats[te.DefaultTexture.MaterialID] = m_knownMaterials[te.DefaultTexture.MaterialID];
265 }
266 }
267
268 if (te.FaceTextures != null)
269 {
270 foreach (var face in te.FaceTextures)
271 {
272 if (face != null)
273 {
274 lock (m_knownMaterials)
275 {
276 if (m_knownMaterials.ContainsKey(face.MaterialID))
277 mats[face.MaterialID] = m_knownMaterials[face.MaterialID];
278 }
279 }
280 }
281 }
282 if (mats.Count == 0)
283 return;
284
285 OSDArray matsArr = new OSDArray();
286 foreach (KeyValuePair<UUID, OSDMap> kvp in mats)
287 {
288 OSDMap matOsd = new OSDMap();
289 matOsd["ID"] = OSD.FromUUID(kvp.Key);
290 matOsd["Material"] = kvp.Value;
291 matsArr.Add(matOsd);
292 }
293
294 OSDMap OSMaterials = new OSDMap();
295 OSMaterials["Materials"] = matsArr;
296
297 lock (part.DynAttrs)
298 part.DynAttrs.SetStore("OpenSim", "Materials", OSMaterials);
299 }
300 catch (Exception e)
301 {
302 m_log.Warn("[MaterialsDemoModule]: exception in StoreMaterialsForPart(): " + e.ToString());
303 }
304 }
305
306 public string RenderMaterialsPostCap(string request, string path,
307 string param, IOSHttpRequest httpRequest,
308 IOSHttpResponse httpResponse)
309 {
310 m_log.Debug("[MaterialsDemoModule]: POST cap handler");
311
312 OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request);
313 OSDMap resp = new OSDMap();
314
315 OSDMap materialsFromViewer = null;
316
317 OSDArray respArr = new OSDArray();
318
319 if (req.ContainsKey("Zipped"))
320 {
321 OSD osd = null;
322
323 byte[] inBytes = req["Zipped"].AsBinary();
324
325 try
326 {
327 osd = ZDecompressBytesToOsd(inBytes);
328
329 if (osd != null)
330 {
331 if (osd is OSDArray) // assume array of MaterialIDs designating requested material entries
332 {
333 foreach (OSD elem in (OSDArray)osd)
334 {
335
336 try
337 {
338 UUID id = new UUID(elem.AsBinary(), 0);
339
340 lock (m_knownMaterials)
341 {
342 if (m_knownMaterials.ContainsKey(id))
343 {
344 m_log.Info("[MaterialsDemoModule]: request for known material ID: " + id.ToString());
345 OSDMap matMap = new OSDMap();
346 matMap["ID"] = OSD.FromBinary(id.GetBytes());
347
348 matMap["Material"] = m_knownMaterials[id];
349 respArr.Add(matMap);
350 }
351 else
352 m_log.Info("[MaterialsDemoModule]: request for UNKNOWN material ID: " + id.ToString());
353 }
354 }
355 catch (Exception e)
356 {
357 // report something here?
358 continue;
359 }
360 }
361 }
362 else if (osd is OSDMap) // reqest to assign a material
363 {
364 materialsFromViewer = osd as OSDMap;
365
366 if (materialsFromViewer.ContainsKey("FullMaterialsPerFace"))
367 {
368 OSD matsOsd = materialsFromViewer["FullMaterialsPerFace"];
369 if (matsOsd is OSDArray)
370 {
371 OSDArray matsArr = matsOsd as OSDArray;
372
373 try
374 {
375 foreach (OSDMap matsMap in matsArr)
376 {
377 m_log.Debug("[MaterialsDemoModule]: processing matsMap: " + OSDParser.SerializeJsonString(matsMap));
378
379 uint matLocalID = 0;
380 try { matLocalID = matsMap["ID"].AsUInteger(); }
381 catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"ID\" from matsMap: " + e.Message); }
382 m_log.Debug("[MaterialsDemoModule]: matLocalId: " + matLocalID.ToString());
383
384
385 OSDMap mat = null;
386 try { mat = matsMap["Material"] as OSDMap; }
387 catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"Material\" from matsMap: " + e.Message); }
388 m_log.Debug("[MaterialsDemoModule]: mat: " + OSDParser.SerializeJsonString(mat));
389
390 UUID id = HashOsd(mat);
391 lock (m_knownMaterials)
392 m_knownMaterials[id] = mat;
393
394
395 var sop = m_scene.GetSceneObjectPart(matLocalID);
396 if (sop == null)
397 m_log.Debug("[MaterialsDemoModule]: null SOP for localId: " + matLocalID.ToString());
398 else
399 {
400 var te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length);
401
402 if (te == null)
403 {
404 m_log.Debug("[MaterialsDemoModule]: null TextureEntry for localId: " + matLocalID.ToString());
405 }
406 else
407 {
408 int face = -1;
409
410 if (matsMap.ContainsKey("Face"))
411 {
412 face = matsMap["Face"].AsInteger();
413 if (te.FaceTextures == null) // && face == 0)
414 {
415 if (te.DefaultTexture == null)
416 m_log.Debug("[MaterialsDemoModule]: te.DefaultTexture is null");
417 else
418 te.DefaultTexture.MaterialID = id;
419 }
420 else
421 {
422 if (te.FaceTextures.Length >= face - 1)
423 {
424 if (te.FaceTextures[face] == null)
425 te.DefaultTexture.MaterialID = id;
426 else
427 te.FaceTextures[face].MaterialID = id;
428 }
429 }
430 }
431 else
432 {
433 if (te.DefaultTexture != null)
434 te.DefaultTexture.MaterialID = id;
435 }
436
437 m_log.Debug("[MaterialsDemoModule]: setting material ID for face " + face.ToString() + " to " + id.ToString());
438
439 //we cant use sop.UpdateTextureEntry(te); because it filters so do it manually
440
441 if (sop.ParentGroup != null)
442 {
443 sop.Shape.TextureEntry = te.GetBytes();
444 sop.TriggerScriptChangedEvent(Changed.TEXTURE);
445 sop.UpdateFlag = UpdateRequired.FULL;
446 sop.ParentGroup.HasGroupChanged = true;
447
448 sop.ScheduleFullUpdate();
449
450 StoreMaterialsForPart(sop);
451 }
452 }
453 }
454 }
455 }
456 catch (Exception e)
457 {
458 m_log.Warn("[MaterialsDemoModule]: exception processing received material: " + e.Message);
459 }
460 }
461 }
462 }
463 }
464
465 }
466 catch (Exception e)
467 {
468 m_log.Warn("[MaterialsDemoModule]: exception decoding zipped CAP payload: " + e.Message);
469 //return "";
470 }
471 m_log.Debug("[MaterialsDemoModule]: knownMaterials.Count: " + m_knownMaterials.Count.ToString());
472 }
473
474
475 resp["Zipped"] = ZCompressOSD(respArr, false);
476 string response = OSDParser.SerializeLLSDXmlString(resp);
477
478 //m_log.Debug("[MaterialsDemoModule]: cap request: " + request);
479 m_log.Debug("[MaterialsDemoModule]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary()));
480 m_log.Debug("[MaterialsDemoModule]: cap response: " + response);
481 return response;
482 }
483
484
485 public string RenderMaterialsGetCap(string request, string path,
486 string param, IOSHttpRequest httpRequest,
487 IOSHttpResponse httpResponse)
488 {
489 m_log.Debug("[MaterialsDemoModule]: GET cap handler");
490
491 OSDMap resp = new OSDMap();
492 int matsCount = 0;
493 OSDArray allOsd = new OSDArray();
494
495 lock (m_knownMaterials)
496 {
497 foreach (KeyValuePair<UUID, OSDMap> kvp in m_knownMaterials)
498 {
499 OSDMap matMap = new OSDMap();
500
501 matMap["ID"] = OSD.FromBinary(kvp.Key.GetBytes());
502 matMap["Material"] = kvp.Value;
503 allOsd.Add(matMap);
504 matsCount++;
505 }
506 }
507
508 resp["Zipped"] = ZCompressOSD(allOsd, false);
509 m_log.Debug("[MaterialsDemoModule]: matsCount: " + matsCount.ToString());
510
511 return OSDParser.SerializeLLSDXmlString(resp);
512 }
513
514 static string ZippedOsdBytesToString(byte[] bytes)
515 {
516 try
517 {
518 return OSDParser.SerializeJsonString(ZDecompressBytesToOsd(bytes));
519 }
520 catch (Exception e)
521 {
522 return "ZippedOsdBytesToString caught an exception: " + e.ToString();
523 }
524 }
525
526 /// <summary>
527 /// computes a UUID by hashing a OSD object
528 /// </summary>
529 /// <param name="osd"></param>
530 /// <returns></returns>
531 private static UUID HashOsd(OSD osd)
532 {
533 using (var md5 = MD5.Create())
534 using (MemoryStream ms = new MemoryStream(OSDParser.SerializeLLSDBinary(osd, false)))
535 return new UUID(md5.ComputeHash(ms), 0);
536 }
537
538 public static OSD ZCompressOSD(OSD inOsd, bool useHeader)
539 {
540 OSD osd = null;
541
542 using (MemoryStream msSinkCompressed = new MemoryStream())
543 {
544 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed,
545 Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true))
546 {
547 CopyStream(new MemoryStream(OSDParser.SerializeLLSDBinary(inOsd, useHeader)), zOut);
548 zOut.Close();
549 }
550
551 msSinkCompressed.Seek(0L, SeekOrigin.Begin);
552 osd = OSD.FromBinary( msSinkCompressed.ToArray());
553 }
554
555 return osd;
556 }
557
558
559 public static OSD ZDecompressBytesToOsd(byte[] input)
560 {
561 OSD osd = null;
562
563 using (MemoryStream msSinkUnCompressed = new MemoryStream())
564 {
565 using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true))
566 {
567 CopyStream(new MemoryStream(input), zOut);
568 zOut.Close();
569 }
570 msSinkUnCompressed.Seek(0L, SeekOrigin.Begin);
571 osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray());
572 }
573
574 return osd;
575 }
576
577 static void CopyStream(System.IO.Stream input, System.IO.Stream output)
578 {
579 byte[] buffer = new byte[2048];
580 int len;
581 while ((len = input.Read(buffer, 0, 2048)) > 0)
582 {
583 output.Write(buffer, 0, len);
584 }
585
586 output.Flush();
587 }
588
589 // FIXME: This code is currently still in UuidGatherer since we cannot use Scene.EventManager as some
590 // calls to the gatherer are done for objects with no scene.
591// /// <summary>
592// /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps
593// /// </summary>
594// /// <param name="part"></param>
595// /// <param name="assetUuids"></param>
596// private void GatherMaterialsUuids(SceneObjectPart part, IDictionary<UUID, AssetType> assetUuids)
597// {
598// // scan thru the dynAttrs map of this part for any textures used as materials
599// OSD osdMaterials = null;
600//
601// lock (part.DynAttrs)
602// {
603// if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
604// {
605// OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
606// if (materialsStore == null)
607// return;
608//
609// materialsStore.TryGetValue("Materials", out osdMaterials);
610// }
611//
612// if (osdMaterials != null)
613// {
614// //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd));
615//
616// if (osdMaterials is OSDArray)
617// {
618// OSDArray matsArr = osdMaterials as OSDArray;
619// foreach (OSDMap matMap in matsArr)
620// {
621// try
622// {
623// if (matMap.ContainsKey("Material"))
624// {
625// OSDMap mat = matMap["Material"] as OSDMap;
626// if (mat.ContainsKey("NormMap"))
627// {
628// UUID normalMapId = mat["NormMap"].AsUUID();
629// if (normalMapId != UUID.Zero)
630// {
631// assetUuids[normalMapId] = AssetType.Texture;
632// //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString());
633// }
634// }
635// if (mat.ContainsKey("SpecMap"))
636// {
637// UUID specularMapId = mat["SpecMap"].AsUUID();
638// if (specularMapId != UUID.Zero)
639// {
640// assetUuids[specularMapId] = AssetType.Texture;
641// //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString());
642// }
643// }
644// }
645//
646// }
647// catch (Exception e)
648// {
649// m_log.Warn("[MaterialsDemoModule]: exception getting materials: " + e.Message);
650// }
651// }
652// }
653// }
654// }
655// }
656 }
657} \ No newline at end of file