Game Development Community

dev|Pro Game Development Curriculum

Dynamic snow accumulation using Sahara for Torque 3D 1.1

by Konrad Kiss · 09/11/2011 (3:55 pm) · 36 comments

This is a resource that allows you to dynamically change the accumulation of snow on mission objects depending on the weather. It requires Sahara for Torque 3D 1.1.

This resource is not functional by itself! You need Sahara for Torque 3D 1.1 to be able to follow this tutorial.

Of course, this resource is a shameless plug! :) But it's also a nice resource requested by Sahara licensees. It also has many pretty pictures and a video.

There's now a Torque 3D 1.1 Final compatible version along with a price drop to $34.95 for the Indie license. Check out the manual, the discussion thread and the website for more information about Sahara.


Let's get down to business then...

What I'm trying to achieve here is having warmer objects melt snow on them while it is calm in my scene, but gather some snow while it is snowing.

I chose the built-in Deathball Desert mission to get started.

pub.bitgap.com/s1101.jpg

First of all, I changed the environment and the terrain to a winter theme, so my demo object - the Cheetah - would fit the surroundings better.

pub.bitgap.com/s1102.jpg

I already had Sahara installed, so I started out with creating a new material that I would be able to use as a basis for other snowy materials:

singleton Material(SnowyMaterial)
{
   superClass = "SnowyMaterial";

   accuMap[0] = "art/terrains/winter/Snow_05.jpg";
   accuStrength[0] = "0.862745";
   accuCoverage[0] = "1.56863";
   accuSpecular[0] = "2";

   materialTag0 = "Vegetation";
};

Replace that snowy texture with yours, of course.

Then I made every material that I wanted to have snow on them be descended from this material. This was useful, because this way I only had to set up Sahara's accu* parameters on a single Material. Ie.:

singleton Material(Cheetah_Body_Cheetah : SnowyMaterial)
...

pub.bitgap.com/s1103.jpg

Now I had all my objects covered in snow. I created a new client side script file, scripts/client/saharaSnow.cs and added the following (see the code for comments):

/// A global that controls snowing and snow accumulation
$isSnowing = false;

/// Material method that adds a bit more snow
function SnowyMaterial::moreSnow(%this)
{
   // a bit more snow
   %this.accuStrength = %this.accuStrength + 0.01;
   %this.reload();
}

/// Material method that melts some snow
function SnowyMaterial::lessSnow(%this)
{
   // a bit less snow
   %this.accuStrength = %this.accuStrength - 0.01;
   %this.reload();
}

/// SimGroup method that is called every %delay msecs to
/// modify snow accumulation strength on all affected surfaces.
function SnowGroup::manageSnow(%this, %delay)
{
   // delay defaults to 1 second (1000 msecs)
   if (%delay $= "") %delay = 1000;

   // handle our "Snowfall" snow precipitation
   // use Snowfall.snowPerc to keep track of the actual percentage
   // of snow that we see.
   if ($isSnowing) {
      Snowfall.snowPerc = Snowfall.snowPerc + 10;
      if (Snowfall.snowPerc>100) Snowfall.snowPerc = 100;
   } else {
      Snowfall.snowPerc = Snowfall.snowPerc - 10;
      if (Snowfall.snowPerc<1) Snowfall.snowPerc = 1;
   }
   Snowfall.setPercentage(Snowfall.snowPerc/100);

   // iterate through all members of the SnowGroup and make them gather
   // more or less snow depending on the value of the $isSnowing global var
   for ( %i = 0; %i < %this.getCount(); %i++ )  
   {  
      %obj = %this.getObject( %i );
      if ($isSnowing) {
         if (%obj.accuStrength<0.86) %obj.moreSnow();
      } else {
         if (%obj.accuStrength>0.56) %obj.lessSnow();
      }
   }

   // schedule this method to run again
   %this.snowSchedule = %this.schedule(%delay, "manageSnow");
}

/// A convenience function that initializes and starts the snowing
function manageSnow(%delay)
{
   // create a snow precipitation in the mission but never save with the mission
   if (!isObject(Snowfall)) {
      new Precipitation(Snowfall) {
         numDrops = "8092";
         boxWidth = "100";
         boxHeight = "100";
         dropSize = "0.1";
         animateSplashes = "0";
         doCollision = "0";
         minSpeed = "0.2";
         maxSpeed = "0.6";
         minMass = "6";
         maxMass = "10";
         useTurbulence = "0";
         maxTurbulence = "0.01";
         turbulenceSpeed = "0.02";
         dataBlock = "HeavySnow";
         position = "0 0 0";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         canSave = "0";
         snowPerc = 100;
         canSaveDynamicFields = "0";
      };
      MissionGroup.add(Snowfall);
   }
   
   // create a SnowGroup simgroup or cancel any previous schedules
   if (!isObject(SnowGroup)) {
      new SimGroup(SnowGroup);
   } else if (SnowGroup.snowSchedule !$= "")
      cancel(SnowGroup.snowSchedule);
      
   // add the right materials to the SnowGroup group
   for(%i = 0; %i < RootGroup.getCount(); %i++)
      if( RootGroup.getObject(%i).getSuperClassNamespace() $= "SnowyMaterial" )
         SnowGroup.add(RootGroup.getObject(%i));
   
   // start "managing" snow - fire SnowGroup.manageSnow every <%delay> milliseconds
   SnowGroup.manageSnow(%delay);

   randomizeWeather();
}

/// Changes weather at least once every minute
function randomizeWeather()
{
   $isSnowing = !$isSnowing;
   schedule(getRandom(60000), 0, "randomizeWeather");
}

The above pretty much handles everything we need. It schedules a change on all materials that are descended from our SnowyMaterial and it also updates the dynamically created Snowfall precipitation object to reflect the strength of the snowstorm. It also takes care of randomizing the weather by changing it at least once every minute. The "weather" being the $isSnowing boolean global - either true or false.

I made sure this file is used by exec'ing it in scripts/client/init.cs in initClient() just after the loadMaterials call:

// >>>
   exec("scripts/client/saharaSnow.cs");
   // <<<

To make it start snowing, call manageSnow(); once your mission has loaded.

pub.bitgap.com/s1100.jpg

Finally, here is a quick time-lapse video of the resource in action on the Cheetah:


Thanks for reading through. As always, comments are much appreciated.

--Konrad

@konradkiss
KonradKiss @ GitHub
konradkiss.com
Page«First 1 2 Next»
#21
09/13/2011 (11:11 am)
Thanks guys! :)

@EB: Lol, thank you now! :)
#22
09/14/2011 (5:51 am)
That's really cool (pardon the pun) Konrad, but should't you be working on releasing Xenocell? :P
#23
09/14/2011 (6:28 am)
Yep, that too. :) This is also in Xenocell, so I actually hit two birds with one stone! :) how is that for an excuse? :)
#24
09/14/2011 (6:28 am)
Oops, double post.
#25
09/14/2011 (7:44 pm)
Very Nice Konrad! Loving it....
#26
09/16/2011 (1:00 pm)
Nice work! Nice to see the Cheetah being put to good use :)
#27
09/17/2011 (1:21 pm)
Thanks Jim and Elie. :)

Haha, I'm sure there are better uses for the Cheetah than catching snow. :)
#28
11/08/2011 (11:12 am)
Finally got around to tamper with this, but all I get is;

==>manageSnow();
GameBase::setDatablockProperty - Could not find data block "HeavySnow"
#29
11/08/2011 (11:19 am)
Hi Christian,

This is a precipitation datablock.. it should be defined in FPS Example/game/art/datablocks/environment.cs:

datablock PrecipitationData(HeavySnow)
{
   dropTexture = "art/environment/precipitation/snow";
   splashTexture = "art/environment/precipitation/snow";
   dropSize = 0.27;
   splashSize = 0.27;
   useTrueBillboards = false;
   splashMS = 50;
};

The .cs should be called from datablockExec.cs in the same directory. It is stock in 1.1 final.
#30
11/08/2011 (12:31 pm)
is not part of the axf Im using, so much thanks :)
#31
11/08/2011 (12:34 pm)
No worries - I assumed you were working on something non-vanilla, that's why I added the datablock definition. Good luck! :)
#32
01/08/2012 (8:57 pm)
Konrad@ Trying to apply this to latest T3D 1.2 ... and ran into the following error, which causes the engine to crash;

Error: cannot change namespace parent linkage of SnowyMaterial from Material to SnowyMaterial.

So I commented out the superClass = "SnowMaterial"; line and it compiles, executes, etc...

When I load into the mission, the Cheetah seems to already be 100% covered in snow ...

then I run in console :
ManageSnow();

and it starts snowing... but its not working otherwise, because of the superclass issue above.

Believe w/o the superclass, its not finding the proper materials to modify ... since the code looks for that superclass.

Any ideas how to resolve the superclass issue in the material.cs ?
#33
01/08/2012 (11:16 pm)
Jeff, I'm not at my PC, but try renaming the SuperClass to ie. WinterMaterial in the definition of SnowyMaterial. Then also change the moreSnow and lessSnow declarations to be in the WinterMaterial namespace instead of SnowyMaterial. I think then it should be fine.

Sahara for Torque 3D is not yet out, but as far as I can tell, no modifications are needed when applying the one for Torque 3D 1.1. Let me know how this goes, I'll be around to help.
#34
01/09/2012 (6:03 pm)
I changed the materials file like this;
singleton Material(MySaharaMaterial)
{
   superClass = "SaharaMaterial";

   accuMap[0] = "art/terrains/snow";
   accuStrength[0] = "0.56"; // 862745"; // start with no snow
   accuCoverage[0] = "1.56863";
   accuSpecular[0] = "2";

   materialTag0 = "Vegetation";
};

singleton Material(Cheetah_Main : MySaharaMaterial)
{
   mapTo = "Cheetah_Main";
   diffuseMap[0]  = "art/shapes/Cheetah/Cheetah_D";
   normalMap[0]   = "art/shapes/Cheetah/Cheetah_N";
   specularMap[0] = "art/shapes/Cheetah/Cheetah_S";
   specular[0] = "0.9 0.9 0.9 1";
   specularPower[0] = "10";
   translucentBlendOp = "None";  
};

And the SaharaSnow.cpp to match and added some echo statements.

Also updated SnowGroup::manageSnow & ManageSnow functions.

No more errors when it runs, but I'm not changing the texture when it executes.

ie. it appears to be changing the "MySaharaMaterial" material accu properties, but those are not updating the child material "Cheetah_Main".

EDIT:removed excessive information


#35
01/09/2012 (6:32 pm)
Here is what I did to get it working, bypassing the group ... just to make sure it the actual engine code was working.

function Cheetah::moreSnow()
{     
   // a bit more snow
   $Cheetah::MatObj.accuStrength = $Cheetah::MatObj.accuStrength + 0.10;
   $Cheetah::MatObj.reload();
   
   echo("+++ Cheetah::moreSnow (" @ $Cheetah::MatObj.accuStrength @ ")+++");
}
function Cheetah::lessSnow()
{    
   // a bit more snow
   $Cheetah::MatObj.accuStrength = $Cheetah::MatObj.accuStrength - 0.10;
   $Cheetah::MatObj.reload();
   
   echo("--- Cheetah::lessSnow (" @ $Cheetah::MatObj.accuStrength @ ")---");
}
function Cheetah::initSnow()
{
   // add the right materials to the SnowGroup group
   %id = -1;
   for(%i = 0; %i < RootGroup.getCount(); %i++)
      if( RootGroup.getObject(%i).getName() $= "Cheetah_Main")
        %id = %i;
                
   if (%id > -1)
   {
      $Cheetah::MatObj = RootGroup.getObject(%id);
      echo("Found Cheetah_Main material (" @ %id @ ")" );
   }
   else
   {
      echo("Error! Cheetah_Main not found!!");
   }
}

And this works perfect ... ran Cheetah::initSnow() and then Cheetah::moreSnow() a few times ... it immediatly updated the objects texture, like it should ... and Cheetah::lessSnow() removes it as expected too.

So, the engine code works fine in T3D 1.2 ... possibly its not handling the update of the parent texture, to refresh the child texture -- if that makes any sense.
#36
01/12/2012 (2:59 pm)
yep - just to confirm...

THIS:
/// Material method that adds a bit more snow  
function SnowyMaterial::moreSnow(%this)  
{  
   // a bit more snow  
   %this.accuStrength = %this.accuStrength + 0.01;  
   %this.reload();  
}

>> updates the parent material (SnowyMaterial) properly, but this fails to cause the child material(s) to update... not sure why or what changed to cause this.

==>ManageSnow();
called manageSnow()

creating new SnowGroup
350) adding object (obj=350,id=2086,mat=Cheetah_Main) to SnowGroup
*** added cheetah material to global var, currently accuStrength = 0.56 ***

=== SnowGroup::manageSnow() isSnowing(0)===
MySaharaMaterial accuStrength=0.56

=== SnowGroup::manageSnow() isSnowing(1)===
MySaharaMaterial accuStrength=0.56
+++ SaharaMaterial::moreSnow (MySaharaMaterial)+++
Parent MySaharaMaterial texture re-loaded, child material should also update
 --- however, $Cheetah::MatObj accu Strength =0.56 ---

=== SnowGroup::manageSnow() isSnowing(1)===
MySaharaMaterial accuStrength=0.57
+++ SaharaMaterial::moreSnow (MySaharaMaterial)+++
Parent MySaharaMaterial texture re-loaded, child material should also update
 --- however, $Cheetah::MatObj accu Strength =0.56 ---

=== SnowGroup::manageSnow() isSnowing(1)===
MySaharaMaterial accuStrength=0.58
+++ SaharaMaterial::moreSnow (MySaharaMaterial)+++
Parent MySaharaMaterial texture re-loaded, child material should also update
 --- however, $Cheetah::MatObj accu Strength =0.56 ---

=== SnowGroup::manageSnow() isSnowing(1)===
MySaharaMaterial accuStrength=0.59
+++ SaharaMaterial::moreSnow (MySaharaMaterial)+++
Parent MySaharaMaterial texture re-loaded, child material should also update
 --- however, $Cheetah::MatObj accu Strength =0.56 ---
Page«First 1 2 Next»