본문 바로가기
카테고리 없음

바빌론JS - 마을 꾸미기

지난 게시글에는 집을 만들고 표면을 꾸며보았지요.

https://itadventure.tistory.com/688

 

바빌론 JS - 집의 벽 표면 지대로.

지난 게시글에서는 집을 만들어 보았었는데요.https://itadventure.tistory.com/687 바빌론JS - 땅위에 집 만들기 ( 자바스크립트로 )오늘은 공식 튜토리얼의 집 짓기(영문)편을 크레이가 소화한 내용을 재

itadventure.tistory.com

이번 시간에는 만들어진 집을 이용하여 마을을 꾸며보겠습니다.


박스와 지붕 합치기

다양한 위치에 집을 배치하려면 박스과 지붕을 합쳐놓는 것이 좋습니다.
그래서 하나의 house 라는 모델을 다루는 것이 편리한데요.

두개의 모델을 합치는 명령은 BABYLON.Mesh.MergeMeshes 입니다.

지난번 함수화한 코드 3줄 중에서, box 와 root 2개 모델을 합칠 겁니다.

const ground = buildGround();   // 바닥
const box = buildBox(); // 상자
const roof = buildRoof();   // 지붕

2개의 모델을 합치는 코드를 추가해 봅시다.

const house = BABYLON.Mesh.MergeMeshes([box, roof]);

엇 그러자, 이게 무슨 일입니까?
지붕이.. 지붕이...

마지 지붕이 벽에 물들어 버린 것처럼 이상하게 되버렸군요.
그것은 앞쪽 박스의 매트리얼만 유지되기 때문인데요.
만일 합치는 순서를 바꾸면,

const house = BABYLON.Mesh.MergeMeshes([roof, box]);

아래와 같이 되버립니다. 이것도 이상하지요? :)

2개 모델의 매트리얼을 원본 그대로 유지하려면
멀티 매트리얼(Multi Material)을 유지하도록 부가 옵션을 주어야 하는데요.
바로 아래 코드입니다.

const house = BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);

결과눈 대성공!

그리고 바빌론 공식 사이트의 튜토리얼대로 하려면 FaceUV 코드를 약간 수정해야 합니다.
FaceUV 코드를 아래와 같이 수정해 주세요.

faceUV[2] = new BABYLON.Vector4(0.5, 0.0, 0.75, 1.0);
faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0);
faceUV[0] = new BABYLON.Vector4(0.25, 0, 0.5, 1.0);
faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0);

이제 house 도 코드를 합쳐서 더 깔끔하게 하겠습니다.

박스, 지붕, 집 3줄의 코드를

const box = buildBox(); // 상자
const roof = buildRoof();   // 지붕
const house = BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);

 

아래와 같이 변경하고

const house = buildHouse(); // 집

지난번 함수화한 목록 아래 새로운 함수를 추가해 주세요. 참고로 결과는 같습니다.

// 집1 함수화
const buildHouse = () => {
    const box = buildBox(); // 상자2
    const roof = buildRoof();   // 지붕2
    // 메시 합치기, 멀티 메터리얼
    return BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);
}

집 복제하기

집을 여기 저기 배치하여 마을을 꾸미려면 집을 복제하는 기술이 필요한데요.
집을 복제하는 코드는 2가지가 있습니다.
clonecreateInstance 입니다.

2가지가 약간의 차이점이 있는데요.
clone 은 집을 복제해 새로운 복사본을 만드는 방법이고,

const houseClone = house.clone("houseClone");

createInstance 는 복제는 하되 원본의 모양을 그대로 가져다 쓰는 방법입니다.

const houseInstance = house.createInstance("houseInstance");

전자는 복제본의 변형이 가능하지만,
후자는 복제본을 변형하면 원본도 함께 바뀌는데요.
후자는 메모리를 적게 사용하는 장점이 있습니다.
그러다 보니 속도도 더 빠르겠지요.
여기서는 후자를 사용하겠습니다.

아래 코드를 추가하면 집이 복제되는데요.

const houseInstance = house.createInstance("houseInstance");

원본과 위치가 똑같아 복제한 티가 안 납니다.
아래 코드를 추가해 위치를 변경해주면,

houseInstance.position.x = 2;

집이 2채가 됩니다.


방향 돌리기

복제한 집을 왼쪽으로 45도 돌리려면 어떻게 할까요?
바빌론JS에서 동서남북 방향 회전시 Y축을 기준으로 하면 되는데요.
rotation.y 를 사용하면 됩니다.
아래 코드를 추가해 보세요.

houseInstance.rotation.y = Math.PI / 4; // 180도 / 4

참고로 Math.PI 는 180 도 입니다. 180 도를 4로 나누면 45도가 되지요.


2번째 집 스타일 추가

공식 튜토리얼에서 작은 집(1x1) 과 큰 집(1x2)을 만드는 부분이 있었는데요
그런게 있거니 하고 넘어갔었는데 이제 보니 마을 꾸미기에서 필요하더군요.

큰 집을 추가하였습니다. 작은 집과 별 다를건 없으니 상세 설명은 생략합니다.
아래 코드를 추가하고,

const house2 = buildHouse2(); // 큰 집
house2.position.x = -2.5;

함수 추가 영역에는 아래 코드를 추가해 보세요.

// 상자2 함수화
const buildBox2 = () => {
    const faceUV = [];
    faceUV[0] = new BABYLON.Vector4(0.25, 0.0, 0.75, 1.0);
    faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0);
    faceUV[2] = new BABYLON.Vector4(0.5, 0, 0.75, 1.0);
    faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0);
    
    var box = BABYLON.MeshBuilder.CreateBox("house", {size: 1, width:2, faceUV: faceUV, wrap: true});
    box.position.y = 0.5;
    
    // 박스 벽 매트리얼
    const wallMat = new BABYLON.StandardMaterial("wallMat");
    wallMat.diffuseTexture = new BABYLON.Texture("./wall-4.png");
    box.material = wallMat;

    return box;
}

// 지붕2 함수화
const buildRoof2 = () => {
    const roof = BABYLON.MeshBuilder.CreateCylinder("roof", {diameter: 1.3, height: 1.2, tessellation: 3});
    roof.scaling.x = 0.75;
    roof.scaling.y = 1.8;
    roof.rotation.z = Math.PI / 2;
    roof.position.y = 1.22;

    // 지붕 매트리얼
    const roofMat = new BABYLON.StandardMaterial("rootMat");
    roofMat.diffuseTexture = new BABYLON.Texture("./roof.jpg");
    roof.material = roofMat;

    return roof;
}

// 집2 함수화
const buildHouse2 = () => {
    const box2 = buildBox2(); // 상자2
    const roof2 = buildRoof2();   // 지붕2
    // 메시 합치기, 멀티 메터리얼
    return BABYLON.Mesh.MergeMeshes([box2, roof2], true, false, null, false, true);
}

아래와 같이 보이신다면 성공!


마을 꾸미기

땅이 좁기 때문에 크기를 20x20으로 키우겠습니다.

buildGround 함수에서 아래 코드를 찾아

const ground = BABYLON.MeshBuilder.CreateGround("crayground", {width: 10, height: 10});

아래와 같이 변경해 주세요.

const ground = BABYLON.MeshBuilder.CreateGround("crayground", {width: 20, height: 20});

그리고 복제를 테스트했던 아래 코드는 삭제해 주세요.

const houseInstance = house.createInstance("houseInstance");
houseInstance.position.x = 2;
houseInstance.rotation.y = Math.PI / 4; // 180도 / 4

넓은 땅에 집 2채만 덩그라니 남아 있을텐데요.

이 2채의 집도 숨기도록 합시다.
아래 코드를 찾아

const house2 = buildHouse2(); // 큰 집

그 아래 코드를 추가해 줍시다. setEnabled(false)는 모델을 보이지 않도록 감춰 줍니다,

house.setEnabled(false);
house2.setEnabled(false);

그리고 큰 집의 위치를 지정한 코드는 삭제합니다.

house2.position.x = -2.5;

이제 큰 땅에는 아무 집도 보이지는 않으나 보이지 않는 집이 투명인간처럼 존재합니다.

이제 집을 대량으로 복제해 봅시다.
집을 하나 하나 일일히 복제해서 방향을 돌리고 위치를 일일히 변경하는 코드 방법도 있지만
배열을 이용하면 편리합니다.

아래 코드를 추가해 주세요.

const places = []; //each entry is an array [house type, rotation, x, z]
places.push([1, -Math.PI / 16, -6.8, 2.5 ]);    // 작은 집(1)
places.push([2, -Math.PI / 16, -4.5, 3 ]);      // 큰 집(2)

const houses = [];
for (let i = 0; i < places.length; i++) {
    if (places[i][0] === 1)
        houses[i] = house.createInstance("house" + i);
    else
        houses[i] = house2.createInstance("house" + i);
    houses[i].rotation.y = places[i][1];
    houses[i].position.x = places[i][2];
    houses[i].position.z = places[i][3];
}

그러면 집2채가 추가됩니다.

아래 코드는 대괄호 기준으로 앞에서부터 집 유형, 회전각X좌표, Z좌표를 의미하는데요.

places.push([1, -Math.PI / 16, -6.8, 2.5 ]);

 

집 2채 추가하는데 왜 이런 요란한 코드를 사용하는지 모르겠다면 큰 오산입니다.
이제 아래와 같이 한줆씩 코드를 추가할 때마다 

집이 한 채씩 추가되거든요.

공식 튜토리얼의 집 배치 코드를 모두 적용하면,

const places = []; //each entry is an array [house type, rotation, x, z]
places.push([1, -Math.PI / 16, -6.8, 2.5 ]);    // 작은 집(1)
places.push([2, -Math.PI / 16, -4.5, 3 ]);      // 큰 집(2)
places.push([2, -Math.PI / 16, -1.5, 4 ]);
places.push([2, -Math.PI / 3, 1.5, 6 ]);
places.push([2, 15 * Math.PI / 16, -6.4, -1.5 ]);
places.push([1, 15 * Math.PI / 16, -4.1, -1 ]);
places.push([2, 15 * Math.PI / 16, -2.1, -0.5 ]);
places.push([1, 5 * Math.PI / 4, 0, -1 ]);
places.push([1, Math.PI + Math.PI / 2.5, 0.5, -3 ]);
places.push([2, Math.PI + Math.PI / 2.1, 0.75, -5 ]);
places.push([1, Math.PI + Math.PI / 2.25, 0.75, -7 ]);
places.push([2, Math.PI / 1.9, 4.75, -1 ]);
places.push([1, Math.PI / 1.95, 4.5, -3 ]);
places.push([2, Math.PI / 1.9, 4.75, -5 ]);
places.push([1, Math.PI / 1.9, 4.75, -7 ]);
places.push([2, -Math.PI / 3, 5.25, 2 ]);
places.push([1, -Math.PI / 3, 6, 4 ]);

와우~ 그럴싸한 마을이 완성되었네요.


마을 코드 함수화

앞으로 더 만들게 많기 때문에 마을을 생성하는 모든 코드를 함수화 적용하겠습니다.

아래 코드부터

const ground = buildGround();   // 바닥

아래 코드까지를

    houses[i].rotation.y = places[i][1];
    houses[i].position.x = places[i][2];
    houses[i].position.z = places[i][3];
}

buildDwellings() 이란 이름으로 함수화할텐데요.
코드 변경 사항이 많기 때문에 전체 코드 공개로 마무리하겠습니다.

const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas,true);
var createScene = function () {
// This creates a basic Babylon Scene object (non-mesh)
var scene = new BABYLON.Scene(engine);

// This creates and positions a free camera (non-mesh)
// var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);

var camera = new BABYLON.ArcRotateCamera("camera", BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(65), 10, BABYLON.Vector3.Zero(), scene);
camera.wheelDeltaPercentage = 0.01;

// This targets the camera to scene origin
camera.setTarget(BABYLON.Vector3.Zero());

// This attaches the camera to the canvas
camera.attachControl(canvas, true);

// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

// Default intensity is 1. Let's dim the light a small amount
light.intensity = 1.5;

buildDwellings(); // 마을

return scene;
};

// 바닥 함수화
const buildGround = () => {
    const ground = BABYLON.MeshBuilder.CreateGround("crayground", {width: 20, height: 20});
    const groundMat = new BABYLON.StandardMaterial("groundMat");  // 매트리얼 생성 ( 3D 에서는 모든 것이 메트리얼 )
    groundMat.diffuseTexture = new BABYLON.Texture("./brick.jpg"); // 매트리얼에 그림을 적용
    ground.material = groundMat;  // 땅의 매트리얼에 적용
    return ground;
}

// 상자 함수화
const buildBox = () => {
    const faceUV = [];
    faceUV[2] = new BABYLON.Vector4(0.5, 0.0, 0.75, 1.0);
    faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0);
    faceUV[0] = new BABYLON.Vector4(0.25, 0, 0.5, 1.0);
    faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0);
    
    var box = BABYLON.MeshBuilder.CreateBox("house", {size: 1, faceUV: faceUV, wrap: true});
    box.position.y = 0.5;
    
    // 박스 벽 매트리얼
    const wallMat = new BABYLON.StandardMaterial("wallMat");
    wallMat.diffuseTexture = new BABYLON.Texture("./wall-4.png");
    box.material = wallMat;

    return box;
}

// 지붕 함수화
const buildRoof = () => {
    const roof = BABYLON.MeshBuilder.CreateCylinder("roof", {diameter: 1.3, height: 1.2, tessellation: 3});
    roof.scaling.x = 0.75;
    roof.rotation.z = Math.PI / 2;
    roof.position.y = 1.22;

    // 지붕 매트리얼
    const roofMat = new BABYLON.StandardMaterial("rootMat");
    roofMat.diffuseTexture = new BABYLON.Texture("./roof.jpg");
    roof.material = roofMat;

    return roof;
}

// 집1 함수화
const buildHouse = () => {
    const box = buildBox(); // 상자2
    const roof = buildRoof();   // 지붕2
    // 메시 합치기, 멀티 메터리얼
    return BABYLON.Mesh.MergeMeshes([box, roof], true, false, null, false, true);
}

// 상자2 함수화
const buildBox2 = () => {
    const faceUV = [];
    faceUV[0] = new BABYLON.Vector4(0.25, 0.0, 0.75, 1.0);
    faceUV[1] = new BABYLON.Vector4(0.0, 0.0, 0.25, 1.0);
    faceUV[2] = new BABYLON.Vector4(0.5, 0, 0.75, 1.0);
    faceUV[3] = new BABYLON.Vector4(0.75, 0, 1.0, 1.0);
    
    var box = BABYLON.MeshBuilder.CreateBox("house", {size: 1, width:2, faceUV: faceUV, wrap: true});
    box.position.y = 0.5;
    
    // 박스 벽 매트리얼
    const wallMat = new BABYLON.StandardMaterial("wallMat");
    wallMat.diffuseTexture = new BABYLON.Texture("./wall-4.png");
    box.material = wallMat;

    return box;
}

// 지붕2 함수화
const buildRoof2 = () => {
    const roof = BABYLON.MeshBuilder.CreateCylinder("roof", {diameter: 1.3, height: 1.2, tessellation: 3});
    roof.scaling.x = 0.75;
    roof.scaling.y = 1.8;
    roof.rotation.z = Math.PI / 2;
    roof.position.y = 1.22;

    // 지붕 매트리얼
    const roofMat = new BABYLON.StandardMaterial("rootMat");
    roofMat.diffuseTexture = new BABYLON.Texture("./roof.jpg");
    roof.material = roofMat;

    return roof;
}

// 집2 함수화
const buildHouse2 = () => {
    const box2 = buildBox2(); // 상자2
    const roof2 = buildRoof2();   // 지붕2
    // 메시 합치기, 멀티 메터리얼
    return BABYLON.Mesh.MergeMeshes([box2, roof2], true, false, null, false, true);
}

// 마을
const buildDwellings = () => {
    const ground = buildGround();   // 바닥

    const house = buildHouse(); // 집
    const house2 = buildHouse2(); // 큰 집
    house.setEnabled(false);
    house2.setEnabled(false);
    
    const places = []; //each entry is an array [house type, rotation, x, z]
    places.push([1, -Math.PI / 16, -6.8, 2.5 ]);    // 작은 집(1)
    places.push([2, -Math.PI / 16, -4.5, 3 ]);      // 큰 집(2)
    places.push([2, -Math.PI / 16, -1.5, 4 ]);
    places.push([2, -Math.PI / 3, 1.5, 6 ]);
    places.push([2, 15 * Math.PI / 16, -6.4, -1.5 ]);
    places.push([1, 15 * Math.PI / 16, -4.1, -1 ]);
    places.push([2, 15 * Math.PI / 16, -2.1, -0.5 ]);
    places.push([1, 5 * Math.PI / 4, 0, -1 ]);
    places.push([1, Math.PI + Math.PI / 2.5, 0.5, -3 ]);
    places.push([2, Math.PI + Math.PI / 2.1, 0.75, -5 ]);
    places.push([1, Math.PI + Math.PI / 2.25, 0.75, -7 ]);
    places.push([2, Math.PI / 1.9, 4.75, -1 ]);
    places.push([1, Math.PI / 1.95, 4.5, -3 ]);
    places.push([2, Math.PI / 1.9, 4.75, -5 ]);
    places.push([1, Math.PI / 1.9, 4.75, -7 ]);
    places.push([2, -Math.PI / 3, 5.25, 2 ]);
    places.push([1, -Math.PI / 3, 6, 4 ]);
    
    const houses = [];
    for (let i = 0; i < places.length; i++) {
        if (places[i][0] === 1)
            houses[i] = house.createInstance("house" + i);
        else
            houses[i] = house2.createInstance("house" + i);
        houses[i].rotation.y = places[i][1];
        houses[i].position.x = places[i][2];
        houses[i].position.z = places[i][3];
    }
    
}


const scene = createScene();

engine.runRenderLoop(function () {
    scene.render();
}
);
window.addEventListener("resize", function () {
    engine.resize();
}
);

var camera = new BABYLON.ArcRotateCamera("camera", BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(65), 10, BABYLON.Vector3.Zero(), scene);

실습이 어려우신 분은 제가 준비한 아래 결과물 페이지를 참조해 주세요.

http://dreamplan7.cafe24.com/babylon/ex7/


마무~리

코드가 점점 심화되어 가는 듯합니다.
찾으시는 분에게 흥미로운 컨텐츠가 되는지 궁금하군요 :)
오늘도 방문해주신 모든 분들께 감사드립니다~


다음 게시글 : https://itadventure.tistory.com/690

 

바빌론JS - 마을 저장 & 열기

지난 시간에는 멋진 마을을 꾸며 보았습니다.https://itadventure.tistory.com/689 바빌론JS - 마을 꾸미기지난 게시글에는 집을 만들고 표면을 꾸며보았지요.https://itadventure.tistory.com/688 바빌론 JS - 집의

itadventure.tistory.com