Chapter 02Torus Knots

Using the CreateTorusKnot() method of the MeshBuilder class, we can make torus knots.

let torusKnot = BABYLON.MeshBuilder.CreateTorusKnot("name", {options}, scene);

The first argument of CreateTorusKnot() is a String that we can use to retrieve the entire torus knot mesh and data about the torus knot later. The second argument is an object that specifies various properties of the torus knot. The last argument is a reference to the scene in which the torus knot 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 / 4, Math.PI / 4, 8, 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);

    let torusKnot = BABYLON.MeshBuilder.CreateTorusKnot("torusKnot", {}, scene);
    torusKnot.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. Default Babylon.js Torus Knot.

Properties

Radius

The radius property can be used to specify the length of the entire knot. The default value is 2.

<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 / 4, Math.PI / 4, 8, 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);

    let knot = BABYLON.MeshBuilder.CreateTorusKnot("knot", {radius: 1}, scene);
    knot.material = mat;
    
    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. The radius property is set to 2.

Tube

The tube property allows us to specify the thickness of the tube which makes up the torus knot. The default value is 0.5.

<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 / 4, Math.PI / 4, 8, 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);

    let knot = BABYLON.MeshBuilder.CreateTorusKnot("knot", {tube: 0.25}, scene);
    knot.material = mat;
    
    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. The tube property is set to 0.25.

RadialSegment

The radialSegment property specifies how many segments make up the entire tube. The more segments we have, the smoother the knot will be. The default value is 32.

<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 / 4, Math.PI / 4, 8, 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 knot = BABYLON.MeshBuilder.CreateTorusKnot("knot", {radialSegments: 16}, scene);
    knot.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 4. The radialSegments property is set to 16.

TubularSegment

The tubularSegment property specifies how many segments make up each radial segment along the tube. The default value is 32.

<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 / 4, Math.PI / 4, 8, 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 knot = BABYLON.MeshBuilder.CreateTorusKnot("knot", {radialSegments: 16, tubularSegments: 5}, scene);
    knot.material = mat;
    
    return scene;
};

var scene4 = createScene4(canvas4, engine4);

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

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

<canvas id="renderCanvas4"></canvas>
Figure 5. The tubularSegments property is set to 5.

P

The p property allows us to specify the amount of windings the torus knot has along the z-axis.

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

function createLocalAxes(size, scene) {
    let [local_axisX, local_axisY, local_axisZ] = createAxis(size, scene);

    let origin = new BABYLON.TransformNode("origin");

    local_axisX.parent = origin;
    local_axisY.parent = origin;
    local_axisZ.parent = origin;

    return origin;
}

function createAxis(size, scene) {
    let makeTextPlane = function(text, color, size, camera, billboardMode) {
        let dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", 50, scene, true);
        dynamicTexture.hasAlpha = true;
        dynamicTexture.drawText(text, 5, 40, "bold 36px Arial", color , "transparent", true);

        let plane = new BABYLON.MeshBuilder.CreatePlane("TextPlane", {size: size, updatable: true}, scene);
        plane.material = new BABYLON.StandardMaterial("TextPlaneMaterial", scene);
        plane.material.specularColor = new BABYLON.Color3(0, 0, 0);
        plane.material.diffuseTexture = dynamicTexture;
        plane.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;

        return plane;
    };

    let axisX = BABYLON.Mesh.CreateLines("axisX", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(size, 0, 0),
        new BABYLON.Vector3(size * 0.95, 0.05 * size, 0),
        new BABYLON.Vector3(size, 0, 0),
        new BABYLON.Vector3(size * 0.95, -0.05 * size, 0)
    ], scene);
    axisX.color = new BABYLON.Color3(1, 0, 0);

    let xChar = makeTextPlane("X", "red", size / 8);
    xChar.position = new BABYLON.Vector3(0.9 * size, -0.05 * size, 0);
    xChar.parent = axisX;

    let axisY = BABYLON.Mesh.CreateLines("axisY", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(0, size, 0),
        new BABYLON.Vector3( -0.05 * size, size * 0.95, 0),
        new BABYLON.Vector3(0, size, 0),
        new BABYLON.Vector3( 0.05 * size, size * 0.95, 0)
    ], scene);
    axisY.color = new BABYLON.Color3(0, 1, 0);

    let yChar = makeTextPlane("Y", "green", size / 8);
    yChar.position = new BABYLON.Vector3(0, 0.9 * size, -0.05 * size);
    yChar.parent = axisY;

    let axisZ = BABYLON.Mesh.CreateLines("axisZ", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(0, 0, size),
        new BABYLON.Vector3( 0 , -0.05 * size, size * 0.95),
        new BABYLON.Vector3(0, 0, size),
        new BABYLON.Vector3( 0, 0.05 * size, size * 0.95)
    ], scene);
    axisZ.color = new BABYLON.Color3(0, 0, 1);

    let zChar = makeTextPlane("Z", "blue", size / 8);
    zChar.position = new BABYLON.Vector3(0, 0.05 * size, 0.9 * size);
    zChar.parent = axisZ;

    return [axisX, axisY, axisZ];
}

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, 30, 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 knot1 = BABYLON.MeshBuilder.CreateTorusKnot("knot1", {p: 1, q: 1}, scene);
    knot1.material = mat;
    knot1.position = new BABYLON.Vector3(-16, 0, 0);
    let axis_origin1 = createLocalAxes(5, scene);
    axis_origin1.parent = knot1;

    let knot2 = BABYLON.MeshBuilder.CreateTorusKnot("knot2", {p: 2, q: 1}, scene);
    knot2.material = mat;
    knot2.position = new BABYLON.Vector3(-8, 0, 0);
    let axis_origin2 = createLocalAxes(5, scene);
    axis_origin2.parent = knot2;

    let knot3 = BABYLON.MeshBuilder.CreateTorusKnot("knot3", {p: 3, q: 1}, scene);
    knot3.material = mat;
    knot3.position = new BABYLON.Vector3(0, 0, 0);
    let axis_origin3 = createLocalAxes(5, scene);
    axis_origin3.parent = knot3;

    let knot4 = BABYLON.MeshBuilder.CreateTorusKnot("knot4", {p: 4, q: 1}, scene);
    knot4.material = mat;
    knot4.position = new BABYLON.Vector3(8, 0, 0);
    let axis_origin4 = createLocalAxes(5, scene);
    axis_origin4.parent = knot4;

    let knot5 = BABYLON.MeshBuilder.CreateTorusKnot("knot5", {p: 5, q: 1}, scene);
    knot5.material = mat;
    knot5.position = new BABYLON.Vector3(16, 0, 0);
    let axis_origin5 = createLocalAxes(5, scene);
    axis_origin5.parent = knot5;

    return scene;
};

var scene5 = createScene5(canvas5, engine5);

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

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

<canvas id="renderCanvas5"></canvas>
Figure 6. From left to right, the values for p are 1, 2, 3, 4, and 5.

Q

The q property allows us to specify the amount of windings the torus knot has on the xy-plane.

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

function createLocalAxes(size, scene) {
let [local_axisX, local_axisY, local_axisZ] = createAxis(size, scene);

    let origin = new BABYLON.TransformNode("origin");

    local_axisX.parent = origin;
    local_axisY.parent = origin;
    local_axisZ.parent = origin;

    return origin;
}

function createAxis(size, scene) {
let makeTextPlane = function(text, color, size, camera, billboardMode) {
let dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", 50, scene, true);
dynamicTexture.hasAlpha = true;
dynamicTexture.drawText(text, 5, 40, "bold 36px Arial", color , "transparent", true);

        let plane = new BABYLON.MeshBuilder.CreatePlane("TextPlane", {size: size, updatable: true}, scene);
        plane.material = new BABYLON.StandardMaterial("TextPlaneMaterial", scene);
        plane.material.specularColor = new BABYLON.Color3(0, 0, 0);
        plane.material.diffuseTexture = dynamicTexture;
        plane.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;

        return plane;
    };

    let axisX = BABYLON.Mesh.CreateLines("axisX", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(size, 0, 0),
        new BABYLON.Vector3(size * 0.95, 0.05 * size, 0),
        new BABYLON.Vector3(size, 0, 0),
        new BABYLON.Vector3(size * 0.95, -0.05 * size, 0)
    ], scene);
    axisX.color = new BABYLON.Color3(1, 0, 0);

    let xChar = makeTextPlane("X", "red", size / 8);
    xChar.position = new BABYLON.Vector3(0.9 * size, -0.05 * size, 0);
    xChar.parent = axisX;

    let axisY = BABYLON.Mesh.CreateLines("axisY", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(0, size, 0),
        new BABYLON.Vector3( -0.05 * size, size * 0.95, 0),
        new BABYLON.Vector3(0, size, 0),
        new BABYLON.Vector3( 0.05 * size, size * 0.95, 0)
    ], scene);
    axisY.color = new BABYLON.Color3(0, 1, 0);

    let yChar = makeTextPlane("Y", "green", size / 8);
    yChar.position = new BABYLON.Vector3(0, 0.9 * size, -0.05 * size);
    yChar.parent = axisY;

    let axisZ = BABYLON.Mesh.CreateLines("axisZ", [
        new BABYLON.Vector3.Zero(),
        new BABYLON.Vector3(0, 0, size),
        new BABYLON.Vector3( 0 , -0.05 * size, size * 0.95),
        new BABYLON.Vector3(0, 0, size),
        new BABYLON.Vector3( 0, 0.05 * size, size * 0.95)
    ], scene);
    axisZ.color = new BABYLON.Color3(0, 0, 1);

    let zChar = makeTextPlane("Z", "blue", size / 8);
    zChar.position = new BABYLON.Vector3(0, 0.05 * size, 0.9 * size);
    zChar.parent = axisZ;

    return [axisX, axisY, axisZ];
}

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, 30, 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 knot1 = BABYLON.MeshBuilder.CreateTorusKnot("knot1", {p: 1, q: 1}, scene);
    knot1.material = mat;
    knot1.position = new BABYLON.Vector3(-16, 0, 0);
    let axis_origin1 = createLocalAxes(5, scene);
    axis_origin1.parent = knot1;

    let knot2 = BABYLON.MeshBuilder.CreateTorusKnot("knot2", {p: 1, q: 2}, scene);
    knot2.material = mat;
    knot2.position = new BABYLON.Vector3(-8, 0, 0);
    let axis_origin2 = createLocalAxes(5, scene);
    axis_origin2.parent = knot2;

    let knot3 = BABYLON.MeshBuilder.CreateTorusKnot("knot3", {p: 1, q: 3}, scene);
    knot3.material = mat;
    knot3.position = new BABYLON.Vector3(0, 0, 0);
    let axis_origin3 = createLocalAxes(5, scene);
    axis_origin3.parent = knot3;

    let knot4 = BABYLON.MeshBuilder.CreateTorusKnot("knot4", {p: 1, q: 4}, scene);
    knot4.material = mat;
    knot4.position = new BABYLON.Vector3(8, 0, 0);
    let axis_origin4 = createLocalAxes(5, scene);
    axis_origin4.parent = knot4;

    let knot5 = BABYLON.MeshBuilder.CreateTorusKnot("knot5", {p: 1, q: 5}, scene);
    knot5.material = mat;
    knot5.position = new BABYLON.Vector3(16, 0, 0);
    let axis_origin5 = createLocalAxes(5, scene);
    axis_origin5.parent = knot5;

    return scene;
};

var scene6 = createScene6(canvas6, engine6);

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

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

<canvas id="renderCanvas6"></canvas>
Figure 7. From left to right, the values for q are 1, 2, 3, 4, and 5.

Updatable

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

SideOrientation

The sideOrientation property allows us to make our torus knot double-sided. The default value is FRONTSIDE.

The different values for the sideOrientation property are:

FrontUVs and BackUVs

If the torus knot is double-sided, we can choose what parts of a texture we 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 canvas7 = document.getElementById("renderCanvas7");
var engine7 = new BABYLON.Engine(canvas7, true);

function createScene7(canvas, engine){
    let scene = new BABYLON.Scene(engine);
    scene.clearColor = new BABYLON.Color3(1, 1, 1);
    let camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 4, Math.PI / 4, 8, 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);

    let torusKnot = BABYLON.MeshBuilder.CreateTorusKnot("torusKnot", {frontUVs: front, backUVs: back}, scene);
    torusKnot.material = mat;
    
    return scene;
};

var scene7 = createScene7(canvas7, engine7);

engine7.runRenderLoop(function(){
    scene7.render();
});

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

<canvas id="renderCanvas7"></canvas>
Figure 8. A texture is applied to the torus knot using the frontUVs and backUVs properties.

References