Chapter 02Ribbons

Using the CreateRibbon() method of the MeshBuilder class, we can make ribbons.

let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {options}, scene);

The first argument of CreateRibbon() is a String that we can use to retrieve the entire ribbon mesh and data about the ribbon later. The second argument is an object that specifies various properties of the ribbon. The last argument is a reference to the scene in which the ribbon will be inserted into.

<script>
var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, true);

function createScene(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 3, 15, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseColor = new BABYLON.Color3(0, 1, 0);

    const myPaths = [
        [ 	new BABYLON.Vector3(7.0, 0, -2),
            new BABYLON.Vector3(5.0, 4, -4)
        ],
        [   new BABYLON.Vector3(0, 4, 0),
            new BABYLON.Vector3(0, 0, 0)
        ],
        [	new BABYLON.Vector3(-7.0, 0, 2),
            new BABYLON.Vector3(-5.0, 4, 4)
        ]
    ];

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
    ribbon.material = mat;

    return scene;
};

var scene = createScene(canvas, engine);

engine.runRenderLoop(function(){
    scene.render();
});

window.addEventListener("resize", function(){
    engine.resize();
});
</script>

<canvas id="renderCanvas"></canvas>
Figure 1. A Babylon.js Ribbon.

Properties

PathArray

The pathArray property is a two-dimensional array of Vector3 objects where each array of Vector3 objects is a path. Facets are then constructed between the paths to define the geometry of the ribbon. The pathArray property is required in order to create a ribbon mesh.

In figure 2 below, the red ribbon, for example, is constructed using two paths and the green ribbon is constructed using three paths.

<script>
var canvas1 = document.getElementById("renderCanvas1");
var engine1 = new BABYLON.Engine(canvas1, true);

function createScene1(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 40, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseColor = new BABYLON.Color3.Red;
    mat.wireframe = true;
    
    let mat1 = new BABYLON.StandardMaterial("mat", scene);
    mat1.diffuseColor = new BABYLON.Color3.Green;
    mat1.wireframe = true;
    
    let mat2 = new BABYLON.StandardMaterial("mat", scene);
    mat2.diffuseColor = new BABYLON.Color3.Blue;
    mat2.wireframe = true;
    
    let mat3 = new BABYLON.StandardMaterial("mat", scene);
    mat3.diffuseColor = new BABYLON.Color3.Magenta;
    mat3.wireframe = true;

    const myPaths = [
        [ 	new BABYLON.Vector3(-7, 0, 0),
            new BABYLON.Vector3(-7, 2, 0),
            new BABYLON.Vector3(-7, 4, 0)
        ],
        [   new BABYLON.Vector3(7, 0, 0),
            new BABYLON.Vector3(7, 2, 0),
            new BABYLON.Vector3(7, 4, 0)
        ]
    ];

    const myPaths1 = [
        [ 	new BABYLON.Vector3(-7, 0, 0),
            new BABYLON.Vector3(-7, 2, 0),
            new BABYLON.Vector3(-7, 4, 0)
        ],
        [ 	new BABYLON.Vector3(0, 0, 0),
            new BABYLON.Vector3(0, 2, 0),
            new BABYLON.Vector3(0, 4, 0)
        ],
        [   new BABYLON.Vector3(7, 0, 0),
            new BABYLON.Vector3(7, 2, 0),
            new BABYLON.Vector3(7, 4, 0)
        ]
    ];

    const myPaths2 = [
        [ 	new BABYLON.Vector3(-7, 0, 0),
            new BABYLON.Vector3(-7, 2, 0),
            new BABYLON.Vector3(-7, 4, 0)
        ],
        [ 	new BABYLON.Vector3(0, 0, 0),
            new BABYLON.Vector3(0, 2, 0),
            new BABYLON.Vector3(0, 4, 0)
        ],
        [ 	new BABYLON.Vector3(2, 0, 0),
            new BABYLON.Vector3(2, 2, 0),
            new BABYLON.Vector3(2, 4, 0)
        ],
        [   new BABYLON.Vector3(7, 0, 0),
            new BABYLON.Vector3(7, 2, 0),
            new BABYLON.Vector3(7, 4, 0)
        ]
    ];

    const myPaths3 = [
        [ 	new BABYLON.Vector3(-7, 0, 0),
            new BABYLON.Vector3(-7, 2, 0),
            new BABYLON.Vector3(-7, 4, 0)
        ],
        [ 	new BABYLON.Vector3(0, 0, 0),
            new BABYLON.Vector3(0, 2, 0),
            new BABYLON.Vector3(0, 4, 0)
        ],
        [ 	new BABYLON.Vector3(2, 0, 0),
            new BABYLON.Vector3(2, 2, 0),
            new BABYLON.Vector3(2, 4, 0)
        ],
        [ 	new BABYLON.Vector3(4, 0, 0),
            new BABYLON.Vector3(4, 2, 0),
            new BABYLON.Vector3(4, 4, 0)
        ],
        [   new BABYLON.Vector3(7, 0, 0),
            new BABYLON.Vector3(7, 2, 0),
            new BABYLON.Vector3(7, 4, 0)
        ]
    ];

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
    ribbon.material = mat;
    ribbon.position = new BABYLON.Vector3(-24, 0, 0)

    let ribbon1 = BABYLON.MeshBuilder.CreateRibbon("ribbon1", {pathArray: myPaths1, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
    ribbon1.material = mat1;
    ribbon1.position = new BABYLON.Vector3(-8, 0, 0)

    let ribbon2 = BABYLON.MeshBuilder.CreateRibbon("ribbon2", {pathArray: myPaths2, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
    ribbon2.material = mat2;
    ribbon2.position = new BABYLON.Vector3(8, 0, 0)

    let ribbon3 = BABYLON.MeshBuilder.CreateRibbon("ribbon3", {pathArray: myPaths3, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
    ribbon3.material = mat3;
    ribbon3.position = new BABYLON.Vector3(24, 0, 0)

    return scene;
};

var scene1 = createScene1(canvas1, engine1);

engine1.runRenderLoop(function(){
    scene1.render();
});

window.addEventListener("resize", function(){
    engine1.resize();
});
</script>

<canvas id="renderCanvas1"></canvas>
Figure 2. From left to right, the ribbons have 2, 3, 4, and 5 paths.

CloseArray

When we set the closeArray property to true, Babylon infers an additional line between each pair of adjacent paths in the path array, and then adds facets between the existing paths and the new paths.

<script>
var canvas2 = document.getElementById("renderCanvas2");
var engine2 = new BABYLON.Engine(canvas2, true);

function createScene2(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseColor = new BABYLON.Color3(0, 1, 0);
    mat.wireframe = true;

    let myPaths = [];
    myPaths[0] = [];
    myPaths[1] = [];
    myPaths[2] = [];

    let theta = 0;
    for (let i = 0; i < 20; i++){
        let x = 3 * Math.cos(theta);
        let y = 3 * Math.sin(theta);

        myPaths[0][i] = new BABYLON.Vector3(x, y, 0);
        myPaths[1][i] = new BABYLON.Vector3(x, y, 5);
        myPaths[2][i] = new BABYLON.Vector3(x, y, 10);

        theta += Math.PI / 20;
    }

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths}, scene);
    ribbon.material = mat;
    ribbon.position = new BABYLON.Vector3(-5, -3, 0);

    let ribbon1 = BABYLON.MeshBuilder.CreateRibbon("ribbon1", {pathArray: myPaths, closeArray: true}, scene);
    ribbon1.material = mat;
    ribbon1.position = new BABYLON.Vector3(5, -3, 0);

    return scene;
};

var scene2 = createScene2(canvas2, engine2);

engine2.runRenderLoop(function(){
    scene2.render();
});

window.addEventListener("resize", function(){
    engine2.resize();
});
</script>

<canvas id="renderCanvas2"></canvas>
Figure 3. In the right-hand mesh, the inferred paths are visible as white lines between the first existing path and the middle existing path, and in between the middle existing path and the last existing path.

ClosePath

The closePath property closes the end points of each path using additional facets when the property is set to true.

<script>
var canvas4 = document.getElementById("renderCanvas4");
var engine4 = new BABYLON.Engine(canvas4, true);

function createScene4(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseColor = new BABYLON.Color3(0, 1, 0);
    mat.wireframe = true;

    let myPaths = [];
    myPaths[0] = [];
    myPaths[1] = [];
    myPaths[2] = [];

    let theta = 0;
    for (let i = 0; i < 20; i++){
        let x = 3 * Math.cos(theta);
        let y = 3 * Math.sin(theta);

        myPaths[0][i] = new BABYLON.Vector3(x, y, 0);
        myPaths[1][i] = new BABYLON.Vector3(x, y, 5);
        myPaths[2][i] = new BABYLON.Vector3(x, y, 10);

        theta += Math.PI / 20;
    }

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths}, scene);
    ribbon.material = mat;
    ribbon.position = new BABYLON.Vector3(-5, -3, 0);

    let ribbon1 = BABYLON.MeshBuilder.CreateRibbon("ribbon1", {pathArray: myPaths, closePath: true}, scene);
    ribbon1.material = mat;
    ribbon1.position = new BABYLON.Vector3(5, -3, 0);

    return scene;
};

var scene4 = createScene4(canvas4, engine4);

engine4.runRenderLoop(function(){
    scene4.render();
});

window.addEventListener("resize", function(){
    engine4.resize();
});
</script>

<canvas id="renderCanvas4"></canvas>
Figure 4. The closePath property connects the paths' endpoints.

Updatable

If we want the ribbon to be able to have its internal geometry changed after creation, we can set the Boolean parameter updatable equal to true. The default value is false.

SideOrientation

The sideOrientation property allows us to make our specify which sides of the ribbon are visible in the scene.The default value is FRONTSIDE.

The different values for the sideOrientation property are:

FrontUVs and BackUVs

If the ribbon is double-sided, we can choose what parts of a texture to crop and stick on the front and back sides of our mesh with the frontUVs and backUVs properties, respectively. The default value for both is Vector4(0, 0, 1, 1), which is the entire texture.

<script>
var canvas3 = document.getElementById("renderCanvas3");
var engine3 = new BABYLON.Engine(canvas3, true);

function createScene3(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 3, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let front = new BABYLON.Vector4(0, 0, 1, 1);
    let back = new BABYLON.Vector4(0, 0, 1, 1);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/fur.jpg", scene);
    mat.diffuseColor = new BABYLON.Color3(0, 1, 0);

    const myPaths = [
        [ 	new BABYLON.Vector3(7.0, 0, -2),
            new BABYLON.Vector3(5.0, 4, -4)
        ],
        [   new BABYLON.Vector3(0, 4, 0),
            new BABYLON.Vector3(0, 0, 0)
        ],
        [	new BABYLON.Vector3(-7.0, 0, 2),
            new BABYLON.Vector3(-5.0, 4, 4)
        ]
    ];

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths, sideOrientation: BABYLON.Mesh.DOUBLESIDE, frontUVs: front, backUVs: back}, scene);
    ribbon.material = mat;

    return scene;
};

var scene3 = createScene3(canvas3, engine3);

engine3.runRenderLoop(function(){
    scene3.render();
});

window.addEventListener("resize", function(){
    engine3.resize();
});
</script>

<canvas id="renderCanvas3"></canvas>
Figure 5. We apply a texture to the front and back of the ribbon using the frontUVs and backUVs properties, respectively.

Instance

The instance property, whose default value null, allows us to change the paths used in a ribbon mesh, as long as the number of paths used in the mesh does not change.

<script>
var canvas6 = document.getElementById("renderCanvas6");
var engine6 = new BABYLON.Engine(canvas6, true);

function createScene6(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", Math.PI/2, Math.PI / 2, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseColor = new BABYLON.Color3(0, 1, 0);

    const pathArray = [
        [ 	new BABYLON.Vector3(7.0, 0, -2),
            new BABYLON.Vector3(5.0, 4, -4)
        ],
        [   new BABYLON.Vector3(0, 4, 0),
            new BABYLON.Vector3(0, 0, 0)
        ],
        [	new BABYLON.Vector3(-7.0, 0, 2),
            new BABYLON.Vector3(-5.0, 4, 4)
        ]
    ];

    let options = {pathArray: pathArray, sideOrientation: BABYLON.Mesh.DOUBLESIDE, updatable: true};

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon1", options, scene);
    ribbon.material = mat;

    return scene;
};

var scene6 = createScene6(canvas6, engine6);

engine6.runRenderLoop(function(){
    scene6.render();
});

window.addEventListener("resize", function(){
    engine6.resize();
});

var ogPaths = true;

function changePaths() {
    let ribbon = scene6.getMeshByName("ribbon1");

    let options = {};

    options.instance = ribbon;

    if (ogPaths){
        options.pathArray = [[new BABYLON.Vector3(7.0, 0, -2), new BABYLON.Vector3(5.0, 4, -4)], [new BABYLON.Vector3(0, 0, 0), new BABYLON.Vector3(0, 4, 0)], [new BABYLON.Vector3(-7.0, 0, 2), new BABYLON.Vector3(-5.0, 4, 4)]];

        ogPaths = false;
    } else{
        options.pathArray = [[new BABYLON.Vector3(7.0, 0, -2), new BABYLON.Vector3(5.0, 4, -4)], [new BABYLON.Vector3(0, 4, 0), new BABYLON.Vector3(0, 0, 0)], [new BABYLON.Vector3(-7.0, 0, 2), new BABYLON.Vector3(-5.0, 4, 4)]];

        ogPaths = true;
    }

    ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", options);
}
</script>

<canvas id="renderCanvas6"></canvas>

<button onclick = "changePaths()">Change Instance</button>
Figure 6. The instance property is used to change the paths used in the ribbon mesh.

InvertUV

The invertUV array rotates the texture on the ribbon 90 degrees counter-clockwise by swapping the u and v coordinates of the texture. The default value is false.

<script>
var canvas5 = document.getElementById("renderCanvas5");
var engine5 = new BABYLON.Engine(canvas5, true);

function createScene5(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    let light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);

    let front = new BABYLON.Vector4(0, 0, 1, 1);
    let back = new BABYLON.Vector4(0, 0, 1, 1);

    let mat = new BABYLON.StandardMaterial("mat", scene);
    mat.diffuseTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/icons/Back.png", scene);
    mat.diffuseColor = new BABYLON.Color3(1, 1, 1);

    const myPaths = [
        [ 	new BABYLON.Vector3(5.0, 0, 0),
            new BABYLON.Vector3(5.0, 4, 0)
        ],
        [	new BABYLON.Vector3(-5.0, 0, 0),
            new BABYLON.Vector3(-5.0, 4, 0)
        ]
    ];

    let ribbon = BABYLON.MeshBuilder.CreateRibbon("ribbon", {pathArray: myPaths, frontUVs: front, backUVs: back}, scene);
    ribbon.material = mat;
    ribbon.position = new BABYLON.Vector3(-8, 0, 0);

    let ribbon1 = BABYLON.MeshBuilder.CreateRibbon("ribbon1", {pathArray: myPaths, frontUVs: front, backUVs: back, invertUV: true}, scene);
    ribbon1.material = mat;
    ribbon1.position = new BABYLON.Vector3(8, 0, 0);

    return scene;
};

var scene5 = createScene5(canvas5, engine5);

engine5.runRenderLoop(function(){
    scene5.render();
});

window.addEventListener("resize", function(){
    engine5.resize();
});
</script>

<canvas id="renderCanvas5"></canvas>
Figure 7. The right-hand ribbon has the invertUV property set to true.

References