본문 바로가기

Dev/graphics

[playcanvas] Entity Picking을 이용해본 Gizmo 형태

https://velbi.io/154 이 글을 번역하면서, 스스로 익히기위해

Entity Picking을 사용해 Gizmo 형태만 한번 만들어 봤습니다. (나중에 Drag도 추가해보겠습니다.)

 

클릭하면 흰색으로 바뀌게 했습니다. 

에디터에서

카메라에 picker 스크립트컴포넌트를 등록하고 거기에 picker.js 를 넣어놨습니다.

그리고 각 엔티티에다가는 changeColor.js 를 넣어놨습니다.

picker.js 

var Picker = pc.createScript('picker');

// initialize code called once per entity
Picker.prototype.initialize = function() {
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
};

// update code called every frame
Picker.prototype.onSelect = function(e) {
   var from = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.nearClip);
   var to = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.farClip);

    this.app.systems.rigidbody.raycastFirst(from, to, function (result) {
        var pickedEntity = result.entity;
        pickedEntity.script.changeColor.shine();
    });
};

먼저 화면캔버스 어디든지 클릭하면 onSelect 함수가 (이 함수의 이름은 내가 정의하는거임) 실행되고 엔티티를 클릭했을때는 raycastFirst함수가 실행됩니다.

그리고 screenToWorld 함수는 카메라로 부터 목표까지 ray를 쏴야하는데 그 시작위치와 목표위치를 구하기 위해 

카메라 기준으로 볼땐 2차원 좌표이기 때문에, 3차원 좌표로 바꿔줍니다. 그럼 아래 그림의 두가 판이있기때문에 3차원 공간내에서 두가지의 좌표를 얻게 됩니다.  시작 좌표와 목표 좌표 .. 물론 모델은 이사이에 존재하기 때문에 레이를 발생시키기 좋은 위치입니다. 레이가 충돌 컴포넌트를 만나면, pc.RaycastResult를 리턴해서 엔티티를 가져올수있게 됩니다.

그럼 제가 클릭한 엔티티를 가져올수 있게됩니다. 그럴때 마다 shine()함수를 실행시켜줍니다.

var ChangeColor = pc.createScript('changeColor');

// initialize code called once per entity
ChangeColor.prototype.initialize = function() {
    
};

// update code called every frame
ChangeColor.prototype.update = function(dt) {
    
};


ChangeColor.prototype.checkIntegrity = function() {
    for (i=0; i< this.entity.parent.children.length; i++) {
        var sibling = this.entity.parent.children[i];
        if (sibling.model.material.diffuse.r === 255 && sibling.model.material.diffuse.g === 255 && sibling.model.material.diffuse.b === 255) {
            return false;
        }   
    }
    return true;
};

ChangeColor.prototype.shine = function() {
    // Selected entity model material diffuse
    var diffuse = this.entity.model.material.diffuse;
    var checker =  this.checkIntegrity();
    if (checker === true) {
        diffuse.set(255, 255, 255); // Using trick by 255
        
    } else {
        // Return to original Color of all (red, green, blue)
        var siblings = this.entity.parent.children;
        siblings.forEach(function (v, i) {
           if (v.name === 'redBox'){
               v.model.material.diffuse.set(1, 0, 0, 1);
           } else if (v.name === 'greenBox') {
               v.model.material.diffuse.set(0, 1, 0, 1);
           } else if (v.name == 'blueBox'){
               v.model.material.diffuse.set(0, 0, 1, 1);
           } else {}
            v.model.material.update();
        });
        diffuse.set(255, 255, 255); 
    }
    this.entity.model.material.update();            
};

 shine 함수는 이렇게 작성했습니다. 

0. 이미 선택한 막대기가 있는지 없는지 확인해주는 함수를 작성합니다. (checkIntegrity)

1. 이미선택한 막대기가 없으면, 그냥 흰색으로 색상을 변경합니다. diffuse 색상은 원래 0~1값을 넣어야하는데 255를 넣으면  빛과 상관없이 완전흰색이 되는 버그(?)가 있어서 그냥 사용했습닌다. 

2. 만약 이미 선택한 막대기가 없으면, 초기화 (모두 원래색상 rgb로 돌아오게) 해줍니다. 그리고는 다시 선택한 막대기에 색상을 변경합니다. 


깨달은점

1. 함수이름 을 작성하는데, searchFalse로 할지 checkIntegrity 로 할지 잠시고민했다. 코드상으로는 searchFalse가 맞고 의미적으로는 check Integiry가 맞다.  매개변수랑 인자이름랑 다른거과 비슷한 맥락인것같다. 나중에 싹다 정리해서 변수와 함수 이름 적는거에 대해 글을 써야겠다.

2. 또하나는 데이터든 3D scene에서든 어떤 뭉댕이를 검사하는 탐색 기법이 내가 스스로 많이 쓰는것 같다는 것이다.  이 데이터에 튀는 값이 없는지 검사한다든가, 이 데이터가 무결한지 검사하는건데, 이럴때 filter, map, reduce, forEach  다 좋지만 그냥 for 문을 쓰도록 하는게 좋을것 같다. 왜냐면 문제가 있으면 루프를 중단해야하는경우가 생기는데 이럴때 안에 함수를 콜백으로 넣어주면, 함수만 빠져나오고 루프를 못빠져나오는 경우가 있다. 뭐 .. 내가 js 에 아직 덜 익수해서 그런거지만 이런 무결성 체크 하는 경우에는 일단 for 문을 사용해서 작성하면 좋을것 같다.


언젠가는 이렇게 구현할수있길

* 충돌 Collision 컴포넌트가 등록되어있어야함 그래야 레이가 충돌을 감지할수있음