OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
MaterialsModule.cs
Go to the documentation of this file.
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 
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Reflection;
32 using System.Security.Cryptography; // for computing md5 hash
33 using log4net;
34 using Mono.Addins;
35 using Nini.Config;
36 
37 using OpenMetaverse;
38 using OpenMetaverse.StructuredData;
39 
40 using OpenSim.Framework;
41 using OpenSim.Framework.Servers;
42 using OpenSim.Framework.Servers.HttpServer;
43 using OpenSim.Region.Framework.Interfaces;
44 using OpenSim.Region.Framework.Scenes;
46 
47 using Ionic.Zlib;
48 
49 // You will need to uncomment these lines if you are adding a region module to some other assembly which does not already
50 // specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans
51 // the available DLLs
52 //[assembly: Addin("MaterialsModule", "1.0")]
53 //[assembly: AddinDependency("OpenSim", "0.8.1")]
54 
55 namespace OpenSim.Region.OptionalModules.Materials
56 {
57  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsModule")]
59  {
60  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
61 
62  public string Name { get { return "MaterialsModule"; } }
63 
64  public Type ReplaceableInterface { get { return null; } }
65 
66  private Scene m_scene = null;
67  private bool m_enabled = false;
68  private int m_maxMaterialsPerTransaction = 50;
69 
70  public Dictionary<UUID, OSDMap> m_regionMaterials = new Dictionary<UUID, OSDMap>();
71 
72  public void Initialise(IConfigSource source)
73  {
74  m_enabled = true; // default is enabled
75 
76  IConfig config = source.Configs["Materials"];
77  if (config != null)
78  {
79  m_enabled = config.GetBoolean("enable_materials", m_enabled);
80  m_maxMaterialsPerTransaction = config.GetInt("MaxMaterialsPerTransaction", m_maxMaterialsPerTransaction);
81  }
82 
83  if (m_enabled)
84  m_log.DebugFormat("[Materials]: Initialized");
85  }
86 
87  public void Close()
88  {
89  if (!m_enabled)
90  return;
91  }
92 
93  public void AddRegion(Scene scene)
94  {
95  if (!m_enabled)
96  return;
97 
98  m_scene = scene;
99  m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
100  m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene;
101  }
102 
103  private void EventManager_OnObjectAddedToScene(SceneObjectGroup obj)
104  {
105  foreach (var part in obj.Parts)
106  if (part != null)
107  GetStoredMaterialsInPart(part);
108  }
109 
110  private void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps)
111  {
112  string capsBase = "/CAPS/" + caps.CapsObjectPath;
113 
114  IRequestHandler renderMaterialsPostHandler
115  = new RestStreamHandler("POST", capsBase + "/",
116  (request, path, param, httpRequest, httpResponse)
117  => RenderMaterialsPostCap(request, agentID),
118  "RenderMaterials", null);
119  caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler);
120 
121  // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET
122  // and POST handlers, (at least at the time this was originally written), so we first set up a POST
123  // handler normally and then add a GET handler via MainServer
124 
125  IRequestHandler renderMaterialsGetHandler
126  = new RestStreamHandler("GET", capsBase + "/",
127  (request, path, param, httpRequest, httpResponse)
128  => RenderMaterialsGetCap(request),
129  "RenderMaterials", null);
130  MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler);
131 
132  // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well
133  IRequestHandler renderMaterialsPutHandler
134  = new RestStreamHandler("PUT", capsBase + "/",
135  (request, path, param, httpRequest, httpResponse)
136  => RenderMaterialsPostCap(request, agentID),
137  "RenderMaterials", null);
138  MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler);
139  }
140 
141  public void RemoveRegion(Scene scene)
142  {
143  if (!m_enabled)
144  return;
145 
146  m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
147  m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene;
148  }
149 
150  public void RegionLoaded(Scene scene)
151  {
152  if (!m_enabled) return;
153 
154  ISimulatorFeaturesModule featuresModule = scene.RequestModuleInterface<ISimulatorFeaturesModule>();
155  if (featuresModule != null)
156  featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest;
157  }
158 
159  private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features)
160  {
161  features["MaxMaterialsPerTransaction"] = m_maxMaterialsPerTransaction;
162  }
163 
168  private void GetLegacyStoredMaterialsInPart(SceneObjectPart part)
169  {
170  if (part.DynAttrs == null)
171  return;
172 
173  OSD OSMaterials = null;
174  OSDArray matsArr = null;
175 
176  lock (part.DynAttrs)
177  {
178  if (part.DynAttrs.ContainsStore("OpenSim", "Materials"))
179  {
180  OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials");
181 
182  if (materialsStore == null)
183  return;
184 
185  materialsStore.TryGetValue("Materials", out OSMaterials);
186  }
187 
188  if (OSMaterials != null && OSMaterials is OSDArray)
189  matsArr = OSMaterials as OSDArray;
190  else
191  return;
192  }
193 
194  if (matsArr == null)
195  return;
196 
197  foreach (OSD elemOsd in matsArr)
198  {
199  if (elemOsd != null && elemOsd is OSDMap)
200  {
201  OSDMap matMap = elemOsd as OSDMap;
202  if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material"))
203  {
204  try
205  {
206  lock (m_regionMaterials)
207  m_regionMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"];
208  }
209  catch (Exception e)
210  {
211  m_log.Warn("[Materials]: exception decoding persisted legacy material: " + e.ToString());
212  }
213  }
214  }
215  }
216  }
217 
221  private void GetStoredMaterialsInPart(SceneObjectPart part)
222  {
223  if (part.Shape == null)
224  return;
225 
226  var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length);
227  if (te == null)
228  return;
229 
230  GetLegacyStoredMaterialsInPart(part);
231 
232  if (te.DefaultTexture != null)
233  GetStoredMaterialInFace(part, te.DefaultTexture);
234  else
235  m_log.WarnFormat(
236  "[Materials]: Default texture for part {0} (part of object {1}) in {2} unexpectedly null. Ignoring.",
237  part.Name, part.ParentGroup.Name, m_scene.Name);
238 
239  foreach (Primitive.TextureEntryFace face in te.FaceTextures)
240  {
241  if (face != null)
242  GetStoredMaterialInFace(part, face);
243  }
244  }
245 
249  private void GetStoredMaterialInFace(SceneObjectPart part, Primitive.TextureEntryFace face)
250  {
251  UUID id = face.MaterialID;
252  if (id == UUID.Zero)
253  return;
254 
255  lock (m_regionMaterials)
256  {
257  if (m_regionMaterials.ContainsKey(id))
258  return;
259 
260  byte[] data = m_scene.AssetService.GetData(id.ToString());
261  if (data == null)
262  {
263  m_log.WarnFormat("[Materials]: Prim \"{0}\" ({1}) contains unknown material ID {2}", part.Name, part.UUID, id);
264  return;
265  }
266 
267  OSDMap mat;
268  try
269  {
270  mat = (OSDMap)OSDParser.DeserializeLLSDXml(data);
271  }
272  catch (Exception e)
273  {
274  m_log.WarnFormat("[Materials]: cannot decode material asset {0}: {1}", id, e.Message);
275  return;
276  }
277 
278  m_regionMaterials[id] = mat;
279  }
280  }
281 
282  public string RenderMaterialsPostCap(string request, UUID agentID)
283  {
284  OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request);
285  OSDMap resp = new OSDMap();
286 
287  OSDMap materialsFromViewer = null;
288 
289  OSDArray respArr = new OSDArray();
290 
291  if (req.ContainsKey("Zipped"))
292  {
293  OSD osd = null;
294 
295  byte[] inBytes = req["Zipped"].AsBinary();
296 
297  try
298  {
299  osd = ZDecompressBytesToOsd(inBytes);
300 
301  if (osd != null)
302  {
303  if (osd is OSDArray) // assume array of MaterialIDs designating requested material entries
304  {
305  foreach (OSD elem in (OSDArray)osd)
306  {
307  try
308  {
309  UUID id = new UUID(elem.AsBinary(), 0);
310 
311  lock (m_regionMaterials)
312  {
313  if (m_regionMaterials.ContainsKey(id))
314  {
315  OSDMap matMap = new OSDMap();
316  matMap["ID"] = OSD.FromBinary(id.GetBytes());
317  matMap["Material"] = m_regionMaterials[id];
318  respArr.Add(matMap);
319  }
320  else
321  {
322  m_log.Warn("[Materials]: request for unknown material ID: " + id.ToString());
323 
324  // Theoretically we could try to load the material from the assets service,
325  // but that shouldn't be necessary because the viewer should only request
326  // materials that exist in a prim on the region, and all of these materials
327  // are already stored in m_regionMaterials.
328  }
329  }
330  }
331  catch (Exception e)
332  {
333  m_log.Error("Error getting materials in response to viewer request", e);
334  continue;
335  }
336  }
337  }
338  else if (osd is OSDMap) // request to assign a material
339  {
340  materialsFromViewer = osd as OSDMap;
341 
342  if (materialsFromViewer.ContainsKey("FullMaterialsPerFace"))
343  {
344  OSD matsOsd = materialsFromViewer["FullMaterialsPerFace"];
345  if (matsOsd is OSDArray)
346  {
347  OSDArray matsArr = matsOsd as OSDArray;
348 
349  try
350  {
351  foreach (OSDMap matsMap in matsArr)
352  {
353  uint primLocalID = 0;
354  try {
355  primLocalID = matsMap["ID"].AsUInteger();
356  }
357  catch (Exception e) {
358  m_log.Warn("[Materials]: cannot decode \"ID\" from matsMap: " + e.Message);
359  continue;
360  }
361 
362  OSDMap mat = null;
363  try
364  {
365  mat = matsMap["Material"] as OSDMap;
366  }
367  catch (Exception e)
368  {
369  m_log.Warn("[Materials]: cannot decode \"Material\" from matsMap: " + e.Message);
370  continue;
371  }
372 
373  SceneObjectPart sop = m_scene.GetSceneObjectPart(primLocalID);
374  if (sop == null)
375  {
376  m_log.WarnFormat("[Materials]: SOP not found for localId: {0}", primLocalID.ToString());
377  continue;
378  }
379 
380  if (!m_scene.Permissions.CanEditObject(sop.UUID, agentID))
381  {
382  m_log.WarnFormat("User {0} can't edit object {1} {2}", agentID, sop.Name, sop.UUID);
383  continue;
384  }
385 
386  Primitive.TextureEntry te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length);
387  if (te == null)
388  {
389  m_log.WarnFormat("[Materials]: Error in TextureEntry for SOP {0} {1}", sop.Name, sop.UUID);
390  continue;
391  }
392 
393 
394  UUID id;
395  if (mat == null)
396  {
397  // This happens then the user removes a material from a prim
398  id = UUID.Zero;
399  }
400  else
401  {
402  id = StoreMaterialAsAsset(agentID, mat, sop);
403  }
404 
405 
406  int face = -1;
407 
408  if (matsMap.ContainsKey("Face"))
409  {
410  face = matsMap["Face"].AsInteger();
411  Primitive.TextureEntryFace faceEntry = te.CreateFace((uint)face);
412  faceEntry.MaterialID = id;
413  }
414  else
415  {
416  if (te.DefaultTexture == null)
417  m_log.WarnFormat("[Materials]: TextureEntry.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID);
418  else
419  te.DefaultTexture.MaterialID = id;
420  }
421 
422  //m_log.DebugFormat("[Materials]: in \"{0}\" {1}, setting material ID for face {2} to {3}", sop.Name, sop.UUID, face, id);
423 
424  // We can't use sop.UpdateTextureEntry(te) because it filters, so do it manually
425  sop.Shape.TextureEntry = te.GetBytes();
426 
427  if (sop.ParentGroup != null)
428  {
429  sop.TriggerScriptChangedEvent(Changed.TEXTURE);
430  sop.UpdateFlag = UpdateRequired.FULL;
431  sop.ParentGroup.HasGroupChanged = true;
432  sop.ScheduleFullUpdate();
433  }
434  }
435  }
436  catch (Exception e)
437  {
438  m_log.Warn("[Materials]: exception processing received material ", e);
439  }
440  }
441  }
442  }
443  }
444 
445  }
446  catch (Exception e)
447  {
448  m_log.Warn("[Materials]: exception decoding zipped CAP payload ", e);
449  //return "";
450  }
451  }
452 
453 
454  resp["Zipped"] = ZCompressOSD(respArr, false);
455  string response = OSDParser.SerializeLLSDXmlString(resp);
456 
457  //m_log.Debug("[Materials]: cap request: " + request);
458  //m_log.Debug("[Materials]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary()));
459  //m_log.Debug("[Materials]: cap response: " + response);
460  return response;
461  }
462 
463  private UUID StoreMaterialAsAsset(UUID agentID, OSDMap mat, SceneObjectPart sop)
464  {
465  UUID id;
466  // Material UUID = hash of the material's data.
467  // This makes materials deduplicate across the entire grid (but isn't otherwise required).
468  byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat));
469  using (var md5 = MD5.Create())
470  id = new UUID(md5.ComputeHash(data), 0);
471 
472  lock (m_regionMaterials)
473  {
474  if (!m_regionMaterials.ContainsKey(id))
475  {
476  m_regionMaterials[id] = mat;
477 
478  // This asset might exist already, but it's ok to try to store it again
479  string name = "Material " + ChooseMaterialName(mat, sop);
480  name = name.Substring(0, Math.Min(64, name.Length)).Trim();
481  AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString());
482  asset.Data = data;
483  m_scene.AssetService.Store(asset);
484  }
485  }
486  return id;
487  }
488 
492  private string ChooseMaterialName(OSDMap mat, SceneObjectPart sop)
493  {
494  UUID normMap = mat["NormMap"].AsUUID();
495  if (normMap != UUID.Zero)
496  {
497  AssetBase asset = m_scene.AssetService.GetCached(normMap.ToString());
498  if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR"))
499  return asset.Name;
500  }
501 
502  UUID specMap = mat["SpecMap"].AsUUID();
503  if (specMap != UUID.Zero)
504  {
505  AssetBase asset = m_scene.AssetService.GetCached(specMap.ToString());
506  if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR"))
507  return asset.Name;
508  }
509 
510  if (sop.Name != "Primitive")
511  return sop.Name;
512 
513  if ((sop.ParentGroup != null) && (sop.ParentGroup.Name != "Primitive"))
514  return sop.ParentGroup.Name;
515 
516  return "";
517  }
518 
519 
520  public string RenderMaterialsGetCap(string request)
521  {
522  OSDMap resp = new OSDMap();
523  int matsCount = 0;
524  OSDArray allOsd = new OSDArray();
525 
526  lock (m_regionMaterials)
527  {
528  foreach (KeyValuePair<UUID, OSDMap> kvp in m_regionMaterials)
529  {
530  OSDMap matMap = new OSDMap();
531 
532  matMap["ID"] = OSD.FromBinary(kvp.Key.GetBytes());
533  matMap["Material"] = kvp.Value;
534  allOsd.Add(matMap);
535  matsCount++;
536  }
537  }
538 
539  resp["Zipped"] = ZCompressOSD(allOsd, false);
540 
541  return OSDParser.SerializeLLSDXmlString(resp);
542  }
543 
544  private static string ZippedOsdBytesToString(byte[] bytes)
545  {
546  try
547  {
548  return OSDParser.SerializeJsonString(ZDecompressBytesToOsd(bytes));
549  }
550  catch (Exception e)
551  {
552  return "ZippedOsdBytesToString caught an exception: " + e.ToString();
553  }
554  }
555 
561  private static UUID HashOsd(OSD osd)
562  {
563  byte[] data = OSDParser.SerializeLLSDBinary(osd, false);
564  using (var md5 = MD5.Create())
565  return new UUID(md5.ComputeHash(data), 0);
566  }
567 
568  public static OSD ZCompressOSD(OSD inOsd, bool useHeader)
569  {
570  OSD osd = null;
571 
572  byte[] data = OSDParser.SerializeLLSDBinary(inOsd, useHeader);
573 
574  using (MemoryStream msSinkCompressed = new MemoryStream())
575  {
576  using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed,
577  Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true))
578  {
579  zOut.Write(data, 0, data.Length);
580  }
581 
582  msSinkCompressed.Seek(0L, SeekOrigin.Begin);
583  osd = OSD.FromBinary(msSinkCompressed.ToArray());
584  }
585 
586  return osd;
587  }
588 
589 
590  public static OSD ZDecompressBytesToOsd(byte[] input)
591  {
592  OSD osd = null;
593 
594  using (MemoryStream msSinkUnCompressed = new MemoryStream())
595  {
596  using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true))
597  {
598  zOut.Write(input, 0, input.Length);
599  }
600 
601  msSinkUnCompressed.Seek(0L, SeekOrigin.Begin);
602  osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray());
603  }
604 
605  return osd;
606  }
607  }
608 }
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
OpenMetaverse.StructuredData.OSDArray OSDArray
OpenSim.Framework.SLUtil.OpenSimAssetType OpenSimAssetType
string RenderMaterialsPostCap(string request, UUID agentID)
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
OpenMetaverse.StructuredData.OSDMap OSDMap
A scene object group is conceptually an object in the scene. The object is constituted of SceneObject...
Add remove or retrieve Simulator Features that will be given to a viewer via the SimulatorFeatures ca...
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
static OSD ZCompressOSD(OSD inOsd, bool useHeader)
OpenMetaverse.StructuredData.OSD OSD
Interactive OpenSim region server
Definition: OpenSim.cs:55
OpenSim.Framework.SLUtil.OpenSimAssetType OpenSimAssetType
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
DAMap DynAttrs
Dynamic attributes can be created and deleted as required.
Material
Material type for a primitive
Definition: OdeScene.cs:79
void Initialise(IConfigSource source)
This is called to initialize the region module. For shared modules, this is called exactly once...
bool ContainsStore(string ns, string storeName)
Definition: DAMap.cs:266