OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
MeshCost.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 
29 using System;
30 using System.IO;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Text;
34 
35 using OpenMetaverse;
36 using OpenMetaverse.StructuredData;
37 
38 using OpenSim.Framework;
39 using OpenSim.Region.Framework;
40 using OpenSim.Region.Framework.Scenes;
41 using OpenSim.Framework.Capabilities;
42 
43 using ComponentAce.Compression.Libs.zlib;
44 
47 
48 namespace OpenSim.Region.ClientStack.Linden
49 {
50  public struct ModelPrimLimits
51  {
52 
53  }
54 
55  public class ModelCost
56  {
57 
58  // upload fee defaults
59  // fees are normalized to 1.0
60  // this parameters scale them to basic cost ( so 1.0 translates to 10 )
61 
62  public float ModelMeshCostFactor = 0.0f; // scale total cost relative to basic (excluding textures)
63  public float ModelTextureCostFactor = 1.0f; // scale textures fee to basic.
64  public float ModelMinCostFactor = 0.0f; // 0.5f; // minimum total model free excluding textures
65 
66  // itens costs in normalized values
67  // ie will be multiplied by basicCost and factors above
68  public float primCreationCost = 0.002f; // extra cost for each prim creation overhead
69  // weigthed size to normalized cost
70  public float bytecost = 1e-5f;
71 
72  // mesh upload fees based on compressed data sizes
73  // several data sections are counted more that once
74  // to promote user optimization
75  // following parameters control how many extra times they are added
76  // to global size.
77  // LOD meshs
78  const float medSizeWth = 1f; // 2x
79  const float lowSizeWth = 1.5f; // 2.5x
80  const float lowestSizeWth = 2f; // 3x
81  // favor potencially physical optimized meshs versus automatic decomposition
82  const float physMeshSizeWth = 6f; // counts 7x
83  const float physHullSizeWth = 8f; // counts 9x
84 
85  // stream cost area factors
86  // more or less like SL
87  const float highLodFactor = 17.36f;
88  const float midLodFactor = 277.78f;
89  const float lowLodFactor = 1111.11f;
90 
91  // physics cost is below, identical to SL, assuming shape type convex
92  // server cost is below identical to SL assuming non scripted non physical object
93 
94  // internal
95  const int bytesPerCoord = 6; // 3 coords, 2 bytes per each
96 
97  // control prims dimensions
98  public float PrimScaleMin = 0.001f;
99  public float NonPhysicalPrimScaleMax = 256f;
100  public float PhysicalPrimScaleMax = 10f;
101  public int ObjectLinkedPartsMax = 512;
102 
103  // storage for a single mesh asset cost parameters
104  private class ameshCostParam
105  {
106  // LOD sizes for size dependent streaming cost
107  public int highLODSize;
108  public int medLODSize;
109  public int lowLODSize;
110  public int lowestLODSize;
111  // normalized fee based on compressed data sizes
112  public float costFee;
113  // physics cost
114  public float physicsCost;
115  }
116 
117  // calculates a mesh model costs
118  // returns false on error, with a reason on parameter error
119  // resources input LLSD request
120  // basicCost input region assets upload cost
121  // totalcost returns model total upload fee
122  // meshcostdata returns detailed costs for viewer
123  // avatarSkeleton if mesh includes a avatar skeleton
124  // useAvatarCollider if we should use physics mesh for avatar
125  public bool MeshModelCost(LLSDAssetResource resources, int basicCost, out int totalcost,
126  LLSDAssetUploadResponseData meshcostdata, out string error, ref string warning)
127  {
128  totalcost = 0;
129  error = string.Empty;
130 
131  bool avatarSkeleton = false;
132 
133  if (resources == null ||
134  resources.instance_list == null ||
135  resources.instance_list.Array.Count == 0)
136  {
137  error = "missing model information.";
138  return false;
139  }
140 
141  int numberInstances = resources.instance_list.Array.Count;
142 
143  if (ObjectLinkedPartsMax != 0 && numberInstances > ObjectLinkedPartsMax)
144  {
145  error = "Model would have more than " + ObjectLinkedPartsMax.ToString() + " linked prims";
146  return false;
147  }
148 
149  meshcostdata.model_streaming_cost = 0.0;
150  meshcostdata.simulation_cost = 0.0;
151  meshcostdata.physics_cost = 0.0;
152  meshcostdata.resource_cost = 0.0;
153 
154  meshcostdata.upload_price_breakdown.mesh_instance = 0;
155  meshcostdata.upload_price_breakdown.mesh_physics = 0;
156  meshcostdata.upload_price_breakdown.mesh_streaming = 0;
157  meshcostdata.upload_price_breakdown.model = 0;
158 
159  int itmp;
160 
161  // textures cost
162  if (resources.texture_list != null && resources.texture_list.Array.Count > 0)
163  {
164  float textures_cost = (float)(resources.texture_list.Array.Count * basicCost);
165  textures_cost *= ModelTextureCostFactor;
166 
167  itmp = (int)(textures_cost + 0.5f); // round
168  meshcostdata.upload_price_breakdown.texture = itmp;
169  totalcost += itmp;
170  }
171 
172  // meshs assets cost
173  float meshsfee = 0;
174  int numberMeshs = 0;
175  bool haveMeshs = false;
176 
177  bool curskeleton;
178  bool curAvatarPhys;
179 
180  List<ameshCostParam> meshsCosts = new List<ameshCostParam>();
181 
182  if (resources.mesh_list != null && resources.mesh_list.Array.Count > 0)
183  {
184  numberMeshs = resources.mesh_list.Array.Count;
185 
186  for (int i = 0; i < numberMeshs; i++)
187  {
188  ameshCostParam curCost = new ameshCostParam();
189  byte[] data = (byte[])resources.mesh_list.Array[i];
190 
191  if (!MeshCost(data, curCost,out curskeleton, out curAvatarPhys, out error))
192  {
193  return false;
194  }
195 
196  if (curskeleton)
197  {
198  if (avatarSkeleton)
199  {
200  error = "model can only contain a avatar skeleton";
201  return false;
202  }
203  avatarSkeleton = true;
204  }
205  meshsCosts.Add(curCost);
206  meshsfee += curCost.costFee;
207  }
208  haveMeshs = true;
209  }
210 
211  // instances (prims) cost
212 
213 
214  int mesh;
215  int skipedSmall = 0;
216  for (int i = 0; i < numberInstances; i++)
217  {
218  Hashtable inst = (Hashtable)resources.instance_list.Array[i];
219 
220  ArrayList ascale = (ArrayList)inst["scale"];
221  Vector3 scale;
222  double tmp;
223  tmp = (double)ascale[0];
224  scale.X = (float)tmp;
225  tmp = (double)ascale[1];
226  scale.Y = (float)tmp;
227  tmp = (double)ascale[2];
228  scale.Z = (float)tmp;
229 
230  if (scale.X < PrimScaleMin || scale.Y < PrimScaleMin || scale.Z < PrimScaleMin)
231  {
232  skipedSmall++;
233  continue;
234  }
235 
236  if (scale.X > NonPhysicalPrimScaleMax || scale.Y > NonPhysicalPrimScaleMax || scale.Z > NonPhysicalPrimScaleMax)
237  {
238  error = "Model contains parts with sides larger than " + NonPhysicalPrimScaleMax.ToString() + "m. Please ajust scale";
239  return false;
240  }
241 
242  if (haveMeshs && inst.ContainsKey("mesh"))
243  {
244  mesh = (int)inst["mesh"];
245 
246  if (mesh >= numberMeshs)
247  {
248  error = "Incoerent model information.";
249  return false;
250  }
251 
252  // streamming cost
253 
254  float sqdiam = scale.LengthSquared();
255 
256  ameshCostParam curCost = meshsCosts[mesh];
257  float mesh_streaming = streamingCost(curCost, sqdiam);
258 
259  meshcostdata.model_streaming_cost += mesh_streaming;
260  meshcostdata.physics_cost += curCost.physicsCost;
261  }
262  else // instance as no mesh ??
263  {
264  // to do later if needed
265  meshcostdata.model_streaming_cost += 0.5f;
266  meshcostdata.physics_cost += 1.0f;
267  }
268 
269  // assume unscripted and static prim server cost
270  meshcostdata.simulation_cost += 0.5f;
271  // charge for prims creation
272  meshsfee += primCreationCost;
273  }
274 
275  if (skipedSmall > 0)
276  {
277  if (skipedSmall > numberInstances / 2)
278  {
279  error = "Model contains too many prims smaller than " + PrimScaleMin.ToString() +
280  "m minimum allowed size. Please check scalling";
281  return false;
282  }
283  else
284  warning += skipedSmall.ToString() + " of the requested " +numberInstances.ToString() +
285  " model prims will not upload because they are smaller than " + PrimScaleMin.ToString() +
286  "m minimum allowed size. Please check scalling ";
287  }
288 
289  if (meshcostdata.physics_cost <= meshcostdata.model_streaming_cost)
290  meshcostdata.resource_cost = meshcostdata.model_streaming_cost;
291  else
292  meshcostdata.resource_cost = meshcostdata.physics_cost;
293 
294  if (meshcostdata.resource_cost < meshcostdata.simulation_cost)
295  meshcostdata.resource_cost = meshcostdata.simulation_cost;
296 
297  // scale cost
298  // at this point a cost of 1.0 whould mean basic cost
299  meshsfee *= ModelMeshCostFactor;
300 
301  if (meshsfee < ModelMinCostFactor)
302  meshsfee = ModelMinCostFactor;
303 
304  // actually scale it to basic cost
305  meshsfee *= (float)basicCost;
306 
307  meshsfee += 0.5f; // rounding
308 
309  totalcost += (int)meshsfee;
310 
311  // breakdown prices
312  // don't seem to be in use so removed code for now
313 
314  return true;
315  }
316 
317  // single mesh asset cost
318  private bool MeshCost(byte[] data, ameshCostParam cost,out bool skeleton, out bool avatarPhys, out string error)
319  {
320  cost.highLODSize = 0;
321  cost.medLODSize = 0;
322  cost.lowLODSize = 0;
323  cost.lowestLODSize = 0;
324  cost.physicsCost = 0.0f;
325  cost.costFee = 0.0f;
326 
327  error = string.Empty;
328 
329  skeleton = false;
330  avatarPhys = false;
331 
332  if (data == null || data.Length == 0)
333  {
334  error = "Missing model information.";
335  return false;
336  }
337 
338  OSD meshOsd = null;
339  int start = 0;
340 
341  error = "Invalid model data";
342 
343  using (MemoryStream ms = new MemoryStream(data))
344  {
345  try
346  {
347  OSD osd = OSDParser.DeserializeLLSDBinary(ms);
348  if (osd is OSDMap)
349  meshOsd = (OSDMap)osd;
350  else
351  return false;
352  }
353  catch
354  {
355  return false;
356  }
357  start = (int)ms.Position;
358  }
359 
360  OSDMap map = (OSDMap)meshOsd;
361  OSDMap tmpmap;
362 
363  int highlod_size = 0;
364  int medlod_size = 0;
365  int lowlod_size = 0;
366  int lowestlod_size = 0;
367  int skin_size = 0;
368 
369  int hulls_size = 0;
370  int phys_nhulls;
371  int phys_hullsvertices = 0;
372 
373  int physmesh_size = 0;
374  int phys_ntriangles = 0;
375 
376  int submesh_offset = -1;
377 
378  if (map.ContainsKey("skeleton"))
379  {
380  tmpmap = (OSDMap)map["skeleton"];
381  if (tmpmap.ContainsKey("offset") && tmpmap.ContainsKey("size"))
382  {
383  int sksize = tmpmap["size"].AsInteger();
384  if(sksize > 0)
385  skeleton = true;
386  }
387  }
388 
389  if (map.ContainsKey("physics_convex"))
390  {
391  tmpmap = (OSDMap)map["physics_convex"];
392  if (tmpmap.ContainsKey("offset"))
393  submesh_offset = tmpmap["offset"].AsInteger() + start;
394  if (tmpmap.ContainsKey("size"))
395  hulls_size = tmpmap["size"].AsInteger();
396  }
397 
398  if (submesh_offset < 0 || hulls_size == 0)
399  {
400  error = "Missing physics_convex block";
401  return false;
402  }
403 
404  if (!hulls(data, submesh_offset, hulls_size, out phys_hullsvertices, out phys_nhulls))
405  {
406  error = "Bad physics_convex block";
407  return false;
408  }
409 
410  submesh_offset = -1;
411 
412  // only look for LOD meshs sizes
413 
414  if (map.ContainsKey("high_lod"))
415  {
416  tmpmap = (OSDMap)map["high_lod"];
417  // see at least if there is a offset for this one
418  if (tmpmap.ContainsKey("offset"))
419  submesh_offset = tmpmap["offset"].AsInteger() + start;
420  if (tmpmap.ContainsKey("size"))
421  highlod_size = tmpmap["size"].AsInteger();
422  }
423 
424  if (submesh_offset < 0 || highlod_size <= 0)
425  {
426  error = "Missing high_lod block";
427  return false;
428  }
429 
430  bool haveprev = true;
431 
432  if (map.ContainsKey("medium_lod"))
433  {
434  tmpmap = (OSDMap)map["medium_lod"];
435  if (tmpmap.ContainsKey("size"))
436  medlod_size = tmpmap["size"].AsInteger();
437  else
438  haveprev = false;
439  }
440 
441  if (haveprev && map.ContainsKey("low_lod"))
442  {
443  tmpmap = (OSDMap)map["low_lod"];
444  if (tmpmap.ContainsKey("size"))
445  lowlod_size = tmpmap["size"].AsInteger();
446  else
447  haveprev = false;
448  }
449 
450  if (haveprev && map.ContainsKey("lowest_lod"))
451  {
452  tmpmap = (OSDMap)map["lowest_lod"];
453  if (tmpmap.ContainsKey("size"))
454  lowestlod_size = tmpmap["size"].AsInteger();
455  }
456 
457  if (map.ContainsKey("skin"))
458  {
459  tmpmap = (OSDMap)map["skin"];
460  if (tmpmap.ContainsKey("size"))
461  skin_size = tmpmap["size"].AsInteger();
462  }
463 
464  cost.highLODSize = highlod_size;
465  cost.medLODSize = medlod_size;
466  cost.lowLODSize = lowlod_size;
467  cost.lowestLODSize = lowestlod_size;
468 
469  submesh_offset = -1;
470 
471  tmpmap = null;
472  if(map.ContainsKey("physics_mesh"))
473  tmpmap = (OSDMap)map["physics_mesh"];
474  else if (map.ContainsKey("physics_shape")) // old naming
475  tmpmap = (OSDMap)map["physics_shape"];
476 
477  if(tmpmap != null)
478  {
479  if (tmpmap.ContainsKey("offset"))
480  submesh_offset = tmpmap["offset"].AsInteger() + start;
481  if (tmpmap.ContainsKey("size"))
482  physmesh_size = tmpmap["size"].AsInteger();
483 
484  if (submesh_offset >= 0 || physmesh_size > 0)
485  {
486 
487  if (!submesh(data, submesh_offset, physmesh_size, out phys_ntriangles))
488  {
489  error = "Model data parsing error";
490  return false;
491  }
492  }
493  }
494 
495  // upload is done in convex shape type so only one hull
496  phys_hullsvertices++;
497  cost.physicsCost = 0.04f * phys_hullsvertices;
498 
499  float sfee;
500 
501  sfee = data.Length; // start with total compressed data size
502 
503  // penalize lod meshs that should be more builder optimized
504  sfee += medSizeWth * medlod_size;
505  sfee += lowSizeWth * lowlod_size;
506  sfee += lowestSizeWth * lowlod_size;
507 
508  // physics
509  // favor potencial optimized meshs versus automatic decomposition
510  if (physmesh_size != 0)
511  sfee += physMeshSizeWth * (physmesh_size + hulls_size / 4); // reduce cost of mandatory convex hull
512  else
513  sfee += physHullSizeWth * hulls_size;
514 
515  // bytes to money
516  sfee *= bytecost;
517 
518  cost.costFee = sfee;
519  return true;
520  }
521 
522  // parses a LOD or physics mesh component
523  private bool submesh(byte[] data, int offset, int size, out int ntriangles)
524  {
525  ntriangles = 0;
526 
527  OSD decodedMeshOsd = new OSD();
528  byte[] meshBytes = new byte[size];
529  System.Buffer.BlockCopy(data, offset, meshBytes, 0, size);
530  try
531  {
532  using (MemoryStream inMs = new MemoryStream(meshBytes))
533  {
534  using (MemoryStream outMs = new MemoryStream())
535  {
536  using (ZOutputStream zOut = new ZOutputStream(outMs))
537  {
538  byte[] readBuffer = new byte[4096];
539  int readLen = 0;
540  while ((readLen = inMs.Read(readBuffer, 0, readBuffer.Length)) > 0)
541  {
542  zOut.Write(readBuffer, 0, readLen);
543  }
544  zOut.Flush();
545  outMs.Seek(0, SeekOrigin.Begin);
546 
547  byte[] decompressedBuf = outMs.GetBuffer();
548  decodedMeshOsd = OSDParser.DeserializeLLSDBinary(decompressedBuf);
549  }
550  }
551  }
552  }
553  catch
554  {
555  return false;
556  }
557 
558  OSDArray decodedMeshOsdArray = null;
559 
560  byte[] dummy;
561 
562  decodedMeshOsdArray = (OSDArray)decodedMeshOsd;
563  foreach (OSD subMeshOsd in decodedMeshOsdArray)
564  {
565  if (subMeshOsd is OSDMap)
566  {
567  OSDMap subtmpmap = (OSDMap)subMeshOsd;
568  if (subtmpmap.ContainsKey("NoGeometry") && ((OSDBoolean)subtmpmap["NoGeometry"]))
569  continue;
570 
571  if (!subtmpmap.ContainsKey("Position"))
572  return false;
573 
574  if (subtmpmap.ContainsKey("TriangleList"))
575  {
576  dummy = subtmpmap["TriangleList"].AsBinary();
577  ntriangles += dummy.Length / bytesPerCoord;
578  }
579  else
580  return false;
581  }
582  }
583 
584  return true;
585  }
586 
587  // parses convex hulls component
588  private bool hulls(byte[] data, int offset, int size, out int nvertices, out int nhulls)
589  {
590  nvertices = 0;
591  nhulls = 1;
592 
593  OSD decodedMeshOsd = new OSD();
594  byte[] meshBytes = new byte[size];
595  System.Buffer.BlockCopy(data, offset, meshBytes, 0, size);
596  try
597  {
598  using (MemoryStream inMs = new MemoryStream(meshBytes))
599  {
600  using (MemoryStream outMs = new MemoryStream())
601  {
602  using (ZOutputStream zOut = new ZOutputStream(outMs))
603  {
604  byte[] readBuffer = new byte[4096];
605  int readLen = 0;
606  while ((readLen = inMs.Read(readBuffer, 0, readBuffer.Length)) > 0)
607  {
608  zOut.Write(readBuffer, 0, readLen);
609  }
610  zOut.Flush();
611  outMs.Seek(0, SeekOrigin.Begin);
612 
613  byte[] decompressedBuf = outMs.GetBuffer();
614  decodedMeshOsd = OSDParser.DeserializeLLSDBinary(decompressedBuf);
615  }
616  }
617  }
618  }
619  catch
620  {
621  return false;
622  }
623 
624  OSDMap cmap = (OSDMap)decodedMeshOsd;
625  if (cmap == null)
626  return false;
627 
628  byte[] dummy;
629 
630  // must have one of this
631  if (cmap.ContainsKey("BoundingVerts"))
632  {
633  dummy = cmap["BoundingVerts"].AsBinary();
634  nvertices = dummy.Length / bytesPerCoord;
635  }
636  else
637  return false;
638 
639 /* upload is done with convex shape type
640  if (cmap.ContainsKey("HullList"))
641  {
642  dummy = cmap["HullList"].AsBinary();
643  nhulls += dummy.Length;
644  }
645 
646 
647  if (cmap.ContainsKey("Positions"))
648  {
649  dummy = cmap["Positions"].AsBinary();
650  nvertices = dummy.Length / bytesPerCoord;
651  }
652  */
653 
654  return true;
655  }
656 
657  // returns streaming cost from on mesh LODs sizes in curCost and square of prim size length
658  private float streamingCost(ameshCostParam curCost, float sqdiam)
659  {
660  // compute efective areas
661  float ma = 262144f;
662 
663  float mh = sqdiam * highLodFactor;
664  if (mh > ma)
665  mh = ma;
666  float mm = sqdiam * midLodFactor;
667  if (mm > ma)
668  mm = ma;
669 
670  float ml = sqdiam * lowLodFactor;
671  if (ml > ma)
672  ml = ma;
673 
674  float mlst = ma;
675 
676  mlst -= ml;
677  ml -= mm;
678  mm -= mh;
679 
680  if (mlst < 1.0f)
681  mlst = 1.0f;
682  if (ml < 1.0f)
683  ml = 1.0f;
684  if (mm < 1.0f)
685  mm = 1.0f;
686  if (mh < 1.0f)
687  mh = 1.0f;
688 
689  ma = mlst + ml + mm + mh;
690 
691  // get LODs compressed sizes
692  // giving 384 bytes bonus
693  int lst = curCost.lowestLODSize - 384;
694  int l = curCost.lowLODSize - 384;
695  int m = curCost.medLODSize - 384;
696  int h = curCost.highLODSize - 384;
697 
698  // use previus higher LOD size on missing ones
699  if (m <= 0)
700  m = h;
701  if (l <= 0)
702  l = m;
703  if (lst <= 0)
704  lst = l;
705 
706  // force minumum sizes
707  if (lst < 16)
708  lst = 16;
709  if (l < 16)
710  l = 16;
711  if (m < 16)
712  m = 16;
713  if (h < 16)
714  h = 16;
715 
716  // compute cost weighted by relative effective areas
717  float cost = (float)lst * mlst + (float)l * ml + (float)m * mm + (float)h * mh;
718  cost /= ma;
719 
720  cost *= 0.004f; // overall tunning parameter
721 
722  return cost;
723  }
724  }
725 }
OpenMetaverse.StructuredData.OSDMap OSDMap
Definition: MeshCost.cs:46
bool MeshModelCost(LLSDAssetResource resources, int basicCost, out int totalcost, LLSDAssetUploadResponseData meshcostdata, out string error, ref string warning)
Definition: MeshCost.cs:125
OpenMetaverse.StructuredData.OSD OSD
OpenMetaverse.StructuredData.OSDArray OSDArray
Definition: MeshCost.cs:45