Monday, 26 October 2015

TUTORIAL: Gothic Double-Vaulted Ceiling Using the Boolean Modifier

Last night I posted some pictures of a work in progress to my G+ account and was greeted with some very favorable responses (thanks!) but I got the sense that a few people were thinking "gosh, I wish I could do that" and I'm happy to tell you that it's extremely easy. Here's a reference picture to show you the type of ceiling I'm talking about. It's impossible to do this with prims but easy to do it with mesh.
All that's really involved in the classic gothic ceiling is two cylinders, intersecting and then being repeated over and over again to cover the required area. Then plop down some columns to support it all and you're done.

I just make one ceiling section and one column support, import those, then use them as my building blocks. That keeps the mesh data to a minimum, making it much faster to send to the visitor. You can even make the columns using prims if you prefer, so the only thing you can't do in-world is make the ceiling. This mini-tutorial will show you how it's done in Blender and is suitable even for beginner level users. It should take less than half an hour to complete.

Start by adding a mesh cylinder. In most cases 32 vertices is fine, but if you want to you could bump the count up to any even number that is a multiple of 4. Going beyond 48 would be excessive unless you plan to scale it very, very large (which was never the case in real life because the whole point of vaulted ceilings was a lack of technology and building materials that wouldn't collapse if you tried to get them to cover huge areas without support).

For this one I use a 1.5m radius (hence a 3m diameter) and 4m length. The critical thing is the length must be at least as great as the diameter and no more than double the diameter. It's the ratio of the two that determines the overall look of it and is worth experimenting with. The closer the length is to the diameter, the smaller the column portion and intervening joiner.

Don't fill the end caps and rotate it by 90 degrees on the x-axis or y-axis so it's lying on its side.
 
 On the Tools tab of the toolshelf, click the "Smooth" shading button to give it smooth normals. Then enter Edit mode and with all faces selected invert the normals using the menu Mesh > Normals > Flip Normals. That causes the faces to be oriented towards the inside which is where we want them, rather than to the outside. Then return to Object mode.
Now duplicate the cylinder and rotate it by 90 degrees on the z-axis using the hotkey combination of: shift+D R Z 90 (duplicate > rotate > z-axis > 90 degrees) and press enter.

That will now give you a pair of intersecting cylinders where the upper halves of both form the classic Gothic vault shape. All we need to do is get rid of the overlapping parts which is easy using the Boolean modifier.

If you've never used the Boolean modifier I suggest hiding the duplicated cylinder so you can see what's happening as you apply the modifier to the original object (select it and hotkey H or click the little visibility eyeball in the Outliner pane).

Now select your original cylinder and add a Boolean modifier to it (you will find it in the "Generate" class of modifiers). When you first do so it will highlight the name in red because Boolean requires a second object to be defined for the operation.

Click the "Object" box to get a drop-down of other scene objects, and since our only other object is the duplicate we made a moment ago, it will be the only one in the list.

By default the modifier should select the "Intersect" operation which is the one we want in this case. If it doesn't look like the accompanying picture for this step you probably skipped the step, above, to flip the normals so Blender is interpreting the operation differently. If that's the case, either go back and flip normals for both of the cylinders, or set it to "Union" operation and then flip normals later.

As you can see, this modifier produces almost the exact shape we want (once we cut the bottom half off) but there are a few other things we need to take care of as well.

Normally I prefer to wait until the very last possible moment before applying modifiers, but in this case we need to work with the post-Boolean object so go ahead and click the modifier's "Apply" button.

Now un-hide your duplicate cylinder and you'll notice that it's completely unaffected...its only purpose was to provide the secondary object for the Boolean modifier. Since we don't need it any longer we can delete it.

Now switch into edit mode where we first need to do a little clean-up since the Boolean modifier often leave orphaned vertices that don't cause problems in Blender but will usually make a mesh refuse to up-load into Opensim. Select all, then click the "Remove Doubles" button (on the "Tools" tab of the toolshelf) to get rid of them.

Using edge select mode, select the diagonal edge loops and the four cylinder ends. Mark those edges as both sharp and as seams. We want them sharp so they won't distort the normals of the ceiling faces when they're smoothed, and we need them as seams for our UV mapping.

Next, select all the faces of the bottom half of the object and delete them, leaving us with only the final shape we're interested in.

Now select all faces and UV map them using the Unwrap method. If you plan to make your textures using Blender's procedural methods you can work with them from this point however you like.



I prefer to give myself more flexibility and a lot more versatility but arranging my UV map to facilitate using seamless textures with any tiling I need.

To do that you need to arrange the resulting map to have all four islands superimposed and aligned along one of the map edges. The seams will now match perfectly on adjoining pieces when you build your ceiling as long as you tile in whole number increments.

Our final step before export is to add an Edge Split modifier which will break the edges we've marked as sharp. This increases the vertex count but makes the normals of the surface look correct and it doesn't change the face count. I also add a Triangulate modifier set to "Beauty" although with this object there's almost no visible difference between that and the "Shortest Diagonal" method.

You can now export this model to dae and import it in-world to use as a building block for your own classic Gothic vaulted ceiling. Notice that the finished object is a mere 128 triangles which is significantly less than even a prim cylinder. don't forget to apply rotation and scale before you export.

The advantage of the building block method is how rapidly the geometry can be sent to the visitor's viewer and the very low poly count which will make it much more gentle for underpowered graphics cards to display. Using the seamless UV map approach also means we can use any seamless texture we want on it...brick, tile, stucco, paint, marble, or anything else that fits the need.

Because it's a single object, it can also be scaled in any dimension you like. For my sample image at the beginning of this post, I have them scaled to only 1m height around the edges and 2m height for the center ones. Of course this meant I had to make end caps for the transition pieces but that's very easy to do from this point of the model. I made matching mesh columns for mine but you can use prims if you prefer. They just need to be the appropriate size for the arch portions to rest on.

Me standing under a 3x3 array of them before I add columns
If you want to get even fancier, you can make additional ornamental pieces to fit the arching and connections simply by selecting the loops, duplicate to a separate mesh, convert to curve, then set whatever parameters look best. Those will then require conversion back to mesh, then mapping, and probably a separate material and texture. Of course that can increase the poly count rather dramatically if you start to get very ornate.

Wednesday, 21 October 2015

RELEASE: Venus Series Grows Again and New Art Added

This morning I added another half-dozen pieces to the Venus series of statues at Paramour Shopping. The are initially placed in the "Art Building" -- the western of the three pyramid structures - and will remain there until my next statue release, at which point they'll be moved out onto the region's landscape (which is where you'll now find my previous set of releases).

Venus 55

Venus 56

Venus 57

Venus 58

Venus 59

Venus 60

I also added 22 new paintings to the Art Masters set which are available from the vendor in the same building. That brings the entire collection to nearly 200 works of art from various famous (and not so famous) artists.

As always, it's all free and the statues can be scaled and textured any way you wish, including removing them from their plinths to use elsewhere.

HGTP to RefugeGrid.com:8002 then take the local portal to Paramour Shopping.

Saturday, 17 October 2015

BUGFIX: Paramour Tanning Bed

It's with considerable embarrassment that I discovered that the script in the recently released Paramour Tanning Bed has a bug in it that will cause it to fail to display animations correctly if you're currently using an AO that has a sit with animation priority 4 or higher.

I've fixed that in the script, now, but anyone who picked up a copy prior to now (Saturday at 11am grid time) will either need to come grab a replacement copy or you can simply fix the script yourself.

To fix it yourself, open the script and scroll down to line 301 where it says:
while (--stop>-0) { if (llList2Key(anToStop,stop)!=dontStop) osAvatarStopAnimation(user,llList2String(anToStop,stop)); }

The line should read:
while (--stop>=0) { if (llList2Key(anToStop,stop)!=dontStop) osAvatarStopAnimation(user,llList2String(anToStop,stop)); }

[so the condition  is supposed to be --stop>=0 not --stop>-0]

My sincere apologies for the error.

Thursday, 15 October 2015

RELEASE: Paramour Tanning Bed

Tired of those unsightly tan lines? You need the newly released Paramour tanning bed!

When open, lighting system is off and script is idle


This is a sleek, modern-looking mesh tanning bed with a lid that closes when you sit on it and fully customizable lighting effects controlled by a single, ultra-low-low load, custom script -- exactly what you'd expect from any Paramour product -- that also handles your tanning animations. You will need OSSL functions enabled in the region. The script uses no sim resources when idle, and when active uses less than 2ms of frame time per 30 seconds.
Cyan light preset - tanning on back
The bed comes with two very basic animations (because I'm not very good at making them) for tanning on your stomach or back; but you can easily replace these with any other animation(s) you want (with a few minor technical limitations and minimal effort). If more than 1 animation is in the bed's inventory it will cycle through them based on a user-configurable timer.
Violet light preset - tanning on front

There are 4 preset lighting options: cyan, violet, sodium or white and you can choose your lighting during use simply by touching the bed and picking the one you want. You can add more if you like, up to a maximum of 10 different colour options. Only the current user can change colour.


The Paramour Tanning Bed is full perm to allow you to texture it any way you want, and you can tweak or change the lighting effects via user settings at the top of the script (instructions are in the script). DO NOT scale it unless you're prepared to do additional custom set-up work to adjust for the new positions required for the lid, rod and piston. Maybe one day Opensim will allow objects to have armatures and that will no longer be necessary.
End view with sodium light preset and tanning on front

The bed, script and animations are all released under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license and, as usual, it's completely free!

You will find it in the central pyramid building at Paramour Shopping (HGTP to RefugeGrid.com:8002 then take the local portal to Paramour Shopping) near the dance animations.

Saturday, 10 October 2015

RELEASE: PMAC 1.x Cleaning Utility

I've been doing some work on a very large PMAC item (18+ menus and close to 500 animations) and lost track of which of the huge number of animations in the object's inventory were actually being used so I wrote a little utility to help me and thought it might be useful to others who are working with the system.

It's a relatively simple script that analyzes all menu notecards in inventory, checks to make sure the animations they use are actually in PMAC's inventory, and then checks to see if there are animations in inventory that aren't used by any of your menus.

You can then ask it to supply a list of missing animations (it will tell you wish menu calls for it) or a list of the excess ones, or you can have it go ahead and delete all the excess ones for you.

Caution: if you use a custom add-on that calls for animations that aren't used in the menus, or there's any other scripts in the object that use animations, this utility won't catch that and would consider those as excess animations. You'd need to modify the script to handle those cases.

Just copy-paste the script from below and follow the instructions.

************** COPY EVERYTHING BELOW HERE *****************

// PMAC 1.x Cleaning Utility
// by Aine Caoimhe Oct 2015
// Release CC0 - full permission to do anything you like with it
//
// INSTRUCTIONS:
// 1. Create a new script in your PMAC object's inventory and paste this script into it.
// 2. Change the name of the base animation in the next line if you aren't using PMAC's default one
string baseAnimation="~~~~~base_DO_NOT_DELETE_ME!!!!!";
// 3. Decide whether to run in debug mode by setting the following line to either TRUE or FALSE
integer debug=FALSE; // TRUE will prevent auto-deleting of this script when you finish, FALSE will not
// 4. Save.
// 5. Normally this will auto-compile and run the script which triggers a dialog menu for you
// 6. Click the "ANALYSE" button to analyze the object
// 7. You'll see the progress of analysis in chat (which can take a little while if you have a system with tons of menus and animations)
// 8. You'll then be given a report of the analysis results.
// 9. If the utility found any animations that are called for by a menu but aren't in inventory, you can click the "MISSING" button to be given a list of them (in chat)
// 10. If the utility found any animations in inventory that aren't used by any of your menus you will have two additional buttons: "EXCESS" and "CLEAN"
//      If you click "EXCESS" the utility will give you a list of them (in chat) but not do anything about them
//      If you click "CLEAN" the utility will remove them for you (if you want a list of them, click "EXCESS" first, before you click this one)
// 11. You can re-analyze any time if you like.
// 12. Clicking "DONE" will exit the utility with no further changes and the utility script will delete itself from inventory (unless debug is enabled, in which case it will still be there and will initialize itself any time you reset the script)
// 13. If you fail to respond to a dialog within 5 minutes the script will auto-delete itself (unless in debug mode).
//
// This script doesn't check for any animation (gesture?) that might be called by an add-on or other script in the object so don't use the auto-clean unless you're sure
// nothing else in the object uses an animation.
//
// This script doesn't check for objects required by add-ons, nor does it do any other error-checking (it doesn't look for duplicate menu names, duplicate animation
// names, or anything else. This just strictly a "clean things up" utility. Of course you could modify the script to do any additional checking you want.
//
//
// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING!
integer myChannel;  // listens to a channel based on Owner UUID
integer handle;
list invAnims;
list invMenus;
list missing;
list found;
list excess;

showMain()
{
    string txtDia="Please click \"ANALYZE\" to have the PMAC Cleaner Utility examine your menus and check them against the animations in inventory.\n\nClicking \"DONE\" will abort the operation and remove the cleaner script";
    list butDia=["ANALYZE","DONE"];
    llDialog(llGetOwner(),txtDia,butDia,myChannel);
    llSetTimerEvent(300.0);
}
showResults()
{
    string txtDia="ANALYSIS RESULTS";
    txtDia+="\n* * * * * * * * * * * * * * * * * * * * * *";
    txtDia+="\nMenu notecards found: "+(string)llGetListLength(invMenus);
    txtDia+="\nAnimations needed and found: "+(string)llGetListLength(found);
    txtDia+="\nAnimations needed but missing: "+(string)(llGetListLength(missing)/2);
    txtDia+="\nAnimations found in inventory: "+(string)llGetListLength(invAnims);
    txtDia+="\nExcess (unused) animations: "+(string)llGetListLength(excess);
    txtDia+="\n* * * * * * * * * * * * * * * * * * * * * *";
    list butDia;
    if (llGetListLength(missing)>0)
    {
        txtDia+="\nPress \"MISSING\" to be given a list of missing animations";
        butDia=[]+butDia+["MISSING"];
    }
    if (llGetListLength(excess)>0)
    {
        txtDia+="\nPress \"EXCESS\" to be given a list of excess animations that can be safely deleted";
        txtDia+="\nPress \"CLEAN\" to have excess animations automatically deleted for you";
        butDia=[]+butDia+["EXCESS","CLEAN"];
    }
    txtDia+="\nPress \"ANALYZE\" to re-analyze this object";
    txtDia+="\nPress \"DONE\" to exit utility and delete the script";
    butDia=[]+butDia+["ANALYZE","DONE"];
    llDialog(llGetOwner(),txtDia,butDia,myChannel);
    llSetTimerEvent(300.0);
}
doAnalyze()
{
    llSetTimerEvent(0.0);
    buildInventoryLists();
    if (llGetListLength(invAnims)<1)
    {
        llOwnerSay("ERROR! Unable to find any animations in inventory!");
        killMe();
    }
    if (llGetListLength(invMenus)<1)
    {
        llOwnerSay("ERROR! Unable to find any menu notecards!");
        killMe();
    }
    found=[];
    missing=[];
    if (llListFindList(invAnims,[baseAnimation])==-1) missing=[]+missing+["BASE ANIMATION",baseAnimation];
    else found=[]+found+[baseAnimation];
    integer menu;
    integer stop=llGetListLength(invMenus);
    while (menu<stop)
    {
        string thisMenu=llList2String(invMenus,menu);
        llOwnerSay("Analyzing...checking "+thisMenu+"........");
        integer sitters=(integer)llGetSubString(thisMenu,7,7);
        list data=llParseString2List(osGetNotecard(thisMenu),["|","\n"],[]);
        integer line;
        integer end=llGetListLength(data);
        while (line<end)
        {
            integer thisSitter;
            while (thisSitter<sitters)
            {
                string animNeeded=llList2String(data,line+2+thisSitter*3);
                if (llListFindList(invAnims,[animNeeded])==-1) missing=[]+missing+[thisMenu,animNeeded];
                else if (llListFindList(found,[animNeeded])==-1) found=[]+found+[animNeeded];
                // else it's already in our list of found ones that we need
                thisSitter++;
            }
            line+=2+sitters*3;
        }
        menu++;
    }
    // found is now a complete list of required animations so compare actual inventory to it
    excess=[];
    integer a=llGetListLength(invAnims);
    while (--a>=0)
    {
        if (llListFindList(found,llList2List(invAnims,a,a))==-1) excess=[]+excess+[llList2String(invAnims,a)];
    }
    excess=[]+llListSort(excess,1,TRUE);
    showResults();
}
buildInventoryLists()
{
    invAnims=[];
    invMenus=[];
    llOwnerSay("Analyzing.....building animation list.....");
    integer i=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--i>=0) { invAnims=[]+[llGetInventoryName(INVENTORY_ANIMATION,i)]+invAnims;}
    llOwnerSay("Analyzing.....building menus list.....");
    i=llGetInventoryNumber(INVENTORY_NOTECARD);
    while (--i>=0)
    {
        string name=llGetInventoryName(INVENTORY_NOTECARD,i);
        if (llSubStringIndex(name,".menu")==0) invMenus=[]+[name]+invMenus;
    }
}
killMe()
{
    llSetTimerEvent(0.0);
    llListenRemove(handle);
    if (!debug)
    {
        llRemoveInventory(llGetScriptName());
        llOwnerSay("PMAC Cleaner Utility script deleted from inventory");
    }
    else llOwnerSay("PMAC Cleaner Utility script is currently in debug mode...timer and listener have been removed but the utility hasn't.");
}
default
{
    state_entry()
    {
        myChannel=0x80000000|(integer)("0x"+(string)llGetOwner());
        handle=llListen(myChannel,"",llGetOwner(),"");
        // should be impossible for the owner not to be present but just in case...
        if (llGetAgentSize(llGetOwner())!=ZERO_VECTOR) showMain();
        else if (!debug) killMe();
    }
    timer()
    {
        llOwnerSay("Dialog timed out.");
        killMe();
    }
    listen(integer channel, string name, key who, string message)
    {
        if (message=="DONE") killMe();
        else if (message=="ANALYZE") doAnalyze();
        else if (message=="MISSING")
        {
            llSetTimerEvent(0.0);
            string txtToSay="List of missing animations:";
            integer a;
            integer stop=llGetListLength(missing);
            while (a<stop)
            {
                if (llStringLength(txtToSay)>950) // avoid exceeding max chat string length
                {
                    llOwnerSay(txtToSay);
                    txtToSay="List of missing animations (continued)";
                    llSleep(0.1);
                }
                txtToSay+="\n"+llList2String(missing,a+1)+" is needed by menu "+llList2String(missing,a);
                a+=2;
            }
            llOwnerSay(txtToSay);
            showResults();
        }
        else if (message=="EXCESS")
        {
            llSetTimerEvent(0.0);
            string txtToSay="List of excess animations:";
            integer a;
            integer stop=llGetListLength(excess);
            while (a<stop)
            {
                if (llStringLength(txtToSay)>950) // avoid exceeding max chat string length
                {
                    llOwnerSay(txtToSay);
                    txtToSay="List of excess animations (continued)";
                    llSleep(0.1);
                }
                txtToSay+="\n"+llList2String(excess,a);
                a++;
            }
            llOwnerSay(txtToSay);
            showResults();
        }
        else if (message=="CLEAN")
        {
            llSetTimerEvent(0.0);
            llOwnerSay("Removing excess animations...please wait");
            integer a=llGetListLength(excess);
            while (--a>=0)
            {
                llRemoveInventory(llList2String(excess,a));
            }
            excess=[];
            llOwnerSay("Cleaning complete");
            showResults();
        }
    }

Friday, 9 October 2015

RELEASE: Venus Series Grows Yet Again

Today I've added another nine statues to the "Venus" series, available in my Paramour Shopping region in RefugeGrid, bringing the entire set to more than 50.

You'll find the newly added pieces inside the eastern of the three pyramids (where the fine are paintings are displayed) for the next few days. They're remain there until I next release a new group of pieces, at which time I'll be distributing them throughout the region landscape.

As usual, they're all released under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license with full perms to allow you to texture and scale them any way you like.

To visit: HGTP to "RefugeGrid.com:8002:Paramour Shopping"

Venus 46
Venus 47
Venus 48
Venus 49
Venus 50
Venus 51
Venus 52
Venus 53
Venus 54

Monday, 5 October 2015

RELEASE: A Dozen New Statues

I'm not quite sure what it was about last weekend that got my creative juices were flowing, but by the end if it I was surprised to find that I'd completed a full dozen new statues that are now available in my art shop/gallery region (RefugeGrid.com:8002:Paramour Shopping).

Two are new additions to my "The Dancer" series -- inspired by modern dance photography -- which you'll find on the roof of the Roman Bath building.


The other ten further expand the "Venus" collection and have been scattered along the western side of the region, beginning near the back of Val's art exhibition building and continuing down the western bank of the river. One of the them ("Venus 45") is statue version of the Blender test render I posted on Saturday.







Friday, 2 October 2015

RELEASE: New Statues in the "Venus" Series

This afternoon I've added seven and a half new statues to the Venus series of figures, all of which are available at my Paramour Shopping region. Yep, you read that right. One of them has two versions -- one where she's wearing a nightgown and one without -- hence the extra "half". Those of you who follow me on G+ may have seen the Blender render I made of it the other day using skin and fabric textures instead of marble.

I placed almost all of the new pieces at the Temple to Cytheria structure (the Grecian one near the region's landing zone) to fill in the remaining gaps between its columns. The "dressed" Venus is on the path just south of that (near the plinths) and the "regular" version is at the bottom of the hill, near the Fine Art Paintings building, facing across the stream toward a willow tree.

To visit: HGTP to Refugegrid.com:8002:Paramour Shopping

Venus 29

Venus 32

Venus 33

Venus 26

Venus 25

Venus 28

Venus 34

Into The Dream (Venus 34)
In case anyone was wondering about the non-sequential numbering of the Venus series, there is actually a reason for it that relates to the photographic references I'm using to create the poses.