Terasology-Archived / GrammarSystem

Grammar System for procedural structure generation. Basis for the Procedural Architecture Grammar
9 stars 3 forks source link

Update documentation #4

Open Cervator opened 8 years ago

Cervator commented 8 years ago

I'm cleaning up the engine wiki and moving some doc to modules, especially if they're inactive like the grammar system. @skaldarnar save us from the lack of grammar! :-)

Old Grammar System page:

The Grammar System (or Grammar Building System) uses the [[Procedural Architecture Grammar]] to generate building structures. It is included in versions starting from stable #22.

Building Generator

Building Generators are used to create a [[Block Collection]] which can then be placed in the world. For the generation process, the structure's dimension and a proper [[grammar|Procedural Architecture Grammar]] is needed. Please refeer to the corresponding sites for more information.

Console Commands

The Grammar System provides console commands to test the building generation. At the moment the system is quite restricted. You can only call a pre defined generator with specifying width, depth and height of the building. To do so, just type \build <width> <heigth> <depth> in the [[Console|Console Help]]. The generated building will be placed right in front of the player.

Related Links

Old PAG page:

This is a work in Progress

You read that right, this is a grammar for our grammar. That is, this defines the format for the grammars one can use for procedural generation.

PAG specification

PAG := Header 'grammar' Identifier ':' Section+

Header

The PAG-Header will store information for the grammar system, such as which type of structure is created by this grammar, and provide the possibility to assign constants you can later use in the grammar rules.

Header := Declaration*

Declaration := '#'? Identifier ':=' expr ';'

A header declaration that starts with a # will be interpreted as grammar info field, such as building type, or fitting biomes. You can define module dependencies for the grammar here as well:

    #dependencies := ["myModule", "otherAwesomeModule"];

You may have noticed that different forms of expressions are allowed on the right side, we will come to that later. Simple constants can be defined in a similar way: floor_height := 3;

Sections

Sections make up the grammar and group the grammar rules with the same priority. The priority is used to control the order in which derivations are selected. Of course it is possible to have just one section for all rules.

Section := 'Priority' decimalLiteral ':' ruleStatement+

Each ruleStatement is of the form predecessor -> successor. Note that every rule has to be ended by a semicolon. Furthermore, you can specify guards (boolean expressions that guard whether the rule can be applied or not) and probabilities. Guards and probabilities are optional for every rule.

ruleStatement := Predecessor (':' expression)? '->' Successor (':' expression)? ('|' Successor (':' expression)?)+ ';'

Predecessor := Identifier

The successor can either be an identifier, which denotes another rule, a base rule or a sequence of the former enclosed in braces.

Successor := Identifier | Baserule | '{' (Identifier | BaseRule)* '}'

Expressions

The following set of expressions are supported by the grammar system. You can use standard Boolean and arithmetic operations, numbers (integer and decimal), identifiers and strings and lists of expressions. Furthermore, there are a few special operations/method calls.

expression := primary
        | functionCall
        | ('Shape' | 'Scope') '.' ('occ' | 'visible') '(' expression ')'
        | '[' expression ']'
        | ('+' | '-') expression
        | '!' expression
        | expression ('*' | '/' | '%') expression
        | expression ('+' | '-') expression
        | expression ('<=' | '>=' | '<' | '>') expression
        | expression ('==' | '!=') expression
        | expression '&&' expression
        | expression '||' expression

primary := '(' expression ')'  | literal 'r'? | Identifier

The only function call currently available is the random selection from either a list or a defined range.

    functionCall := randomSelection

    randomSelection := 'Random' '(' (exprList | rangeExpr) ')'

An expression list is simply a sequence of expressions:

    exprList := expression (',' expression)*

You can define a range of integers with the following notation:

    rangeExpr := expression '..' expression

E.g. you can select a random block type from a list or define a scale or measure of length randomly within a range from .. to.

    wall_mat := Random("cobblestone", "stonebricks", "stone");
    number_floors := Random(2..5);

The occlusion and visibility checks are useful as guards. They can be used to determine whether the current scope is occluded by any other shape or scope or if a specific shape is visible from the current scope. This can be used to place doors on walls that face to the street. Note that the occlusion test for scopes is faster but less accurate than the test against shapes.

    wall : Shape.visible("street") -> door_wall;
    wall : Scope.occ("nonparent") == "none" -> win_wall;

Base Rules

The following basic rules are supported by the grammar.

BaseRule := SetRule | DivideRule | SplitRule | RepeatRule | Invoke | TransformationRule

SetRule := 'set' '(' '"' qualifiedIdentifier '"' ')'

DivideRule := 'divide' Direction '{' ('[' Size ']' Successor)+  '}'

SplitRule := 'split' '{' ('[' SplitType ']' Successor)+ '}'

RepeatRule := 'repeat' Direction '['expression']' Successor

The invocation of other grammars is just a proposal for allowing the reuse of code. It may be erased from the specification if it proves not to be practical.

Invoke := 'invoke' '(' qualifiedIdentifier ')'

Basic transformation of shapes is supported by the following rules.

TransformationRule := ScaleRule | TranslateRule | RotateRule

ScaleRule := 'S' '(' numericLiteral (',' numericLiteral ',' numericLiteral)? ')'

TranslateRule := 'T' '(' numericLiteral ',' numericLiteral ',' numericLiteral ')'

RotateRule := ('Rx' | 'Ry' | 'Rz') '(' numericLiteral ')'

Moreover, we need special rules for the roof construction:

Roof := 'Roof' '(' roofType ',' expression ')' '{' successor '}'

roofType := 'hipped' | ...

The following attributes are used in the base rules.

Direction := 'X' | 'x' | 'Y' | 'y' | 'Z' | 'z'

Size := decimalLiteral ('%')? | floatingPointLiteral

SplitType := 'faces' | 'sidefaces' | 'edges' | 'vertices' | 'walls' | 'floor'

Literals

literal := numericLiteral | booleanLiteral

booleanLiteral := 'true' | 'false'

numericLiteral := decimalLiteral | floatingPointLiteral

decimalLiteral := '0' | '1'..'9' Digit*

floatingPointLiteral := Digits '.' Digits? | '.' Digits | Digits 'f'

Digits := Digit*

Digit := '0'..'9'

Identifiers

Identifiers (such as predecessor and variable names) have to start with a letter, followed by any sequence of alphnumerical characters. Qualified Identifiers will be used to specify assets (such as blocks or other grammar files).

Identifier := Letter (Letter | Digit)*

Letter := 'a'..'z' | 'A'..'Z' | '_'

qualifiedIdentifier := Identifier (':' Identifier)* 

Comments, Whitespaces, ...

Comments, whitespaces and such are skipped during parsing and have no influence on the actual grammar functionality. The rules are denoted using a syntax similar to ANTLR.

COMMENT := '/*' (.*)? '*/' -> channel(HIDDEN)

LINE_COMMENT := '//' ~[\r\n]* -> channel(HIDDEN)

WS := ( '\r' | '\t' | ' ' | '\n' )+ -> channel(HIDDEN)

I use the regular expression notation for + (1 or more), ? (at most 1), brackets for characters ranges, * (for 0 or more), _ for escaping, ' ' for literals and parenthesis for grouping. Furthermore, . is a wildcard character and ~_ denotes "everything until ...".

Examples

The following code is an example of a PAG-grammar. It is closely related to a example provided by Müller, Wonka et al.

#type := house;

window_spacing  := 4;
window_width    := 2;
window_depth    := .5;
window_height   := 1;
door_height     := 2;
door_width      := 2;
roof_angle      := 45;

grammar TestHouse:

Priority 1:
    footprint -> {
        S(1r, building_height, 1r) facades
        T(0, building_height, 0) I("prism") roof
    };

    Priority 2:
        facades -> Split {
            ["sidefaces"] facade
        };

        facade : Shape.visible("street") 
            -> Divide "X" {
                [1r] tiles
                [door_width * 1.5] entrance
            } : 0.5
            -> Divide "X" {
                [door_width * 1.5] entrance
                [1r] tiles          
            } : 0.5
            ;
        facade -> tiles;

        tiles -> Repeat "X" [window_spacing] tile;

        tile -> Divide "X" {
            [1r] wall
            [window_width] Divide "Y" {
                    [window_height] window
                    [1r] wall
                }
            [1r] wall
        };

        window : Scope.occ("noparent") != "none" -> wall;
        window -> {
            S(1r, 1r, window_depth) Set("enginge.glass")
        };

        entrance -> Divide "X" {
            [1r] wall
            [door_width] Divide "Y" {
                    [door_height] door
                    [1r] wall
                }
            [1r] wall
        };

        door -> Set("core.door");

        wall -> Set(Random("cobblestone", "stonebricks"));

    Priority 3:
        roof -> Split {
            ["sidefaces"] covering
            ["sideedges"] roofedge
            ["topedge"] roofedge
        };

        covering -> Set("brick" : "stair");

        roofedge -> Set("stone");

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/30148170-update-documentation?utm_campaign=plugin&utm_content=tracker%2F1857106&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F1857106&utm_medium=issues&utm_source=github).