babylon.js 學習筆記(5)

前面我們畫的小房子,基本上都是用內置的標準形狀組合而成,但並非所有對象都這麼簡單,今天我們來畫一個小汽車,汽車由多個零件組成,控制這些零件的縮放、位置、旋轉,如果每個都單獨用代碼來修改position/roration/scaling,未免太複雜,幸好babylon.js中,對象有所謂的child/parent 關係。簡單來說,如果A是B的parent,則對A的任何位置/縮放/旋轉,其child也會同步受影響,但child可以在parent的基礎上,再獨立疊加新變化。有沒有發現,這很符合遺傳學,孩子必然長得象父母,但是又有些自己的特徵。

一、理解 parent / child 關係

const createScene = () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2.2, Math.PI / 2.5, 15, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);

    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0));

    //方塊6個面的顏色
    const faceColors = [];
    faceColors[0] = BABYLON.Color3.Blue();
    faceColors[1] = BABYLON.Color3.Teal()
    faceColors[2] = BABYLON.Color3.Red();
    faceColors[3] = BABYLON.Color3.Purple();
    faceColors[4] = BABYLON.Color3.Green();
    faceColors[5] = BABYLON.Color3.Yellow();

    const boxParent = BABYLON.MeshBuilder.CreateBox("Box", { faceColors: faceColors });
    const boxChild = BABYLON.MeshBuilder.CreateBox("Box", { size: 0.5, faceColors: faceColors });
    //小方塊是大方塊的child
    boxChild.setParent(boxParent);

    //child的獨立特徵
    boxChild.position.x = 0;
    boxChild.position.y = 2;
    boxChild.position.z = 0;

    boxChild.rotation.x = Math.PI / 4;
    boxChild.rotation.y = Math.PI / 4;
    boxChild.rotation.z = Math.PI / 4;

    //parent的位置變化,將影響child
    boxParent.position.x = 2;
    boxParent.position.y = 0;
    boxParent.position.z = 0;

    boxParent.rotation.x = 0;
    boxParent.rotation.y = 0;
    boxParent.rotation.z = -Math.PI / 4;

    //輔助座標軸,方便理解
    const boxChildAxes = localAxes(1.5, scene);
    boxChildAxes.parent = boxChild;
    showAxis(5, scene);
    return scene;
}




//座標軸
const showAxis = (size, scene) => {
    const makeTextPlane = (text, color, size) => {
        const dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", 50, scene, true);
        dynamicTexture.hasAlpha = true;
        dynamicTexture.drawText(text, 5, 40, "bold 36px Arial", color, "transparent", true);
        const plane = new BABYLON.Mesh.CreatePlane("TextPlane", size, scene, true);
        plane.material = new BABYLON.StandardMaterial("TextPlaneMaterial", scene);
        plane.material.backFaceCulling = false;
        plane.material.specularColor = new BABYLON.Color3(0, 0, 0);
        plane.material.diffuseTexture = dynamicTexture;
        return plane;
    };

    const 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)
    ]);
    axisX.color = new BABYLON.Color3(1, 0, 0);
    const xChar = makeTextPlane("X", "white", size / 8);
    xChar.position = new BABYLON.Vector3(0.9 * size, -0.05 * size, 0);

    const 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)
    ]);
    axisY.color = new BABYLON.Color3(0, 1, 0);
    const yChar = makeTextPlane("Y", "white", size / 8);
    yChar.position = new BABYLON.Vector3(0, 0.9 * size, -0.05 * size);

    const 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)
    ]);
    axisZ.color = new BABYLON.Color3(0, 1, 1);
    const zChar = makeTextPlane("Z", "white", size / 8);
    zChar.position = new BABYLON.Vector3(0, 0.05 * size, 0.9 * size);
};

//小方塊的座標軸
localAxes = (size, scene) => {
    const local_axisX = BABYLON.Mesh.CreateLines("local_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);
    local_axisX.color = new BABYLON.Color3(1, 0, 0);

    local_axisY = BABYLON.Mesh.CreateLines("local_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);
    local_axisY.color = new BABYLON.Color3(0, 1, 0);

    const local_axisZ = BABYLON.Mesh.CreateLines("local_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);
    local_axisZ.color = new BABYLON.Color3(0, 1, 1);


    const local_origin = new BABYLON.TransformNode("local_origin");

    local_axisX.parent = local_origin;
    local_axisY.parent = local_origin;
    local_axisZ.parent = local_origin;

    return local_origin;
}

代碼有點長,座標軸的部分可以先不管,只看createScene即可。

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/01.html

 

二、理解 ExtrudePolygon 

ExtrudePolygon方法可以畫出一些不規則形狀,比如下面:

const buildCar = () => {
    //base
    const outline = [
        new BABYLON.Vector3(-0.3, 0, -0.1),
        new BABYLON.Vector3(0.2, 0, -0.1),
    ]

    //top
    outline.push(new BABYLON.Vector3(0, 0, 0.1));
    outline.push(new BABYLON.Vector3(-0.3, 0, 0.1));

    //back formed automatically
    const car = BABYLON.MeshBuilder.ExtrudePolygon("car", { shape: outline, depth: 0.2 });

    return car;
}

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/02.html

具體畫的過程,可以結合下面的圖理解:簡單來說,A->B->C->D 先畫出1個梯形,然後向下拉長,就得到了這個模型。

 再完善一下,把車頭及輪子加上

const createScene = () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0));

    //造車身
    const car = buildCar();
    //安裝輪子
    buildWheel(car);

    showAxis(0.8, scene);

    return scene;
}

//車身
const buildCar = () => {
    //base
    const outline = [
        new BABYLON.Vector3(-0.3, 0, -0.1),
        new BABYLON.Vector3(0.2, 0, -0.1),
    ]

    //curved front
    for (let i = 0; i < 20; i++) {
        outline.push(new BABYLON.Vector3(0.2 * Math.cos(i * Math.PI / 40), 0, 0.2 * Math.sin(i * Math.PI / 40) - 0.1));
    }

    //top
    outline.push(new BABYLON.Vector3(0, 0, 0.1));
    outline.push(new BABYLON.Vector3(-0.3, 0, 0.1));

    //back formed automatically
    const car = BABYLON.MeshBuilder.ExtrudePolygon("car", { shape: outline, depth: 0.2 });

    return car;
}


//輪子
const buildWheel = (car) => {
    const wheelRB = BABYLON.MeshBuilder.CreateCylinder("wheelRB", { diameter: 0.125, height: 0.05 })
    wheelRB.parent = car;
    wheelRB.position.z = -0.1;
    wheelRB.position.x = -0.2;
    wheelRB.position.y = 0.035;

    const wheelRF = wheelRB.clone("wheelRF");
    wheelRF.position.x = 0.1;

    const wheelLB = wheelRB.clone("wheelLB");
    wheelLB.position.y = -0.2 - 0.035;

    const wheelLF = wheelRF.clone("wheelLF");
    wheelLF.position.y = -0.2 - 0.035;
}

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/03.html

最後再加上貼圖:

const createScene = () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0.2, -0.20, 1.5));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0));

    const car = buildCar();
    car.rotation.x = -Math.PI / 2;

    showAxis(0.6, scene);

    return scene;
}

const buildCar = () => {
    //base
    const outline = [
        new BABYLON.Vector3(-0.3, 0, -0.1),
        new BABYLON.Vector3(0.2, 0, -0.1),
    ]

    //curved front
    for (let i = 0; i < 20; i++) {
        outline.push(new BABYLON.Vector3(0.2 * Math.cos(i * Math.PI / 40), 0, 0.2 * Math.sin(i * Math.PI / 40) - 0.1));
    }

    //top
    outline.push(new BABYLON.Vector3(0, 0, 0.1));
    outline.push(new BABYLON.Vector3(-0.3, 0, 0.1));

    //car face UVs
    const faceUV = [];
    faceUV[0] = new BABYLON.Vector4(0, 0.5, 0.38, 1);
    faceUV[1] = new BABYLON.Vector4(0, 0, 1, 0.5);
    faceUV[2] = new BABYLON.Vector4(0.38, 1, 0, 0.5);

    //car material
    const carMat = new BABYLON.StandardMaterial("carMat");
    carMat.diffuseTexture = new BABYLON.Texture("../assets/img/car.png");

    //back formed automatically
    const car = BABYLON.MeshBuilder.ExtrudePolygon("car", { shape: outline, depth: 0.2, faceUV: faceUV, wrap: true });
    car.material = carMat;


    //wheel face UVs
    const wheelUV = [];
    wheelUV[0] = new BABYLON.Vector4(0, 0, 1, 1);
    wheelUV[1] = new BABYLON.Vector4(0, 0.5, 0, 0.5);
    wheelUV[2] = new BABYLON.Vector4(0, 0, 1, 1);

    //car material
    const wheelMat = new BABYLON.StandardMaterial("wheelMat");
    wheelMat.diffuseTexture = new BABYLON.Texture("../assets/img/wheel.png");

    const wheelRB = BABYLON.MeshBuilder.CreateCylinder("wheelRB", { diameter: 0.125, height: 0.05, faceUV: wheelUV })
    wheelRB.material = wheelMat;

    wheelRB.parent = car;
    wheelRB.position.z = -0.1;
    wheelRB.position.x = -0.2;
    wheelRB.position.y = 0.035;

    const wheelRF = wheelRB.clone("wheelRF");
    wheelRF.position.x = 0.1;

    const wheelLB = wheelRB.clone("wheelLB");
    wheelLB.position.y = -0.2 - 0.035;

    const wheelLF = wheelRF.clone("wheelLF");
    wheelLF.position.y = -0.2 - 0.035;

    return car;
}

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/04.html

 

三、輪子動畫

既然是汽車,輪子肯定得轉起來,可以藉助Animation屬性來實現

const createScene = () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0));

    const wheel = buildWheel();
    wheelAnimation(scene, wheel);

    showAxis(0.6, scene);

    return scene;
}

//造輪子
const buildWheel = () => {

    const wheelUV = [];
    wheelUV[0] = new BABYLON.Vector4(0, 0, 1, 1);
    wheelUV[1] = new BABYLON.Vector4(0, 0.5, 0, 0.5);
    wheelUV[2] = new BABYLON.Vector4(0, 0, 1, 1);


    const wheelMat = new BABYLON.StandardMaterial("wheelMat");
    wheelMat.diffuseTexture = new BABYLON.Texture("../assets/img/wheel.png");

    const wheelRB = BABYLON.MeshBuilder.CreateCylinder("wheelRB", { diameter: 0.125, height: 0.05, faceUV: wheelUV })
    wheelRB.material = wheelMat;

    return wheelRB;
}

//輪子轉動
const wheelAnimation = (scene, wheel) => {
    //定義一個動畫,每秒30幀,繞y軸轉動
    const animWheel = new BABYLON.Animation("wheelAnimation", "rotation.y",
        30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

    //動畫關鍵幀
    const wheelKeys = [];

    //起始幀
    wheelKeys.push({
        frame: 0,
        value: 0
    });

    //截止幀(即:第30幀,轉到360度)
    wheelKeys.push({
        frame: 30,
        value: 2 * Math.PI
    });

    //設置關鍵幀
    animWheel.setKeys(wheelKeys);

    //將wheel與動畫關聯
    wheel.animations = [];
    wheel.animations.push(animWheel);

    //開始動畫,最後的true表示循環播放
    scene.beginAnimation(wheel, 0, 30, true);
}

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/05.html

把4個輪子都加上動畫:

const createScene = () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 2, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));

    const car = buildCar();
    const wheels = buildWheels(car);
    car.rotation.x = -Math.PI / 2;
    wheelAnimation(scene, wheels);

    showAxis(0.6, scene);

    return scene;
}

const buildCar = () => {
    //base
    const outline = [
        new BABYLON.Vector3(-0.3, 0, -0.1),
        new BABYLON.Vector3(0.2, 0, -0.1),
    ]

    //curved front
    for (let i = 0; i < 20; i++) {
        outline.push(new BABYLON.Vector3(0.2 * Math.cos(i * Math.PI / 40), 0, 0.2 * Math.sin(i * Math.PI / 40) - 0.1));
    }

    //top
    outline.push(new BABYLON.Vector3(0, 0, 0.1));
    outline.push(new BABYLON.Vector3(-0.3, 0, 0.1));

    //car face UVs
    const faceUV = [];
    faceUV[0] = new BABYLON.Vector4(0, 0.5, 0.38, 1);
    faceUV[1] = new BABYLON.Vector4(0, 0, 1, 0.5);
    faceUV[2] = new BABYLON.Vector4(0.38, 1, 0, 0.5);

    //car material
    const carMat = new BABYLON.StandardMaterial("carMat");
    carMat.diffuseTexture = new BABYLON.Texture("../assets/img/car.png");

    //back formed automatically
    const car = BABYLON.MeshBuilder.ExtrudePolygon("car", { shape: outline, depth: 0.2, faceUV: faceUV, wrap: true });
    car.material = carMat;

    return car;
}


const buildWheels = (car) => {
    //wheel face UVs
    const wheelUV = [];
    wheelUV[0] = new BABYLON.Vector4(0, 0, 1, 1);
    wheelUV[1] = new BABYLON.Vector4(0, 0.5, 0, 0.5);
    wheelUV[2] = new BABYLON.Vector4(0, 0, 1, 1);

    //car material
    const wheelMat = new BABYLON.StandardMaterial("wheelMat");
    wheelMat.diffuseTexture = new BABYLON.Texture("../assets/img/wheel.png");

    const wheelRB = BABYLON.MeshBuilder.CreateCylinder("wheelRB", { diameter: 0.125, height: 0.05, faceUV: wheelUV })
    wheelRB.material = wheelMat;

    wheelRB.parent = car;
    wheelRB.position.z = -0.1;
    wheelRB.position.x = -0.2;
    wheelRB.position.y = 0.035;

    const wheelRF = wheelRB.clone("wheelRF");
    wheelRF.position.x = 0.1;

    const wheelLB = wheelRB.clone("wheelLB");
    wheelLB.position.y = -0.2 - 0.035;

    const wheelLF = wheelRF.clone("wheelLF");
    wheelLF.position.y = -0.2 - 0.035;

    const wheels = [];
    wheels.push(wheelRB);
    wheels.push(wheelRF);
    wheels.push(wheelLB);
    wheels.push(wheelLF);

    return wheels;
}


//輪子轉動
const wheelAnimation = (scene, wheels) => {
    //定義一個動畫,每秒30幀,繞y軸轉動
    const animWheel = new BABYLON.Animation("wheelAnimation", "rotation.y",
        30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

    //動畫關鍵幀
    const wheelKeys = [];

    //起始幀
    wheelKeys.push({
        frame: 0,
        value: 0
    });

    //截止幀(即:第30幀,轉到360度)
    wheelKeys.push({
        frame: 30,
        value: 2 * Math.PI
    });

    //設置關鍵幀
    animWheel.setKeys(wheelKeys);


    for (let i = 0; i < wheels.length; i++) {
        //將wheel與動畫關聯
        wheels[i].animations = [];
        wheels[i].animations.push(animWheel);

        //開始動畫,最後的true表示循環播放
        scene.beginAnimation(wheels[i], 0, 30, true);
    }
}

在線地址:https://yjmyzz.github.io/babylon_js_study/day05/06.html

參考文檔:https://doc.babylonjs.com/features/introductionToFeatures/chap3/carmat

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章