NewScriptFeatures         Shoggoth introduces a new script compiler, which unifies the Ogre scripting language between all script types

Shoggoth introduces a new script compiler, which unifies the Ogre scripting language between all script types. It also makes available new features that makes Ogre scripts more powerful.

Format

The script syntax is no longer strictly line-wise, you can now do things like this:

material Test {
     technique {
         pass {} 
     }
 }

Inheritance



Starting with Shoggoth, script objects can now inherit from each other more generally. The previous concept of inheritance, material copying, was restricted only to the top-level material objects. Now, any level of object can take advantage of inheritance (for instance, techniques, passes, and compositor targets).

material Test
 {
     technique
     {
         pass : ParentPass
         {
         }
     }
 }

Notice that the pass inherits from ParentPass. This allows for the creation of more fine-grained inheritance hierarchies.

Along with the more generalized inheritance system comes an important new keyword: "abstract." This keyword is used at a top-level object declaration (not inside any other object) to denote that it is not something that the compiler should actually attempt to compile, but rather that it is only for the purpose of inheritance. For example, a material declared with the abstract keyword will never be turned into an actual usable material in the material framework. Objects which cannot be at a top-level in the document (like a pass) but that you would like to declare as such for inheriting purpose must be declared with the abstract keyword.

abstract pass ParentPass
 {
     diffuse 1 0 0 1
 }

That declares the ParentPass object which was inherited from in the above example. Notice the abstract keyword which informs the compiler that it should not attempt to actually turn this object into any sort of Ogre resource. If it did attempt to do so, then it would obviously fail, since a pass all on its own like that is not valid.

Variables

A very powerful new feature is variables. Variables allow you to parameterize data in materials so that they can become more generalized. This enables greater reuse of scripts by targeting specific customization points. Using variables along with inheritance allows for huge amounts of overrides and easy object reuse.

abstract pass ParentPass
 {
    diffuse $diffuse_colour
 }
 
 material Test
 {
    technique
    {
        pass : ParentPass
        {
            set $diffuse_colour "1 0 0 1"
        }
    }
 }

The ParentPass object declares a variable called "diffuse_colour" which is then overridden in the Test material's pass. The "set" keyword is used to set the value of that variable. The variable assignment follows lexical scoping rules, which means that the value of "1 0 0 1" is only valid inside that pass definition. Variable assignment in outer scopes carry over into inner scopes.

material Test
 {
     set $diffuse_colour "1 0 0 1"
     technique
     {
         pass : ParentPass
         {
         }
     }
 }

The $diffuse_colour assignment carries down through the technique and into the pass.

Imports

Imports are a feature introduced to remove ambiguity from script dependencies. When using scripts that inherit from each other but which are defined in separate files sometimes errors occur because the scripts are loaded in incorrect order. Using imports removes this issue. The script which is inheriting another can explicitly import its parent's definition which will ensure that no errors occur because the parent's definition was not found.

import * from "parent.material"
 material Child : Parent
 {
 }

The material "Parent" is defined in parent.material and the import ensures that those definitions are found properly. You can also import specific targets from within a file.

import Parent from "parent.material"

If there were other definitions in the parent.material file, they would not be imported.

Note, however that importing does not actually cause objects in the imported script to be fully parsed & created, it just makes the definitions available for inheritence. This has a specific ramification for vertex / fragment program definitions, which must be loaded before any parameters can be specified. You should continue to put common program definitions in .program files to ensure they are fully parsed before being referenced in multiple .material files. The 'import' command just makes sure you can resolve dependencies between equivalent script definitions (e.g. material to material).

Overrides

Overriding refers to the behavior that inherited materials can override the properties of their parents with their own properties. When overriding meets sub-objects, a variety of matching rules comes into play to allow children to powerfully and selectively override pieces of their parents. Here's an example of a child object overriding a sub-object of its parent.

material Parent
 {
    technique
    {
       pass
       {
          diffuse 1 1 1 1
       }
    }
 }

 material Child : Parent
 {
    technique
    {
       pass
       {
          diffuse 0 0 0 0
       }
    }
 }

The color of the pass will be "0 0 0 0." What happened here is that the first pass in the first technique has been overridden by Child based purely on the position of the objects within the material definition. The first non-named, non-wildcard (see later what these mean) overrides the first non-named object in Parent. And, this overriding behavior is nesting, which is why the same rules apply to the passes as well.

Overriding can also make matches based on names. An object will override another object of the same name (unless naming is disabled, like with particle emitters and affectors).

material Parent
 {
    technique
    {
       pass
       {
          diffuse 1 1 1 1
       }
       pass red
       {
          diffuse 1 0 0 1
       }
    }
 }

 material Child : Parent
 {
    technique
    {
       pass red
       {
          diffuse 0 0 0 0
       }
    }
 }

In this case, even though only one pass is defined in Child, it overrides the second pass in Parent (instead of the first, as would have happened with just index-based overriding). The names are matched up, and the second pass now has a diffuse color of "0 0 0 0." What happens to that first pass of Parent? It gets included into the Child definition as well, and will appear in the final compiled material. You can change and override properties, even add new objects, but you can't remove objects coming in from Parents.

The final matching system is based on wildcards. Using the '*' character, you can make a powerful matching scheme and override multiple objects at once, even if you don't know exact names or positions of those objects in the inherited object.

abstract technique Overrider
 {
    pass *color*
    {
       diffuse 0 0 0 0
    }
 }

This technique, when included in a material, will override all passes matching the wildcard "*color*" (color has to appear in the name somewhere) and turn their diffuse properties black. It does not matter their position or exact name in the inherited technique, this will match them.