User:LindaB Helendale/getMeshLODsize

From Second Life Wiki
Jump to navigation Jump to search

Script to find out the size of a mesh on all LOD levels, used in Land Impact calculation. (c) LindaB Helendale. Permission to use this script in any way granted.

After the triangle count estimation script there's also demo code to calculate the download/streaming cost for any mesh radius, given the triangle counts.

<lsl> /******************************************************************************** Script to find out the size of a mesh on all LOD levels, used in Land Impact calculation. (c) LindaB Helendale. Permission to use this script in any way granted.

The size (and thus LI download/streaming cost) depends on the number of vertices and number of triangles. (Though for typical mesh they correlate heavily with two triangles per vertex). The mesh size depends also on the UV map, and maybe on some other attributes. In addition there's gzip compression in storing the mesh data.

This script can be used to measure the effect of various mesh properties and attributes on the size that's used in Land Impact calculation.

Usage:

Drop this script in the (non-linked) mesh and touch. The script prints the triangles values for high, mid, low and lowest LOD that are used in the Land Impact Download cost equations.


How it works:

See http://wiki.secondlife.com/wiki/Mesh/Mesh_Streaming_Cost for the equations. The Streaming Cost / Downdload cost is weighted sum of the mesh size in asset server on each LOD, weighted by the area where the LOD is visible. The weights are variables 'high_area', 'mid_area', 'low_area' and 'lowest_area' in the algorithm on the wiki page. See http://community.secondlife.com/t5/Mesh/Download-weight-and-size-Some-graphs/m-p/1058609#M5642 for graphs of the weights as function of mesh radius.

For a set of radii, the Streaming Cost is computed from a matrix containing the LOD weights as columns multiplied by the triangles vector. The columns are the LOD curves in Drongle's graph image, sampled at the radius values.

         | high_area(r0) mid_area(r0) low_area(r0) lowest_area(r0) |    | tri_high  |
         | high_area(r1) mid_area(r1) low_area(r1) lowest_area(r1) | *  | tri_mid   |
         | ...                                                     |    | tri_low   |
         | high_area(rn) mid_area(rn) low_area(rn) lowest_area(rn) |    | tri_loest |

The result is multiplied by 15000 and divided by MeshTriangleBudget (250000) to give the LI. The byte size can be calculated from the triangle count using the equations

   F32 triangles_low = llmax((F32) bytes_low-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;

with MeshBytesPerTriangle=16 and MeshMetaDataDiscount=384 (see viewer debug settings for the values) but for comparing the LOD sizes the triangle count is what matters.

To estimate the LOD mesh sizes we invert the weight matrix W,

   LI=W*tri => tri=inv(W)*LI

assuming we have 4 radii so W is a square matrix. (For more than 4 radii, use pseudo-inverse to get least squares fitting tri=inv(W'*W)*W'*LI; but with properly chosen radii four samples is sufficient to find the triangle counts.)

The most informative radii are those when each LOD becomes irrelevant (that is, the next higher LOD visibility range reaches the max_area, to be visible in the whole sim.) The LOD is capped when (r/LODdivider)^2*PI=max_area, where LODdivider is [0.24 0.06 0.03] and max_area is 102932 m^2, thus the radii are

   float X=llSqrt(102932.0/PI);
   list radius=[X*0.24, X*0.06, X*0.03, 0.01]; 

The last one is a small mesh, where the lowest LOD has the largest effect. The weight matrix W with these four radii is

   1.0000    0.0000    0.0000    0.0000
   0.0625    0.9375    0.0000    0.0000
   0.0156    0.2344    0.7500    0.0000
   0.0000    0.0000    0.0000    1.0000

and the streaming cost is LI=15000/250000 * W*triangles. The inverse matrix inv(W) is

   1.0000    0.0000    0.0000    0.0000
  -0.0667    1.0667    0.0000    0.0000
   0.0000   -0.3333    1.3334    0.0000
   0.0000    0.0000    0.0000    1.0000

and triangles=inv(W)*LI * 250/15, which is implemented below.

                                                                                                                                                              • /


float getObjectStreamingCost() {

   return llList2Float(llGetObjectDetails(llGetKey(),[OBJECT_STREAMING_COST]),0);

}

float getRadius() {

   return llVecMag(llGetScale())/2.0 ;

}

setRadius(float R) {

   llSetScale(llGetScale()*R/getRadius());
   if (llFabs(getRadius()-R)>0.01)
       llOwnerSay("Warning: failed to set radius to " + (string)R + " The result was " + (string)getRadius());

}

list getLODtriangles() {

   // Measure the triangle count for mesh LODs used in calculating the Streaming Cost/Download Cost
   // component of the mesh Land Impact. Permission to use this script in any way granted.
   // (c) LindaB Helendale
   vector S=llGetScale();
   llSetScale(<1,1,1>); // we set equal scales to make sure the biggest radius is reached
   float MeshTriangleBudgetScaler=50.0/3.0; // = MeshTriangleBudget(250000)/15000
   list radius=[43.4422,   10.8605,    5.43027,    0.01];    
   list C;
   integer i;
   for(i=0;i<4;i++) {
       setRadius(llList2Float(radius,i));
       C += getObjectStreamingCost() * MeshTriangleBudgetScaler;
   }
   list tri;
   tri = [ llList2Float(C,0), 
           16.0/15.0*llList2Float(C,1) - 1.0/15.0*llList2Float(C,0),
             4.0/3.0*llList2Float(C,2) -  1.0/3.0*llList2Float(C,1),
           llList2Float(C,3)
         ];
   llSetScale(S);
   return tri;

}

default {

   touch_start(integer p) {
       if (llDetectedKey(0)!=llGetOwner()) return;
       list tri=getLODtriangles();
       llOwnerSay("LOD triangles:  " + llDumpList2String(tri," "));
   }

} </lsl>


Given the triangle counts, the following demo code shows how to calculate the download cost part of the land impact for any mesh radius

<lsl>

   // store the triangle count in list TRIANGLES, for example by 
   //   list TRIANGLES=getLODtriangles();
   float r=getRadius(); // or use user input
   float DLcost_hat=0; // estimate of the DLcost
   integer i;
   list W=LODweigths(r);
   string txt;
   for(i=0;i<4;i++) {
       float li = llList2Float(TRIANGLES,i)*llList2Float(W,i);
       DLcost_hat += li * 3.0/50.0;   // = 15000/MeshTriangleBudget
       txt += "LOD " + (string)i + " : " + (string)li + "    ";
   }
 
   // get the actual value for comparison
   float DLcost=getObjectStreamingCost();
   llOwnerSay("Download cost= " + (string)DLcost + "  Calculated= " + (string)DLcost_hat + "  Error= " + (string)(DLcost-DLcost_hat )); 
   llOwnerSay(txt);

</lsl>

The LODweigths routine as imported from https://wiki.secondlife.com/wiki/Mesh/Mesh_Streaming_Cost

<lsl> list LODweigths(float r) {

float max_distance = 512;
float dlowest = min(r/0.03, max_distance);
float dlow = min(r/0.06, max_distance);
float dmid = min(r/0.24, max_distance);
   
float max_area = 102932;
float min_area = 1;
float high_area = min(PI*dmid*dmid, max_area);
float mid_area = min(PI*dlow*dlow, max_area);
float low_area = min(PI*dlowest*dlowest, max_area);
float lowest_area = max_area;
   lowest_area = lowest_area - low_area;
   low_area = low_area - mid_area;
   mid_area = mid_area - high_area;
   high_area = llclamp(high_area, min_area, max_area);
   mid_area = llclamp(mid_area, min_area, max_area);
   low_area = llclamp(low_area, min_area, max_area);
   lowest_area = llclamp(lowest_area, min_area, max_area);
   float total_area = high_area + mid_area + low_area + lowest_area;
   high_area = high_area / total_area;
   mid_area = mid_area / total_area;
   low_area = low_area / total_area;
   lowest_area = lowest_area / total_area;
 list weights=[high_area, mid_area, low_area, lowest_area];
 return weights ;

}

float min(float a, float b) {

   if (a<b) return a;
   else     return b;

} float llclamp(float b,float bmin,float bmax) {

   if (b<bmin) b=bmin;
   if (b>bmax) b=bmax;
   return b;    

} </lsl>