PBR Material/Trimsheet Server

PBR Material/Trimsheet Server
Keep a library of unique shared materials used in your region to keep VRAM down.
⚠️
No Support Provided
Please do not contact me for support of feature requests regarding these scripts. They're provided as-is only.

If you are a sim builder, trim-sheets and material libraries are an excellent way to save memory and increase the amount of detail in your region. Here is an excellent  document about trimsheets that is worth reading if you are a creator. I also wrote about my own Trimsheet Creation Workflow here.

The Problem

You're a region builder and make and texture all your own mesh assets, which share materials/trimsheets to save memory. However, content creation is never truly finished and sometimes you want to update a trim-sheet after the fact, add new details to it, make color adjustments etc.

It can be very difficult to make sure that every object in the region has the new updated trimsheet. Espescially if you are a region builder who makes lots and lots of assets. If you miss even one object, you now use double the texture memory for that trim, and it is very difficult to tell if you have made that mistake.

Trimsheet Server Solution

I created a simple Trimsheet Server system that automatically keeps all objects in the region up to date with the latest materials.

  1. Use one prim in your region as the server, and keep all of the materials used in your region inside it.
  2. Place a receiver inside each object you wish to receive material updates in.

All objects in your region will automatically update with the new material, whenever you replace the material inside the server. The server will also warn you if a material is missing.

Server

Place this script inside a single server prim in your region. You should only have one server prim.

Put all materials you create inside your server prims inventory. If you later want to update a material, make sure to first delete the old material from the server prim, before adding the new one to the inventory.

integer CHANNEL_MATERIAL_TRIMS = -3300009;


send_material_key(string material_name) {
    if (llGetInventoryType(material_name) != INVENTORY_MATERIAL) {
        llInstantMessage(llGetOwner(), material_name + " is missing!");
        return;
    }
    key material_key = llGetInventoryKey( material_name );
    string json = llList2Json(JSON_OBJECT, [
        "action", "update material",
        "material_name", material_name,
        "material_key", material_key
    ]);
    llRegionSay(CHANNEL_MATERIAL_TRIMS, json);
}

default
{
    state_entry()
    {
        llListen(CHANNEL_MATERIAL_TRIMS, "", NULL_KEY, "");
    }

    listen(integer channel, string name, key id, string message)
    {
        if (llJsonGetValue(message, ["action"]) == "get_material_uuid") {
            string material_name = llJsonGetValue(message, ["material_name"]);
            send_material_key(material_name);
        }
    }

    changed(integer change)
    {

        if (change & CHANGED_INVENTORY) {
            integer i = 0;
            integer len = llGetInventoryNumber(INVENTORY_MATERIAL);
            while (i  < len) {
                string material_name = llGetInventoryName(INVENTORY_MATERIAL, i);
                send_material_key(material_name);
                llSleep(0.2);
                ++i;
            }
        }
    }
}
Receiver

Goes in each object that should receive materials.

  1. In each object, edit the materials_faces list to indicate which materials are applied where.
  2. On each line, put the material name, followed by the link number (or LINK_THIS) and finally the face number.

    Eg. "cobblestone", LINK_SET, ALL_SIDES to apply to all faces on all objects, or "woodplanks", 2, 0" to apply to link number 2 on face 0.
integer CHANNEL_MATERIAL_TRIMS = -3300009;

// ATTENTION - EDIT THIS LIST
// material name, link number, face number.
list material_faces = [
    "TilesCladding2", LINK_THIS, 5,
    "brushed_iron_aluminium_anodized", LINK_THIS, 4
];

// string PBR_FALLBACK = "35d92820-3ab8-b6a4-71b3-4233abbab706";

update_material(string message) {
    string material_name = llJsonGetValue(message, ["material_name"]);
    string material_key  = llJsonGetValue(message, ["material_key"]);
    integer i = 0;
    integer len = llGetListLength(material_faces);
    while ( i < len ) { 
        if (llList2String(material_faces, i) == material_name) {
            integer link_number = llList2Integer(material_faces, i + 1);
            integer face_number = llList2Integer(material_faces, i + 2);
            llSetLinkPrimitiveParamsFast(link_number, [
            PRIM_RENDER_MATERIAL, face_number, material_key
            // Uncomment the following line if you want a fallback texture applied.
            // ,PRIM_TEXTURE, face_number, PBR_FALLBACK, <4,4,4>,ZERO_VECTOR, 0.0
            ]);
        }
        i += 3;
    }
}

default
{
    state_entry()
    {
        llListen(CHANNEL_MATERIAL_TRIMS, "", NULL_KEY, "");
        integer i = 0;
        integer len = llGetListLength(material_faces);
        while (i < len) {
            string material_name = llList2String(material_faces, i);
            string json = llList2Json(JSON_OBJECT, [
                "action", "get_material_uuid",
                "material_name", material_name
            ]);    
            llRegionSay(CHANNEL_MATERIAL_TRIMS, json);
            i += 3;
        }
    }

    listen(integer channel, string name, key id, string message)
    {
        if (llGetOwnerKey(id) != llGetOwner()) return;
        if (llJsonGetValue(message, ["action"]) == "update material") {
            update_material(message);
        }
    }
}

Important Notes

  • I recommend changing the channel -3300009 to something unique for your region in both scripts.
  • Objects owned by other people cannot change trims in your region. You must be the owner of the object sending the materials.
  • Materials are not encrypted in transit. Anybody who knows (or guesses) your channel number can use your materials and apply them to their own object.

MIT License

Copyright (c) 2023 Ai (extrude.ragu)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.