Microsoft Visual Simulation 사용자가이드및튜토리얼 Microsoft 시뮬레이션개발환경가이드및튜토리얼은아래의항목으로구성됩니다. 목차 1. 시작하기 2. 사용자가이드 3. 시뮬레이션툴및유틸리티 4. 시뮬레이션튜토리얼 1. 시작하기 Microsoft Visual Simulation 시작하기 로봇을개발하는데있어서하나의심각한문제는로봇자체가고가의하드웨어이고대부분한대밖에없다는사실입니다. 이러한문제로인하여, 개발된프로그램들에대해사전에충분한테스트를수행할수없고많은기간과비용이소요되고있습니다. MSRS 는이러한문제를해결하고개발자들에게사전에충분히테스트할수있고재현할수있는환경을제공하기위해시뮬레이션환경을제공합니다. MSRS 의시뮬레이터는물질세계에서의로봇에적용되는중요한요소들을모두시뮬레이션상황하에서구현해낼수있습니다. 기본적으로, 공간과중력, 마찰, 탄성계수뿐만이아니라, 모터와센서등의기본적인작용등도구현이가능합니다. MSRS 의시뮬레이션환경은 Ageia 사의 PhysX 엔진과 Microsoft XNA 프레임워크를기반으로하며, 하드웨어로봇과동일하게작동되는소프트웨어로봇을시뮬레이터상에서개발할수있도록함으로써, 로봇개발에있어서기간과비용을대폭감소시키고, 개발결과물에대한생산성과코드의품질을대폭향상시킵니다. 또한하드웨어로봇없이시뮬레이션환경구성만으로로봇주행알고리즘의연구와같은다양한형태의전문화된알고리즘연구에도활용될수있으므로, 알고리즘및지능형서비스에특화된개발이가능하도록지원합니다. MSRS 시뮬레이션환경에서는간단한물리객체 (Entity) 의추가를통해로봇에필요한모터, 휠, 센서등의기능들을손쉽게구현해낼수있습니다. 1
로봇객체의구현화면 또한하늘, 산, 테이블, 벽등의다양한사물들과환경등을추가하거나설정할수있습니다. 2
테이블객체를추가한화면 MSRS 의시뮬레이션환경에서는기본적으로제공되는 Simple Dashboard 서비스를통하여, 시뮬레이터상의로봇을제어하거나레이저스캐닝데이터등을확인할수있습니다. 로봇에연결하여레이저스캐닝센서데이터를읽고있는화면 MSRS 의시뮬레이션환경에서는단순한형태의물체뿐만이아니라복잡한형태의사물도설계데이터를변환하여손쉽게반영할수있습니다. 3
복잡한사물을시뮬레이션환경에서구현한화면 MSRS 의시뮬레이션환경에서는한대의로봇을시뮬레이션하는것뿐만이아니라동시에여러대의로봇을시뮬레이션하고각각의로봇들을제어하는작업들을손쉽게진행할수있습니다. 4
4 대의로봇들을동시에구현한화면 MSRS 의시뮬레이션환경에서는평지뿐만이아니라산과같은다양한조건의로봇이동환경을추가할수있습니다. 5
골짜기와같은환경을추가한화면 6
2. 사용자가이드 Microsoft 비주얼시뮬레이션환경시작하기 비주얼시뮬레이션환경을경험해보기위해서는 MSRS 의시작메뉴중에서 Visual Simulation Environment 메뉴를클릭한후하부메뉴에있는여러가지의시뮬레이션항목중하나를선택하여실행합니다. MSRS 시작메뉴에는시뮬레이션환경을처음접하는개발자가시뮬레이션환경을이해할수있도록대표적인시뮬레이션활용사례들을모아놓았습니다. 시뮬레이션환경은 MSRS 의하나의파트너서비스로서개발자가개발한시뮬레이션모듈이실행될때같이실행되며, 일반적인서비스를구동하는것과같이메니페스트파일을통해구동이될수있고, 파트너서비스등록을통해구동이되기도합니다. 시뮬레이터가실행될때, 왼쪽마우스버튼을클릭한채로포인터를스크린상에서움직여가면서카메라의뷰포인트를변경시킬수있습니다. 이러한변경은카메라자체의포지션을변경시키지않고단순히카메라가바라보는방향만을변경시킵니다. 카메라를위치를변경시키기위해서는아래와같은키보드명령을사용하여변경시킬수있습니다. w s a d q e 앞으로이동뒤로이동왼쪽으로이동오른쪽으로이동위로이동아래로이동 7
Microsoft 비주얼시뮬레이션환경메뉴 비주얼시뮬레이션환경에서는아래의메뉴들이제공됩니다. File 메뉴 Open Scene scene 파일을불러옵니다. Save Scene As scene 파일을저장합니다. Scene 파일이저장될때, 시뮬레이터는 scene 에있는모든엔터티들의상태값을저장하며, 해당엔터티와연관되어있는서비스와관련메니페스트정보도같이저장됩니다. Open Manifest 서비스메니페스트파일을불러옵니다.. Capture Image As - 현재의시뮬레이션상의뷰를이미지파일로저장합니다. Exit 시뮬레이터를종료하고 DSS 노드를종료합니다. Entity 메뉴 Cut - 현재체크된엔터티들을잘라냅니다. Copy 현재체크된엔터티들을복사합니다. Paste 잘라내거나복사한엔터티들을 scene 안에추가합니다. Paste As Child - 잘라내거나복사한엔터티들을현재체크되어있는엔터티의하위엔터티로추가합니다. New 새로운엔터티를추가하기위한다이얼로그창을표시합니다. Load Entities 파일로부터엔터티들을불러옵니다. Save Entities As 현재체크된엔터티들을파일에저장합니다. View 메뉴 Status bar 상태바를숨기거나표시합니다. 상태바는 1 초단위의프레임비율과카메라위치방향등을표시합니다. Look Along 특정축을기반으로뷰를설정합니다. Render 메뉴 첫번째 4 개의항목들은시뮬레이션에서의엔터티들을어떻게표시할것인지에대해각각다른방법을보여주며, F2 키를눌러서각각의메뉴를순차적으로변경시킬수있습니다. Visual - Full 3D 상태로표시하며, 각각의엔터티에연관된메시들로표시되고, 빛과명암이표시됩니다. Wireframe Scene 을 wireframe 뷰로표시합니다. 이모드에서는얼마나많은폴리곤이메시를 8
구성하는지, 그리고폴리곤의면이어디에있는지에대한대략적인아이디어를보여줍니다. Physics Scene 을 physics 아웃라인형태로보여줍니다. 이모드에서는각각의엔터티가물리엔진에서어떻게표시되는지를알수있습니다. 만약물리엔진이사용가능하지않다면엔터티들이표시되지않을수있습니다. Combined - Full 3D 뷰와 Physics 뷰를결합하여표시합니다. 이모드에서는비주얼메시와물리인터티가얼마나잘매칭되는지를확인할수있습니다. Graphics Settings Scene 이어떻게랜더링되는지에대한컨트롤값을설정합니다. Camera 메뉴 카메라메뉴는쉽게개발자가카메라를변경하여보여지는 Scene 을변경하는기능을제공합니다. 사용자는 F8 키를눌러카메라를순차적으로변경할수있습니다. Main Camera 기본적으로제공되는카메라로설정합니다. Other cameras 현재의메니페스트파일에서제공되는카메라들의목록을보여주고해당카메라를선택할수있도록합니다. Physics 메뉴 Enabled 시뮬레이션에서물리속성을설정하거나해제할수있습니다. Settings 중력값을설정하고, 기본카메라를강체로활용할수있도록설정할수있습니다. 만약카메라옵션이설정되었을경우, 카메라를움직여서 Scene 안에있는물체들에충돌시킬수있습니다. Mode 메뉴 이항목안에있는메뉴들은실행또는편집모드로구성되며 F5 키를통해모드가상호변환됩니다. Run - 기본적인시뮬레이션실행모드로전환합니다. Edit 편집모드로전환하여각각의엔터티들의상태를변경할수있도록합니다. 이모드에서는물리속성이작용하지않습니다. Help 메뉴 Contents 시뮬레이션환경에대한보다더세부적인정보를제공하기위한웹페이지를표시합니다. About 제품의버전정보를표시합니다. 9
Microsoft 비주얼시뮬레이션환경의단축키및마우스 비주얼시뮬레이션환경에서는단축키와마우스를통해서다양한형태의명령들을수행할수있습니다. 단축키사용 시뮬레이터는단축키에의해카메라의방향을변경시킬수있으며, 이러한작동은해당시뮬레이터패널이포커스를가지고있을경우에만가능합니다. 해당패널에포커스가가있을경우에는테두리색갈이파란색으로변경됩니다. w s a d q e 앞으로이동뒤로이동왼쪽으로이동오른쪽으로이동위로이동아래로이동 만약 Shift 키와같이위의키를누를경우 20 배빨리수행됩니다. 각모드간변환키는아래와같습니다. F2 F3 F5 F8 랜더모드변경 Physics 엔진모드변경편집모드와실행모드변경카메라변경 편집모드에서특정엔터티를선택하였을경우, 왼쪽 Ctrl 키를누르면, 해당엔터티가밝게표시됩니다. 또한 Ctrl 키를누른상태에서는다음과같은키를같이사용할수있습니다. Up 방향키 선택된객체를해당객체의 +Y 방향에서바라봅니다. Ship+Up 방향키 선택된객체를해당객체의 -Y 방향에서바라봅니다. Left 방향키 선택된객체를해당객체의 +X 방향에서바라봅니다. Shift+Left 방향키 선택된객체를해당객체의 -X 방향에서바라봅니다. Right 방향키 선택된객체를해당객체의 +Z 방향에서바라봅니다. Shift+Right 방향 선택된객체를해당객체의 -Z 방향에서바라봅니다. 키 마우스사용 실행상태에서왼쪽마우스버튼을누른채마우스를움직이며, 카메라의방향이변경됩니다. 그리고편집모드에서는특정객체를선택하고, 좌측의속성창에서 Position Vector 항목을선택한후왼쪽마우스버튼을클릭한채로마우스를움직이면해당객체를움직일수있습니다. 또한 Rotation Vector 항목을선택한후마찬가지로마우스를움직이면해당객체를회전시킬수있습니다. 시뮬레이션좌표시스템 시뮬레이터는오른손좌표계를사용합니다. +Y 축의방향은위쪽을가리키며, X 와 Z 축은바닥과평행한부분을가리킵니다. 10
3. 시뮬레이션툴및유틸리티 Obj-to-Bos 파일변한툴 (Obj2Bos.exe).obj 파일을.bos 파일로변환하는툴로서, 오브젝트시뮬레이션메쉬파일을최적화된바이너리파일로변환합니다. obj2bos.exe [options] 파라메터 Option /infile:<string> /level:none Default Maximum /outfile:<string> 단축설명 /i.obj 파일을.bos 포멧으로변환합니다. 와일드카드문자열은 * 과? 가사용될수있습니다. /l 최적화수준을나타냅니다. 높은최적화값을지정할경우압축시간은늘어나지만 mesh 파일의크기가줄어듭니다. /o 출력파일이름이며옵션사항입니다. 별도로지정하지안으면동일한파일이름의.bos 파일이생성됩니다. /verbosity:off Error Warning Info Verbose /v Verbosity 수준을정의합니다. @<file> 여러변환대상파일의이름을포함하고있는텍스트파일의이름을지정합니다. 사용예 Obj2Bos.exe /i:"store\media\earth.obj" 11
4. 시뮬레이션튜토리얼 시뮬레이션튜토리얼 1 (C#) 시뮬레이션런타임에대한소개 MSRS 는물리엔진에기반한다양한가상환경에서로봇어플리케이션을개발할수있는환경을제공합니다. 이번튜토리얼은시뮬레이션엔진서비스와렌더링윈도우를어떻게시작하는지에대해보여주며, 두개의객체를가상공간에추가하는방법을보여줍니다. 그림 1 시뮬레이션실행화면 12
그림 2 시뮬레이션의물리적표현모드 시뮬레이션실행모듈은다음과같은컴포넌트로구성되어있습니다. 시뮬레이션엔진서비스 엔터티들의렌더링과물리엔진에서의시뮬레이션시간을처리합니다. 시뮬레이션공간전체의상태를트래킹하고서비스와시뮬레이션앞단부분을처리합니다. Managed 물리엔진랩퍼 저수준의물리엔진의 API 에대해 C# 형태의인터페이스를노출합니다. AGEIA PhysX 기술 AGEIA 물리프로세서를통한하드웨어가속기능을제공합니다 ( 별도의하드웨어카드추가시 ) 엔터티 시뮬레이션공간상에서의하드웨어및물리객체를나타냅니다. 다양한엔터티들이미리정의되어있으며, 이러한엔터티들을조합하여새로운형태의엔터티들을생성할수있고다양한형태의로봇플랫폼을개발할수있습니다. 사전요구사항하드웨어 시뮬레이션을구동하기위해서는 PC 의그래픽카드가이를지원하는카드이어야가능합니다. 지원되는그래픽카드는아래의링크를통해확인가능합니다. http://channel9.msdn.com/wiki/default.aspx/channel9.simulationfaq 13
소프트웨어 시뮬레이션프로그래밍을위해서는아래의툴중한가지가필요합니다. Microsoft Visual C# 2005 Express Edition. Microsoft Visual Studio Standard Edition. Microsoft Visual Studio Professional Edition. 또는 Microsoft Visual Studio Team System. 시작하기 이번튜토리얼의소스코드는아래의폴더에미리작성되어있습니다. Samples\SimulationTutorials\Tutorial1 Visual Studio 에서 SimulationTutorial1.csproj 파일을로딩함으로써튜토리얼을시작합니다. 프로젝트를빌드한후 MSRS Commnad 창에서아래와같이실행시켜봄으로써결과를확인해볼수있습니다. dsshost /port:50000 /manifest:"samples\config\simulationtutorial1.manifest.xml" Visual Studio 에서프로젝트를로딩한경우, 해당프로젝트속성에위의실행명령어가등록되어있으므로, 개발툴에서 F5 키를눌러직접실행을시키면해당프로그램을실행시킬수있습니다. 각각의코드구성에대한설명은아래와같습니다. Step 1: 참조구성 시뮬레이션을실행시키기위해서는아래의 dll 파일들을반드시참조에추가해놓아야하며, 이러한 dll 파일들은 MSRS 의설치폴더안에 bin 폴더에존재합니다. RoboticsCommon.DLL PhysicalModel namespace 를포함하고있으며, 물리적인로봇속성을모델링하기위한공통값들이정의되어있습니다. PhysicsEngine.DLL Native physics engine dll 에대한 Managed C++ 랩퍼입니다. SimulationCommon.DLL 공통타입들이정의되어있습니다. SimulationEngine.DLL 랜더링엔진, 시뮬레이션상태관리와서비스앞단처리를수행합니다. SimulationEngine.Proxy.DLL 시뮬레이션엔진상태의 Proxy 이며엔진이파트너서비스로호출될때사용됩니다. 각참조항목들은 Copy Local Property 가 False 로설정되어있어야합니다. 14
그림 3 SimulationTutorial1 참조항목 SimulationTutorial1.cs 파일에는다음과같은 Using 구분이사용됩니다. using Microsoft.Ccr.Core; using Microsoft.Dss.Core; using Microsoft.Dss.Core.Attributes; using Microsoft.Dss.ServiceModel.Dssp; using Microsoft.Dss.ServiceModel.DsspServiceBase; using System; using System.Collections.Generic; using Microsoft.Robotics.Simulation; using Microsoft.Robotics.Simulation.Engine; using engineproxy = Microsoft.Robotics.Simulation.Engine.Proxy; using Microsoft.Robotics.Simulation.Physics; using Microsoft.Robotics.PhysicalModel; using System.ComponentModel; Step 2: 시뮬레이션엔진시작하기 시뮬레이션엔진은시뮬레이션공간을랜더링하는윈도우를실행시킵니다. 이모듈이 MSRS 의서비스형태로되어있기때문에, 이모듈을구동하기위해서는해당서비스를파트너로등록해놓아야합니다. [DisplayName("Simulation Tutorial 1")] [Description("Simulation Tutorial 1 Service")] [Contract(Contract.Identifier)] public class SimulationTutorial1 : DsspServiceBase [Partner("Engine", Contract = engineproxy.contract.identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)] private engineproxy.simulationengineport _enginestub = 15
new engineproxy.simulationengineport(); // Main service port [ServicePort("/SimulationTutorial1", AllowMultipleInstances=false)] private SimulationTutorial1Operations _mainport = new SimulationTutorial1Operations(); public SimulationTutorial1(DsspServiceCreationPort creationport) : base(creationport) 이미해당엔진이매니페스트를통해실행되는경우도있기때문에파트너속성에서 CreationPolicy.UseExistingOrCreate 로지정하였음을유의하시기바랍니다. _enginestub 변수는단순히파트너를로딩시키기위한목적이외에는다른용도로사용되지않습니다. 대신에튜토리얼에서는시뮬레이션엔진에대한정적참조를사용하며직접메시지를전달합니다. 이러한통신은서비스처리방식에서예외적인부분이며, 성능을극대화하기위해적용된방식입니다. Step 3: 엔터티를환경에추가하기 Start 메소드에서 SetupCamera 와 PopulateWorld 메소드가호출됩니다. SetupCamera 에서는카메라의방향과위치를지정하며, PopulateWorld 에서는간단한환경엔터티들을추가합니다. protected override void Start() base.start(); // Orient sim camera view point SetupCamera(); // Add objects (entities) in our simulated world PopulateWorld(); SetupCamera 는아래와같이정의됩니다. private void SetupCamera() // Set up initial view CameraView view = new CameraView(); view.eyeposition = new Vector3(-1.65f, 1.63f, -0.29f); view.lookatpoint = new Vector3(-0.68f, 1.38f, -0.18f); SimulationEngine.GlobalInstancePort.Update(view); Sky 추가하기 Sky 객체는다음과같이추가됩니다. void AddSky() // Add a sky using a static texture. We will use the sky texture // to do per pixel lighting on each simulation visual entity SkyDomeEntity sky = new SkyDomeEntity("skydome.dds", "sky_diff.dds"); SimulationEngine.GlobalInstancePort.Insert(sky); 16
// Add a directional light to simulate the sun. LightSourceEntity sun = new LightSourceEntity(); sun.state.name = "Sun"; sun.type = LightSourceEntityType.Directional; sun.color = new Vector4(0.8f, 0.8f, 0.8f, 1); sun.direction = new Vector3(0.5f, -.75f, 0.5f); SimulationEngine.GlobalInstancePort.Insert(sun); SkyEntity 클래스는파라메터로 2 개의파일이름을필요로합니다. Sky 비트맵을위한텍스쳐파일 세부적인하늘을묘사하기위한고해상도이미지 단순화된큐브맵형태의텍스쳐파일 픽셀단위광원효과를위한파일 LightEntity 클래스는광원효과를시뮬레이션하기위해사용됩니다. 평지추가 이제평지를시뮬레이션공간에추가합니다. 평지는다른객체를추가하기전에미리추가되어야하며, 그렇지안으면다른객체들이모두평지아래로떨어져버립니다. void AddGround() // create a large horizontal plane, at zero elevation. HeightFieldEntity ground = new HeightFieldEntity( "simple ground", // name "03RamieSc.dds", // texture image new MaterialProperties("ground", 0.2f, // restitution 0.5f, // dynamic friction 0.5f) // static friction ); SimulationEngine.GlobalInstancePort.Insert(ground); Ground 엔터티는미리정의되어있는 HeightFieldEntity 를사용하며, 이객체는다양한 Height 들의배열로구성됩니다. 위의예제는평지를구성한예를보여줍니다. Ground 엔터티를생성한다음에는반드시 Insert 메소드를통해시뮬레이션엔진에등록시켜주어야합니다. 단순한물체추가 다음으로는단순한박스를추구하는코드입니다. 만약별도의메쉬파일이제공되지않는다면, 시뮬레이션엔진은기본적인색상으로해당객체를랜더링합니다. void AddBox(Vector3 position) Vector3 dimensions = new Vector3(0.2f, 0.2f, 0.2f); // meters // create simple movable entity, with a single shape 17
SingleShapeEntity box = new SingleShapeEntity( new BoxShape( new BoxShapeProperties( 100, // mass in kilograms. new Pose(), // relative pose dimensions)), // dimensions position); box.state.massdensity.mass = 0; box.state.massdensity.density = 0; // Name the entity. All entities must have unique names box.state.name = "box"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(box); 미리정의되어있는 SingleShapeEntity 는시뮬레이션공간에객체를추가하는데있어서좋은출발점이될수있으며, 구, 박스또는캡슐형태의객체를추가할수있습니다. 이러한모양의객체를추가할때, 질량과상대적위치, 그리고해당물체의크기를같이지정합니다. SingleShapeEntity 를사용함에있어서유의할사항은아래와같습니다. 질량과밀도가정의되지않은 Shape 은정적이며, 움직일수없고무한의질량을가진것으로가정됩니다. Shape 은물리엔진에서엔터티로묘사되며, 만약메쉬파일이제공된다면, 랜더링은물리적표현과별개로처리됩니다. Pose 클래스는객체의위치와방향을같이나타냅니다. 이값은해당엔터티내에서상대적인위치값으로표시됩니다. 모든엔터티들은고유의식별되는이름을반드시가져야합니다. 만약이름이중복되는경우에는해당엔터티가표시되지않습니다. 메쉬기반의텍스쳐엔터티 시뮬레이션엔진은실제와유사한사진기반의형태로시뮬레이션공간상에서사물을쉽게표현할수있습니다. 이렇게표현하기위해서는파일기반의 DirectX 메쉬파일이필요합니다. 아래의예제에서는이러한메쉬파일에기반하여객체를랜더링하는간단한사례를보여줍니다. void AddTexturedSphere(Vector3 position) SingleShapeEntity entity = new SingleShapeEntity( new SphereShape( new SphereShapeProperties(10, // mass in kg new Pose(), // pose of shape within entity 1)), //default radius position); entity.state.assets.mesh = "earth.obj"; entity.sphereshape.spherestate.material = new MaterialProperties("sphereMaterial", 0.5f, 0.4f, 0.5f); 18
// Name the entity entity.state.name = "detailed sphere"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(entity); 파일기반의메쉬파일을사용하는데있어서유의할사항은아래와같습니다. Asset 멤버에서정의되어있는모든파일경로는 store\media 내에있는것으로간주됩니다. 만약오직파일이름만사용하는경우에는경로는무시되며, 해당파일을 store\media 폴더밑에모두복사해두어야합니다. 메쉬를사용하는경우, 물리 Shape 을계산하기위해 TriangleMeshEnvironmentEntity 또는 SimplifiedConvexMeshEnvironmentEntity 와같은고급엔터티기능을사용할수있습니다. Step 4: 튜토리얼실행 SimulationTutorial1.csproj 파일을로드한후컴파일과정을거쳐실행시킵니다. 정상적으로실행된다면, 먼저비주얼윈도우화면이표시되고, 이튜토리얼의앞부분에있는그림과같은화면이표시됩니다. 단순한시뮬레이션공간을이동시켜보기위해단축키를눌러화면을이동시켜봅니다. 단축키 F1 도움말을숨김 F2 랜더링모드를변경함 카메라이동 A 왼쪽으로이동 D 오른쪽으로이동 W 앞으로이동 S 뒤로이동 Q Y 축을따라위로이동 E Y 축을따라아래로이동 마우스컨트롤 카메라의방향을마우스를통해이동시킬수있습니다. Xbox360 게임패드컨트롤 왼쪽엄지손가락의컨트롤을통해카메라의위치를이동시킬수있습니다. 오른쪽엄지손가락의컨트롤을통해카메라의방향을이동시킬수있습니다. 19
시뮬레이션튜토리얼 2 (C#) 시뮬레이션서비스를통한복합엔터티 이번튜토리얼에서는일반적인엔터티를어떻게생성하고모듈화된시뮬레이션로봇을어떻게생성하는지에대해설명합니다. 그림 1 시뮬레이션실행화면 20
그림 2 시뮬레이션의물리적표현화면 시작하기 이번튜토리얼은 Samples\SimulationTutorials\Tutorial2 밑에소스코드가해당폴더에있는 SimulationTutorial2.csproj 파일을로드하여프로젝트를실행할수있습니다. 해당프로젝트를실행하기위해서는 F5 키를눌러 Visual Studo 에서바로실행할수도있으며, 아래와같이 MSRS 실행창에서명령어를통해실행할수도있습니다. bin\dsshost /port:50000 /manifest:"samples\config\simulationtutorial2.manifest.xml" Step 1: 참조추가 시뮬레이션을실행하기위해서는아래와같은 dll 파일을참조에추가해놓아야합니다. SimulatedBumper.Y2006.M05.Proxy SimulatedDifferentialDrive.2006.M06.Proxy SimulatedLRF.Y2006.M05.Proxy SimulatedWebcam.Y2006.M09.Proxy 또한다음의 Using 문장을프로그램의상단에추가해야합니다. using drive = Microsoft.Robotics.Services.Simulation.Drive.Proxy; using lrf = Microsoft.Robotics.Services.Simulation.Sensors.LaserRangeFinder.Proxy; 21
using bumper = Microsoft.Robotics.Services.Simulation.Sensors.Bumper.Proxy; using simwebcam = Microsoft.Robotics.Services.Simulation.Sensors.SimulatedWebcam.Proxy; 서비스시작 튜토리얼 1 에서보다확장되어좀더복잡한형태의엔터티들이시뮬레이션에추가됩니다. private void SetupCamera() // Set up initial view CameraView view = new CameraView(); view.eyeposition = new Vector3(2.491269f, 0.598689f, 1.046625f); view.lookatpoint = new Vector3(1.873792f, 0.40983f, 0.2830455f); SimulationEngine.GlobalInstancePort.Update(view); private void PopulateWorld() AddSky(); AddGround(); AddCameras(); AddTable(new Vector3(1, 0.5f, -2)); AddPioneer3DXRobot(new Vector3(1, 0.1f, 0)); AddLegoNxtRobot(new Vector3(2, 0.1f, 0)); //AddIRobotCreateRobot(new Vector3(2, 0.1f, 0)); // uncomment this to add an irobot Create robot Table 엔터티는프로그램에의해생성된다중 Shape 엔터티입니다. 그리고 Pioneer3DX 와 LegoNxt 는디퍼런셜드라이드기반의로봇으로서각각레이저파인더와 2 개의범퍼센서그리고한개의전방범퍼센서를포함하고있습니다. Step 2: 다중 Shape 을통한환경엔터티정의 그림 3 시뮬레이션에서의 Table 객체표현 22
그림 4 - 시뮬레이션에서의 Table 의물리적표현 복잡한형태의물체를손쉽게생성하기위해기본적인 Shape 들의집합을사용할수있습니다. 위의 Table 예에서는여러개의 Box Shape 을결합하여하나의엔터티로생성을한예입니다. void AddTable(Vector3 position) // create an instance of our custom entity TableEntity entity = new TableEntity(position); // Name the entity entity.state.name = "table:"+guid.newguid().tostring(); // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(entity); TableEntity 클래스는 MultiShapeEntity 클래스로부터유도되었으며, 여러개의박스를결합하여테이블을생성합니다. 엔터티정의는 SimulationTutorial2.cs 파일의아래부분에기술되어있습니다. /// <summary> /// An entity for approximating a table. /// </summary> [DataContract] public class TableEntity : MultiShapeEntity /// <summary> /// Default constructor. /// </summary> public TableEntity() /// <summary> /// Custom constructor, programmatically builds physics primitive shapes to describe /// a particular table. /// </summary> /// <param name="position"></param> public TableEntity(Vector3 position) 23
State.Pose.Position = position; State.Assets.Mesh = "table_01.obj"; float tableheight = 0.65f; float tablewidth = 1.05f; float tabledepth = 0.7f; float tablethinkness = 0.03f; float legthickness = 0.03f; float legoffset = 0.05f; // add a shape for the table surface BoxShape tabletop = new BoxShape( new BoxShapeProperties(30, new Pose(new Vector3(0, tableheight, 0)), new Vector3(tableWidth, tablethinkness, tabledepth)) ); // add a shape for the left leg BoxShape tableleftleg = new BoxShape( new BoxShapeProperties(10, // mass in kg new Pose( new Vector3(-tableWidth/2 + legoffset, tableheight/2, 0)), new Vector3(legThickness, tableheight + tablethinkness, tabledepth)) ); BoxShape tablerightleg = new BoxShape( new BoxShapeProperties(10, // mass in kg new Pose( new Vector3(tableWidth / 2 - legoffset, tableheight / 2, 0)), new Vector3(legThickness, tableheight + tablethinkness, tabledepth)) ); BoxShapes = new List<BoxShape>(); BoxShapes.Add(tableTop); BoxShapes.Add(tableLeftLeg); BoxShapes.Add(tableRightLeg); public override void Update(FrameUpdate update) base.update(update); 자체의엔터티를생성하는데있어서유의할점은아래와같습니다. 물리적파라메터와 Shape 을기술하기위해커스텀생성자를사용합니다. 만약시뮬레이션상태를캡쳐하고상태파일을로드할경우, 기본생성자가구동되고동일한엔터티가시리얼라이즈된값을이용해재생성될것입니다. 복잡한동작이필요치않다면이미존재하는엔터티를이용해새로운엔터티를생성합니다. MultiShapeEntity 는 Box 와 Sphere 와같은다양한형상의객체들에대한리스트를지원합니다. 24
Step 3: 모듈화된로봇과시뮬레이션하드웨어서비스생성 그림 5 Pioneer3DX 로봇의시뮬레이션화면 그림 6 - Pioneer3DX 로봇의물리적표현화면 시뮬레이션런타임은몇개의사전에정의되어있는휠로봇기반의로봇들을실행시킵니다. 이러한로봇들은디퍼런셜드라이브와몇개의센서들을포함하고있습니다. 이번시뮬레이션튜토리얼에서는 2 개의로봇들을사용할예정이며, 첫번째로 Pioneer3DX 로봇에대한인스턴스를아래와같이생성하여사용합니다. 이러한로봇은기본적으로 DifferentialDriveEntity 를확장하여생성한로봇엔터티입니다. void AddPioneer3DXRobot(Vector3 position) Pioneer3DX robotbaseentity = CreateMotorBase(ref position); // Create Laser entity and start simulated laser service LaserRangeFinderEntity laser = CreateLaserRangeFinder(); // insert laser as child to motor base robotbaseentity.insertentity(laser); // Create bumper array entity and start simulated bumper service BumperArrayEntity bumperarray = CreateBumperArray(); // insert as child of motor base robotbaseentity.insertentity(bumperarray); 25
// create Camera Entity ans start SimulatedWebcam service CameraEntity camera = CreateCamera(); // insert as child of motor base robotbaseentity.insertentity(camera); // Finally insert the motor base and its two children // to the simulation SimulationEngine.GlobalInstancePort.Insert(robotBaseEntity); 모듈화된시뮬레이션로봇은아래와같이 3 단계를거쳐생성됩니다. 1. 디퍼런셜드라이브기반의기본모터를생성합니다. Pioneer3DX 와 LegoNXT 의디퍼런셜드라이브는기본적으로동일한클래스를상속받아생성되었으며, 두로봇사이에는차이가없습니다. 2. 가상하드웨어에대해 LaserRangeFinderEntity 와레이저파인더시뮬레이션서비스의인스턴스를생성합니다. 3. 두개의범퍼에대한 BumperArrayEntity 와범퍼시뮬레이션서비스의인스턴스를생성합니다. 4. CameraEntity 와웹캠서비스에대한시뮬레이션서비스의인스턴스를생성합니다. 위의과정을통해레이저, 범퍼, 카메라를생성한후에는모터기반엔터티에해당엔터티들을자식엔터티로추가해주어야합니다. robotbaseentity.insertentity(bumperarray); 디퍼런셜드라이브엔터티 부모엔터티는 Pioneer3DX 로봇의물리형상을가진디퍼런셜모터클래스로부터생성됩니다. CreateEntityPartner 메소드는엔터티가런타임시에바인딩될필요가있다는것을서비스에게알려주어서비스파트너를생성시킵니다. private Pioneer3DX CreateMotorBase(ref Vector3 position) // use supplied entity that creates a motor base // with 2 active wheels and one caster Pioneer3DX robotbaseentity = new Pioneer3DX(position); // specify mesh. robotbaseentity.state.assets.mesh = "Pioneer3dx.bos"; // specify color if no mesh is specified. robotbaseentity.chassisshape.state.diffusecolor = new Vector4(0.8f, 0.25f, 0.25f, 1.0f); // the name below must match manifest robotbaseentity.state.name = "P3DXMotorBase"; // Start simulated arcos motor service drive.contract.createservice(constructorport, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner( "http://localhost/" + robotbaseentity.state.name) ); 26
return robotbaseentity; Laser Range Finder 엔터티 아래의코드는레이저엔터티와관련된서비스가어떻게시작되는지에대해간략히보여줍니다. 엔터티의이름과파트너이름이매칭되어야한다는것에유의하시기바랍니다. private LaserRangeFinderEntity CreateLaserRangeFinder() // Create a Laser Range Finder Entity. // Place it 30cm above base CenterofMass. LaserRangeFinderEntity laser = new LaserRangeFinderEntity( new Pose(new Vector3(0, 0.30f, 0))); laser.state.name = "P3DXLaserRangeFinder"; laser.laserbox.state.diffusecolor = new Vector4(0.25f, 0.25f, 0.8f, 1.0f); // Create LaserRangeFinder simulation service and specify // which entity it talks to lrf.contract.createservice( ConstructorPort, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner( "http://localhost/" + laser.state.name)); return laser; 센서엔터티에관해한가지흥미로운부분은상대적인위치가모터본체위에항상위치해야한다는것입니다. 모듈화된로봇의경우센서와추가적인하드웨어들은부모엔터티의용어로기술되어야하며, 결합된형태가아닌독립적인형태로기술될필요가있습니다. Bumper Array 엔터티 이번섹션에서는모터본체의앞뒤에위치한두개의 Shape 을사용하여 Bumper Array 를생성하는방법에대해보여줍니다. Pioneer 메쉬파일은 10 개의범포를사용하는것으로보여주고있지만, 시뮬레이션에서는단지 2 개의범퍼만사용합니다. 범퍼 Shpae 의상대적인위치에유의하시기바랍니다. private BumperArrayEntity CreateBumperArray() // Create a bumper array entity with two bumpers BoxShape frontbumper = new BoxShape( new BoxShapeProperties("front", 0.001f, new Pose(new Vector3(0, 0.05f, -0.25f)), new Vector3(0.40f, 0.03f, 0.03f) ) ); frontbumper.state.diffusecolor = new Vector4(0.1f, 0.1f, 0.1f, 1.0f); BoxShape rearbumper = new BoxShape( 27
new BoxShapeProperties("rear", 0.001f, new Pose(new Vector3(0, 0.05f, 0.25f)), new Vector3(0.40f, 0.03f, 0.03f) ) ); rearbumper.state.diffusecolor = new Vector4(0.1f, 0.1f, 0.1f, 1.0f); // The physics engine will issue contact notifications only // if we enable them per shape frontbumper.state.enablecontactnotifications = true; rearbumper.state.enablecontactnotifications = true; // put some force filtering so we only get notified for significant bumps //frontbumper.state.contactfilter = new ContactNotificationFilter(1,1); //rearbumper.state.contactfilter = new ContactNotificationFilter(1, 1); BumperArrayEntity bumperarray = new BumperArrayEntity(frontBumper, rearbumper); // entity name, must match manifest partner name bumperarray.state.name = "P3DXBumpers"; // start simulated bumper service bumper.contract.createservice( ConstructorPort, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner( "http://localhost/" + bumperarray.state.name)); return bumperarray; Bumper Array 엔터티는물리공간에서다른물체와충돌을알리기위해 ContractNotification 방식을적용합니다. 범퍼가충돌을감지하기위해서는아래와같은속성이설정되어야합니다. frontbumper.state.enablecontactnotifications = true; 카메라엔터티 이본섹션에서는실시간을시물레이션공간상의영상을캡쳐해서전송하는카메라를어떻게추가시키는지에설명을합니다. 카메라는모터본체에부착되어있으며, 이로인해로봇이움직일때같이움직이도록되어있습니다. 시뮬레이션엔진은 CameraEntity 를생성하는데있어서다중인스턴스를지원하며, 아래와같이 2 가지의모드로작동될수있습니다. 1. 실시간모드 CameraEntity 는임의의카메라로부터전체영역의영상을실시간으로랜더링할수있습니다. 이모드는웹캠센서를모델링하는데유용하게사용됩니다. 실시간카메라는영상의랜더링을중복적으로수행하기때문에높은랜더링자원을소모합니다. 2. 비실시간모드 CameraEntity 는시뮬레이션상에서오직활성화될때만랜더링을합니다. 따라서랜더링자원이많오소요되지않습니다. 28
private CameraEntity CreateCamera() // low resolution, wide Field of View CameraEntity cam = new CameraEntity(320, 240); cam.state.name = "robocam"; // just on top of the bot cam.state.pose.position = new Vector3(0.0f, 0.5f, 0.0f); // camera renders in an offline buffer at each frame // required for service cam.isrealtimecamera = true; // Start simulated webcam service simwebcam.contract.createservice( ConstructorPort, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner( "http://localhost/" + cam.state.name) ); return cam; Step 4: 시뮬레이션서비스와엔터티파트너쉽 서비스와하드웨어시뮬레이션엔터티와의파트너링 CreateService 메소드는런터임시에해당서비스정보의요소로서파트너요소를제공하는것과함께주어진 Contract 에대한서비스인스턴스를생성하도록합니다. SimulatedDifferentialDrive 서비스의구현은시뮬레이션서비스를어떻게찾고엔터티파트너를어떻게이용하는지에대해구체적으로보여줍니다. 아래코드는 CreateMotorNase 메소드에서사용된코드입니다. // Start simulated arcos motor service drive.contract.createservice(constructorport, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner( "http://localhost/" + robotbaseentity.state.name) ); 시뮬레이션서비스디자인패턴 시뮬레이션서비스들은엔터티생성과정시비동기특성을확실하게하기위해특별한시작패턴을따라야합니다. 엔터티가서비스가시작된후에시뮬레이션에추가될수도있기때문에, 서비스는엔터티 Notification 이도착할때까지 DSSP 오퍼레이션프로세싱을허용하지않도록유의해야합니다. 아래코드는 SimulatedDifferentialDrive 서비스에서사용된코드로서이러한패턴의사례를보여주고있습니다. protected override void Start() // Find our simulation entity that represents the "hardware" or real-world service. // To hook up with simulation entities we do the following steps // 1) have a manifest or some other service create us, specifying a partner named SimulationEntity // 2) in the simulation service (us) issue a subscribe to the simulation engine 29
looking for // an instance of that simulation entity. We use the Entity.State.Name for the match so it must be // exactly the same. See SimulationTutorial2 for the creation process // 3) Listen for a notification telling us the entity is available // 4) cache reference to entity and communicate with it issuing low level commands. _simengine = simengine.simulationengine.globalinstanceport; _notificationtarget = new simengine.simulationengineport(); if (_state == null) CreateDefaultState(); // PartnerType.Service is the entity instance name. _simengine.subscribe(serviceinfo.partnerlist, _notificationtarget); // dont start listening to DSSP operations, other than drop, until notification of entity Activate(new Interleave( new TeardownReceiverGroup ( Arbiter.Receive<simengine.InsertSimulationEntity>(false, _notificationtarget, InsertEntityNotificationHandlerFirstTime), Arbiter.Receive<DsspDefaultDrop>(false, _mainport, DefaultDropHandler) ), new ExclusiveReceiverGroup(), new ConcurrentReceiverGroup() )); void CreateDefaultState() _state = new diffdrive.drivedifferentialtwowheelstate(); _state.leftwheel = new Microsoft.Robotics.Services.Motor.Proxy.WheeledMotorState(); _state.rightwheel = new Microsoft.Robotics.Services.Motor.Proxy.WheeledMotorState(); _state.leftwheel.motorstate = new Microsoft.Robotics.Services.Motor.Proxy.MotorState(); _state.rightwheel.motorstate = new Microsoft.Robotics.Services.Motor.Proxy.MotorState(); void InsertEntityNotificationHandlerFirstTime(simengine.InsertSimulationEntity ins) InsertEntityNotificationHandler(ins); base.start(); // Listen on the main port for requests and call the appropriate handler. MainPortInterleave.CombineWith( new Interleave( new TeardownReceiverGroup(), new ExclusiveReceiverGroup( 30
Arbiter.Receive<simengine.InsertSimulationEntity>(true, _notificationtarget, InsertEntityNotificationHandler), Arbiter.Receive<simengine.DeleteSimulationEntity>(true, _notificationtarget, DeleteEntityNotificationHandler) ), new ConcurrentReceiverGroup() ) ); void InsertEntityNotificationHandler(simengine.InsertSimulationEntity ins) _entity = (simengine.differentialdriveentity)ins.body; _entity.servicecontract = Contract.Identifier; // create default state based on the physics entity if(_entity.chassisshape!= null) _state.distancebetweenwheels = _entity.chassisshape.boxstate.dimensions.x; _state.leftwheel.motorstate.powerscalingfactor = _entity.motortorquescaling; _state.rightwheel.motorstate.powerscalingfactor = _entity.motortorquescaling; 초기화하는데있어서핵심적인코드영역은아래와같습니다. 1. Drop 오퍼레이션또는 Entity Notification 에대해한번기다리도록간단한 interleave 를활성화합니다. InsertEntityNotificationHandlerFirstTime 핸들러가단한번사용된것에유의하시기바랍니다. 2. Notification 헨들러가한번사용됨에있어서, 엔터티인스턴스와서비스상태를구성하기위해서정상적인 Notification 헨들러를호출합니다. 3. 베이스클래스인 Start 메소드가호출되며서비스가해당오퍼레이션헨들러를활성화하고서비스디렉토리에추가합니다. 4. 엔터티들이동시삭제또는추가되는동시성상황으로부터오퍼레이션을보호하기위해, 엔터티 Notification 을수신하는단순한 Interleave 를메인포트 Interleave 에결합시킵니다. Step 5: 튜토리얼실행 코드를컴파일하고실행시키면윈도우창과 Simple Dashboard 이름의윈도우창이실행되는것을확인하실수있습니다. 31
그림 7 SimpleDashboard 에대한물리적표현화면 Simple Dashboard 는별도의서비스프로그램으로서매니페스트에서별도의서비스로등록되어있습니다. 따라서 Simple Dashboard 서비스는별도로실행가능한프로그램이며, 이번튜토리얼에서는시뮬레이션로봇을제어하기위해사용되었습니다. Simple Dashboard 프로그램을통해네트워크로로봇에접속할수있으며, 로봇에주행명령을전송할수있습니다. Drive 기반주행명령에대해서는로보틱스튜토리얼을참조하시기바랍니다. 그리고 Simple Dashboard 에서는레이저파인더의값을읽어와서화면에표시해주는기능도지원합니다. 로봇에접속하기위해 Simple Dashboard 창에서아래의명령을수행하시기바랍니다. 1. Machine 입력창에 localhost 를입력하고 Connect 버튼을클릭합니다. 2. Connect 버튼클릭후에리스트박스창에 2 개의서비스목록을확인할수있습니다. 3. 화면하단의 Laser Range Finder 부분에서 Connect 버튼을클릭합니다. 32
그림 8 Simple Dashboard Simple Dashboard 를이용해로봇을제어할수있으며아래와같이수행합니다. 1. 화면우측상단의서비스목록중에서 /simulateddifferentialdrive/ 로시작하는항목을더블클릭합니다. 2. 좌측의버튼중에서 Drive 버튼을클릭합니다. 3. Drive 버튼위에있는원형안에십자모양이그려져있는부분을마우스로클릭하여움직입니다. 이도형은컨트롤러를대신하여로봇을조정하도록합니다. 4. 만약조이스틱이나컨트롤러가부착되어있다면, Direct Input Device 항목에표시되며, 연결된디바이스를통해서도로봇을제어할수있습니다. 33
시뮬레이션튜토리얼 3 (C#) 상태정보와매니페스트를이용한시물레이션생성 이번튜토리얼에서는시뮬레이션엔진의상태를파일로저장하고시뮬레이션구동시이러한상태파일부터시작정보를읽어서초기값을설정하는시나리오를설명합니다. 이번시나리오는별도의샘플소스코드를필요로하지않으며, 기존의튜토리얼을활용하여상태정보를저장하고활용하는과정을소개합니다. 시작하기 이번튜토리얼을시작하기위해서는시물레이션튜토리얼 1 번과 2 번을실행한후실행중인시뮬레이션상태를파일로저장하는것이필요합니다. 상태를저장하는방법은아래의 Step1 을참고하기바랍니다. Step 1: 시뮬레이션실행상태를파일로저장하기 윈도우시작메뉴에서 MSRS Command 창을실행시키고아래의시뮬레이션튜토리얼 1 번을실행시키는명령어를수행합니다. dsshost.exe /port:50000 /tcpport:50001 /manifest:"samples\config\simulationtutorial1.manifest.xml" 웹브라우저를실행한후아래의주소로접속하여서비스가정상적으로실행중인지를체크합니다. http://localhost:50000/simulationengine 연결후에는아래의결과와같은페이지를볼수있습니다. 34
그림 1 Serialized XML 형태로표현된시뮬레이션상태 Serialized XML 형태로표현된시뮬레이션엔진의상태는시뮬레이션환경에있는모든엔터티들의현재속성을보여줍니다. 스크롤다운한후 SerializedEntities XML 노드를확인해보면아래와같습니다. 35
그림 2 SerializedEntities 노드 첫번째로이동가능한엔터티는 SingleShapeEntity 이며, 이엔터티의시리얼라이즈된상태값은첫번째시뮬레이션튜토리얼 (SimulationTutorial1.cs) 에포함되어있는아래의코드부분에서설정한값을보여줍니다. void AddBox(Vector3 position) Vector3 dimensions = new Vector3(0.2f, 0.2f, 0.2f); // meters // create simple movable entity, with a single shape SingleShapeEntity box = new SingleShapeEntity( new BoxShape( new BoxShapeProperties( 100, // mass in kilograms. new Pose(), // relative pose dimensions)), // dimensions position); box.state.massdensity.mass = 0; box.state.massdensity.density = 0; // Name the entity. All entities must have unique names box.state.name = "box"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(box); 시뮬레이션엔진서비스의웹주소에대해 HTTP GET 형태로값을읽어보면, 시뮬레이션엔진서비스의내부에값이유지되는상태값을확인해볼수있습니다. 웹브라우저에보이는내용을 MSRS 설치폴더밑에 \store 폴더안에 SimulationEngineState.xml 로저장해놓습니다. 36
그림 3 XML 파일로저장되어있는시뮬레이션상태값 이제는실행차에서 Ctrl+C 를눌러노드의실행을중지시키고아래의명령어를실행시킵니다. 1. Samples 아래에새로운폴더를생성합니다. cd Samples md MySimulationTutorial3 cd MySimulationTutorial3 2. 위에서 Store 폴더안에저장해놓은파일을생성해놓은폴더안에복사해놓습니다. copy..\..\store\simulationenginestate.xml MySim3.SimulationState.xml Step 2: 시뮬레이션상태파일을결합하기 이제두번째시뮬레이션튜토리얼예제를실행합니다. 위의 Step1 과정과동일하게웹브라우저로접속후실행상태를 XML 파일로저장해놓습니다. 두번째시뮬레이션튜토리얼은아래와같이실행시킵니다. dsshost.exe /p:50000 /t:50001 /manifest:"samples\config\simulationtutorial2.manifest.xml" 실행상태는웹브라우저를통해서아래의주소로접속하여확인입니다. 37
http://localhost:50000/simulationengine 이제실행상태를중지하고웹브라우저의내용을 XML 파일로저장합니다. 파일은위에서만든폴더에생성하며, 파일이름은 MySim3.SimulationState.XML 파일로저장합니다. 그림 4 XML 형태로저장된파일의내용중 Pioneer3DX 부분 이제나중에저장된 XML 파일과 Step 1 에서저장된 XML 파일을 Visual Studio 를통해파일을엽니다. 그리고나중에저장된파일중에서 Pioneer3DX 와이에연관되어있는 LaserRangeFinderEntity, BumperArrayEntity, CameraEntity 관련 XML 노드들을복사하여, 첫번째 XML 파일인 SimulationEngineState.XML 의 <SerializedEntities> 노드안에복사해놓습니다. Pioneer3DX 는아래와같이시작되며, LaserRangeFinderEntity 와나머지항목들도복사해놓고파일을저장합니다. <Pioneer3DX xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns="http://schemas.microsoft.com/robotics/2006/04/simulationengine.html"> Step 3: 저장된 XML 파일로서비스실행하기 이제 SimulationTutorial3.manifest.xml 파일을새롭게생성하여, 위의단계에서생성한 XML 파일을이용해서서비스를구동합니다. MSRS 설치폴더밑에 Samples\Config 폴더에는 SimulationTutorial3.manifest.xml 이름의파일이샘플로이미생성되어있으며, 해당파일의내용은아래와같습니다. <?xml version="1.0"?> <Manifest xmlns="http://schemas.microsoft.com/xw/2004/10/manifest.html" xmlns:dssp="http://schemas.microsoft.com/xw/2004/10/dssp.html" xmlns:simcommon="http://schemas.microsoft.com/robotics/2006/04/simulation.html" > <!--Simulation Tutorial 3 manifest. Uses simulation engine state file as configuration 38
for simulation engine --> <CreateServiceList> <!--Start simulation engine service--> <ServiceRecordType> <dssp:contract>http://schemas.microsoft.com/robotics/2006/04/simulationengine.html</dssp:c ontract> <dssp:partnerlist> <dssp:partner> <dssp:service>simulationtutorial3.simulationenginestate.xml</dssp:service> <dssp:name>dssp:stateservice</dssp:name> </dssp:partner> </dssp:partnerlist> </ServiceRecordType> <!-- Start simulated motor service--> <ServiceRecordType> <dssp:contract>http://schemas.microsoft.com/robotics/simulation/services/2006/05/simulated differentialdrive.html</dssp:contract> <dssp:partnerlist> <dssp:partner> <!--The partner name must match the entity name--> <dssp:service>http://localhost/p3dxmotorbase</dssp:service> <dssp:name>simcommon:entity</dssp:name> </dssp:partner> </dssp:partnerlist> </ServiceRecordType> <!-- Start simulated laser range finder service--> <ServiceRecordType> <dssp:contract>http://schemas.microsoft.com/robotics/simulation/services/2006/05/simulated lrf.html</dssp:contract> <dssp:partnerlist> <dssp:partner> <!--The partner name must match the entity name--> <dssp:service>http://localhost/p3dxlaserrangefinder</dssp:service> <dssp:name>simcommon:entity</dssp:name> </dssp:partner> </dssp:partnerlist> </ServiceRecordType> <!-- Start simulated bumber service--> <ServiceRecordType> <dssp:contract>http://schemas.microsoft.com/robotics/simulation/services/2006/05/simulated bumper.html</dssp:contract> <dssp:partnerlist> <dssp:partner> <!--The partner name must match the entity name--> 39
<dssp:service>http://localhost/p3dxbumpers</dssp:service> <dssp:name>simcommon:entity</dssp:name> </dssp:partner> </dssp:partnerlist> </ServiceRecordType> </CreateServiceList> </Manifest> 위의예제에서는동일한폴더에존재하는 SimulationEngineState.xml 파일을참조하도록되어있습니다. Step 4: 실행하기 이제아래의명령어를 MSRS 실행창에서실행시킵니다. Dsshost.exe /port:50000 /tcpport:50001 /manifest: samples\config\simulationtutorial3.manifest.xml 또한 SimpleDashboard 서비스를아래와같이실행시킨후 SimpleDashboard 에서해당 Pioneer3DX 로봇이정상적으로제어되는지를확인합니다. dsshost.exe /p:51000 /t:51001 /m:"samples\config\simpledashboard.manifest.xml" 40
시뮬레이션튜토리얼 4 (C#) 조인트와다관절 Arm 구현 이번튜토리얼에서는시뮬레이션상에서 6 자유도를가지는조인트 (Joint) 와다관절 Arm (Articulated Arms) 을구현하는예제를소개합니다. 그림 1 다관절 Arm 을시뮬레이션에서구현한예제 41
그림 2 - 다관절 Arm 의물리적랜더링예제 시작하기 이번시뮬레이션튜토리얼은해당샘플코드가이미존재하며 samples\simulationtutorials\tutorial4 폴더를참조합니다. Simulationtutorial4.csproj 파일을비주얼스튜디오를이용해서로드합니다. 프로젝트참조 이번시물레이션프로그램이실행되기위해서는두번째시뮬레이션프로젝트에추가적으로아래와같이 3 개의 Proxy dll 파일을참조에추가해야합니다. RoboticsCommon.Proxy 다관절 Arm 과같은서비스에대한서비스인터페이스정의 SimulatedLBR3Arm.2006.M07.Proxy 시뮬레이션 LBR3 Arm 서비스에대한프록시라이브러리 SimulationCommon.Proxy 공통시뮬레이션타입에대한프록시라이브러리. 결과값을시뮬레이션 Arm 에보내기위해사용됩니다. 42
그림 3 튜토리얼 4 의참조구성 아래의 using 문이파일상단에추가되어야합니다. using arm = Microsoft.Robotics.Services.ArticulatedArm.Proxy; using Microsoft.Robotics.PhysicalModel; using physicalmodelproxy = Microsoft.Robotics.PhysicalModel.Proxy; PhysicalModel 네임스페이스의타입적용 Microsoft.Robotics.PhysicalModel 네임스페이스에서는로봇의물리특성을기술하는데필요한타입들이정의됩니다. 이러한타입들은시뮬레이션에서엔터티와시뮬레이션서비스들을기술하기위해사용되기때문에, Microsoft.Robotics.Simulation.Physics 의네임스페이스와타입들이 PhysicalModel 네임스페이스로단일화되었습니다. 하지만, Vector3 와 Quaternion 과같은공통타입들이정의되는 DirectX 에대한런타임에서의의존성을피하기위하여, 새로운기본타입들이새로운네임스페이스에이러한이름으로정의되었습니다. 이러한새로운모델타입을사용하는시뮬레이션서비스또는실제서비스에연결되는서비스들에있어서는추가적으로복잡한작업이요구되지는않습니다. 단지일반적으로메시지를전달하드시원격서비스에대한프록시에링크를거는수준입니다. Quaternion 이포함되는연산을수행하는서비스나시뮬레이션서비스들에있어서는이러한작업들이큰문제가되지는않으며, 아래와같은주된이유를가집니다. 프록시에서는정적루틴을넘어서변경이안되기때문에, Vector 와 Quaternion 연산에대해서정적 Helper 메소드가 physicalmodelproxy 네임스페이스에서는더이상유효하지가않습니다. 이것은개발자가 RoboticsCommon.dll 과 RoboticsCommonProxy.dll 파일둘다를참조에포함해야한다는것을의미합니다. 이름들이같은프록시네임스페이스에대해서이름의혼동을피하기위하여 Using 문장을기술할때, 별칭 (Alias) 을사용하여축약된형태를사용하는것을권장합니다. 이러한이슈를다룬좋은예제는 SimulatedLBR3Arm 서비스이며 Samples\Simulation\ArticulatedArms\LBR3 폴더안에소스코드가있습니다. Step 1: 서비스를생성하고시작하기 43
서비스시작 이번튜토리얼은두번째튜토리얼과매우유사한방식으로시작되며, PopulateWorld 메소드를호출하고몇개의엔터티를추가합니다. protected override void Start() base.start(); // Orient sim camera view point SetupCamera(); // Add objects (entities) in our simulated world PopulateWorld(); private void SetupCamera() // Set up initial view CameraView view = new CameraView(); view.eyeposition = new Vector3(1.8f, 0.598689f, -1.28f); view.lookatpoint = new Vector3(0, 0, 0.2830455f); SimulationEngine.GlobalInstancePort.Update(view); private void PopulateWorld() AddSky(); AddGround(); AddDomino(new Vector3(0.3f, 0, 0.3f), 10); SpawnIterator(AddArticulatedArm); 시뮬레이션 Arm 서비스와엔터티 AddArticulatedArm 메소드는산업용다관절로봇 Arm 에대한시뮬레이션엔터티들과엔터티들을제어하기위한관련서비스들을생성합니다. 해당메소드가다중의비동기연산을순차적으로수행해야하기때문에 Iterater 방식이적용되었습니다. IEnumerator<ITask> AddArticulatedArm() Vector3 position = new Vector3(0, 0, 0); // Create an instance of our custom arm entity. // Source code for this entity can be found under // Samples\Simulation\Entities\Entities.cs KukaLBR3Entity entity = new KukaLBR3Entity(position); // Name the entity entity.state.name = "LBR3Arm"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(entity); 44
// create simulated arm service DsspResponsePort<CreateResponse> resultport = CreateService( Microsoft.Robotics.Services.Simulation.LBR3Arm.Proxy.Contract.Identifier, Microsoft.Robotics.Simulation.Partners.CreateEntityPartner("http://localhost/" + entity.state.name)); // asynchronously handle service creation result. yield return Arbiter.Choice(resultPort, delegate(createresponse rsp) _armserviceport = ServiceForwarder<arm.ArticulatedArmOperations>(rsp.Service);, delegate(fault fault) LogError(fault); ); if (_armserviceport == null) yield break; // we re-issue the get until we get a response with a fully initialized state do yield return Arbiter.Receive(false, TimeoutPort(1000), delegate(datetime signal) ); yield return Arbiter.Choice( _armserviceport.get(new GetRequestType()), delegate(arm.articulatedarmstate state) _cachedarmstate = state;, delegate(fault f) LogError(f); ); // exit on error if (_cachedarmstate == null) yield break; while (_cachedarmstate.joints == null); // Start a timer that will move the arm in a loop // Comment out the line below if you want to control // the arm through SimpleDashboard //Spawn<DateTime>(DateTime.Now, MoveArm); 두번째튜토리얼에서의 AddModularRobot 과유사하게엔터티가생성되고시뮬레이션엔진과엔터티에대한서비스에더해져서추가됩니다. 그리고아래의작업들이추가적으로적용됩니다. 45
1. CreateService 메소드에대한결과가비동기적으로처리되며, 신규서비스와의통신을위해포트가생성됩니다. 2. GET DSSP 연산이해당상태를조회합니다. 다관절 Arm 의상태정의는각각의 Arm 들을연결하는조인트들을기술하는조인트인스턴스의목록을포함합니다. 3. MoveArm 메소드가증식되면서호출됩니다. MoveArm 메소드는시뮬레이션 Arm 서비스에 Arm 들을연결하는다양한조인트를제어하기위한요청데이터를생성합니다. 시뮬레이션서비스는다관절 Arm 서비스의추상화된인터페이스를사용하기때문에, 이러한메소드는아래와같은서비스구현으로동작합니다. void MoveArm(DateTime signal) _angleinradians += 0.005f; float angle = (float)math.sin(_angleinradians); // Create set pose operation. Notice we have specified // null for the response port to eliminate a roundtrip arm.setjointtargetpose setpose = new arm.setjointtargetpose( new arm.setjointtargetposerequest(), null); // specify by name, which joint to orient setpose.body.jointname = _cachedarmstate.joints[0].state.name; setpose.body.targetorientation = new physicalmodelproxy.axisangle( new physicalmodelproxy.vector3(1, 0, 0), angle); // issue request to arm service for joint0 move. _armserviceport.post(setpose); // now move other joints. setpose.body.jointname = _cachedarmstate.joints[1].state.name; _armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[2].state.name; _armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[3].state.name; _armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[4].state.name; _armserviceport.post(setpose); // re- issue timer request so we wake up again Activate(Arbiter.Receive(false, TimeoutPort(15), MoveArm)); 메소드는 SetJointTargetPose 데이터를 15 밀리초단위로생성하며, 점진적으로조인트를해당축주위로이동시킵니다. 시뮬레이션 LBR3 로봇 Arm 에대해서는모든조인트들이단일자유도를가지도록설정됩니다. Step 2: 조인트구성 다음단계로진행하기전에, 조인트들이물리엔진에서시뮬레이션상으로어떻게기술되는지에대해살펴보도록합니다. 6 자유도와같은세부적인기술자료는 AGEIA PhysX 의기술문서를참고하길바라며, 조인트속성의데이터타입은 Samples\Common\PhysicalModel.cs 파일에정의되어 46
있습니다. /// <summary> /// Joint properties /// </summary> [DataContract] public class JointProperties /// <summary> /// Joint name. Must be unique for all joints between an entity pair /// </summary> [DataMember] [Description("The descriptive identifier for the joint.")] public string Name; /// <summary> /// Pair of entities joined through this joint /// </summary> [DataMember] [Description("The pair of entities connected through this joint.")] public EntityJointConnector[] Connectors = new EntityJointConnector[2]; /// <summary> /// Maximum force supported by the joint /// </summary> [DataMember] [Description("The maximum force supported by the joint.")] public float MaximumForce; /// <summary> /// Maximum torque supported by the joint /// </summary> [DataMember] [Description("The maximum torque supported by the joint.")] public float MaximumTorque; /// <summary> /// Enables collision modelling between entities coupled by the joint /// </summary> [DataMember] [Description("Enables collision between entities couples by the joint.")] public bool EnableCollisions; /// <summary> /// Underlying physics mechanism to compensate joint simulation errors /// </summary> [DataMember] [Description("Underlying physics mechanism to compensate joint simulation errors.")] public JointProjectionProperties Projection; /// <summary> /// If set, defines a joint with translation/linear position drives 47
/// </summary> [DataMember] [Description("Identifies if the joint supports translation/linear position drives.")] public JointLinearProperties Linear; /// <summary> /// If set, defines a joint with angular drives. /// </summary> [DataMember] [Description("Identifies if the joint supports angular drives.")] public JointAngularProperties Angular; //... Constructors omitted for clarity 시뮬레이션엔진은오진한가지타입의조인트만을노출시키며, 이러하조인트는 6 자유도조인트로알려져있습니다. 조인트는 2 개의물리엔터티를연결시키며, 상대엔터티에대한동작을제한시킬수있습니다. 조인트의기본요소는아래와같습니다. 자유도는 Unlock, Lock 또는 Limited 상태를가집니다. 엔터티들은로컬포인트를기준으로연결됩니다. 각각의엔터티에대해기준축이정의됩니다. 조인트법선 (normal) 은해당조인트가향하는방향을보여줍니다. Unlock 자유도에대해옵션사항으로드라이브와스프링메커니즘을적용할수있습니다. 구형조인트예제 구형조인트를구성하기위해서는선형자유도를고정시키고 ( 또는 JointProperties.Linear 관련코드를전혀기술하지않음 ) 각자유도 (Angular degree of freedom) 항목을해제시킵니다. void MoveArm(DateTime signal) _angleinradians += 0.005f; float angle = (float)math.sin(_angleinradians); // Create set pose operation. Notice we have specified // null for the response port to eliminate a roundtrip arm.setjointtargetpose setpose = new arm.setjointtargetpose( new arm.setjointtargetposerequest(), null); // specify by name, which joint to orient setpose.body.jointname = _cachedarmstate.joints[0].state.name; setpose.body.targetorientation = new physicalmodelproxy.axisangle( new physicalmodelproxy.vector3(1, 0, 0), angle); // issue request to arm service for joint0 move. _armserviceport.post(setpose); // now move other joints. setpose.body.jointname = _cachedarmstate.joints[1].state.name; 48
_armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[2].state.name; _armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[3].state.name; _armserviceport.post(setpose); setpose.body.jointname = _cachedarmstate.joints[4].state.name; _armserviceport.post(setpose); // re- issue timer request so we wake up again Activate(Arbiter.Receive(false, TimeoutPort(15), MoveArm)); JointAngularProperties angular = new JointAngularProperties(); angular.twistmode = JointDoFMode.Free; angular.swing1mode = JointDoFMode.Free; angular.swing2mode = JointDoFMode.Free; angular.twistdrive = new JointDriveProperties(JointDriveMode.Position, new SpringProperties(500, 10, 0), 1000); angular.swingdrive = new JointDriveProperties(JointDriveMode.Position, new SpringProperties(500, 10, 0), 1000); JointProperties sphericaljoint = new JointProperties(angular, null, null); 위의코드에서는각자유도항목들이해제되었으며, Twist 자유도에대해서만드라이브메커니즘메적용되었습니다. 이제조인트는 MoveArm 에서보여지는 SetJointtargetPose 를통해서제어됩니다. SetJointTargetVelocity 를이용해조인트드라이브를제어하기위해서는다음과같이드라이브모드를설정해야합니다. angular.twistdrive = new JointDriveProperties(JointDriveMode.Velocity, new SpringProperties(500, 10, 0), 1000); Step 3: 엔터티구현에대한이해와조인트정의 MSRS 에서지원되는모든엔터티에대해서는에제의형태로소스코드가제공됩니다. samples\simulation\entities\entities.cs 에는내장되어있는엔터티의구현사례가제공되어있으며, 컴파일되어 SimulationEngine.dll 안에포함되어있습니다. 이번튜토리얼은 KukaLBR3Entity 에초점을맞추고있으며, 7 개의 Arm 링크와 6 개의조인트로구성되어있습니다. 아래의코드는전체코드중의일부이며, 조인트리스트와 ArticulatedArmEntity 인스턴스항목을참조하기바랍니다. /// <summary> /// Models KUKA LBR3 robotic arm /// </summary> [DataContract] [CLSCompliant(true)] public class KukaLBR3Entity : VisualEntity const float ARM_MASS = 1f; const float ARM_THICKNESS = 0.03f; const float ARM_LENGTH = 0.075f; 49
const float DELTA = 0.000f; // approximation of the Lbr3 arms for arms 1->5 // base is considered arm/link 0, and the end effectors are 6 and 7 float ARM_LENGTH2 = ARM_LENGTH + ARM_THICKNESS * 2; float MIDPOINT_Y = ARM_LENGTH / 2f + ARM_THICKNESS; const int _jointcount = 7; /// <summary> /// Number of joints /// </summary> [DataMember] public int JointCount get return _jointcount; List<Joint> _joints = new List<Joint>(); /// <summary> /// Joints /// </summary> [DataMember] public List<Joint> Joints get return _joints; set _joints = value; List<ArmLinkEntity> _links = new List<ArmLinkEntity>(); /// <summary> /// Default constructor /// </summary> public KukaLBR3Entity() /// <summary> /// Initialization constructor /// </summary> /// <param name="position"></param> public KukaLBR3Entity(Vector3 position) State.Pose.Position = position; 엔터티초기화 다른모든엔터티와유사하게, KukaLBR3 엔터티는초기화루틴안에서데이터구조를맨먼저구성합니다. 그리고그다음으로해당강체에대한물리인스턴스를생성하는 Initalized 메소드를 50
호출합니다. 초기화루틴은조인트가모든링크엔터티가생성되고나서조인트들이생성되어야하기때문에약간복잡합니다. 조인트들은사용되기위해서인스턴스를필요로하기때문에, 물리엔진에추가되고활성화되어져야합니다. 엔터티의초기화메소드는엔터티가어떻게생성되었는지에따라서두가지방식으로구성됩니다. 1. 프로그램에의한생성 초기화생성자를통해엔터티가생성되었다면, 메소드는모든자식링크엔터티들을자동으로생성하고모든조인트파라메터를설정합니다. 2. Deserialization 만약엔터티가 Descerialization 되어있다면, 모든파라메터는설정되지만, 초기화과정은적당한물리객체를생성하기위해호출되어져야합니다. Initialized 메소드는아래와같습니다. /// <summary> /// Initialization /// </summary> /// <param name="device"></param> /// <param name="physicsengine"></param> public override void Initialize(xnagrfx.GraphicsDevice device, PhysicsEngine physicsengine) try InitError = string.empty; // all joints will be essentially of the same type: // A single DoF free, around the Joint Axis. A drive will be associated with // that DoF. Each joint will have a different joint axis and joint normal for (int i = 0; i < _jointcount; i++) PhysicsJoint jointinstance = null; if (_joints.count < i + 1) JointAngularProperties commonangular = new JointAngularProperties(); commonangular.twistmode = JointDoFMode.Free; commonangular.twistdrive = new JointDriveProperties( JointDriveMode.Position, new SpringProperties(500000, 100000, 0), 1000000); jointinstance = PhysicsJoint.Create(new JointProperties(commonAngular, null, null)); // joints must be names jointinstance.state.name = "Joint" + i.tostring(); Joints.Add(jointInstance); else jointinstance = PhysicsJoint.Create(_joints[i].State); _joints[i] = jointinstance; 51
if (_baselink == null) // programmatically build articulated arm CreateBase(_joints[0]); CreateArm1(_joints[0]); CreateArm2(_joints[1]); CreateArm3(_joints[2]); CreateArm4(_joints[3]); CreateArm5(_joints[4]); CreateArm6(_joints[5]); CreateArm7(_joints[6]); // if we were deserialized, _links will be null // Rebuilt it from the individual named fields if (_links == null _links.count == 0) _links = new List<ArmLinkEntity>(); _links.add(_baselink); _links.add(_arm1link); _links.add(_arm2link); _links.add(_arm3link); _links.add(_arm4link); _links.add(_arm5link); _links.add(_arm6link); _links.add(_arm7link); // set the physics shape on each child entity for (int i = 1; i < (_links.count - 1); i++) // Links 1 to 6 use the same capsule shape to approximate the detailed // geometry of the LBR3 arm links. Notice that in the local pose for the // shape, we translate it up by half its total height since, by default // physics engine sets the center of the shape to be its center of mass if (_links[i].capsuleshape == null) _links[i].capsuleshape = new CapsuleShape( new CapsuleShapeProperties(ARM_MASS, new Pose(new Vector3(0, MIDPOINT_Y, 0)), ARM_THICKNESS, ARM_LENGTH)); 52
// initialize will load the graphics mesh base.initialize(device, physicsengine); // initialize children foreach (VisualEntity c in _links) c.parent = this; c.initialize(device, physicsengine); // fix the base link so its immovable. Remove this if you plan to have the arm // attached through yet a different joint, to some other entity _baselink.physicsentity.iskinematic = true; // for each child arm, set the lower and upper joint for (int i = 0; i < _links.count; i++) ArmLinkEntity link = _links[i] as ArmLinkEntity; if (i < _jointcount) link.upperjoint = _joints[i]; // assume children.count == _joints.count + 1 if (i > 0) link.lowerjoint = _joints[i - 1]; // set the entity name in upper connector if (link.upperjoint!= null) link.upperjoint.state.connectors[0].entity = link; if (link.lowerjoint!= null) link.lowerjoint.state.connectors[1].entity = link; them // second pass inserts joints into the simulation, which initializes and activates // We have to do this after the joint connectors are all attached to entities for (int i = 0; i < _jointcount; i++) physicsengine.insertjoint((physicsjoint)_joints[i]); catch (Exception ex) HasBeenInitialized = false; InitError = ex.tostring(); 53
조인트들은 PhysicsJoint.Create 문장을통해물리엔진에서명확히초기화되어져야합니다. 시각적인표현을위한 3D 설계데이터의활용 모든 Arm 형태는기반 Arm 을제외하고는동일한캡슐모양을가집니다. 시각화를위해 3 개의메쉬파일이사용되었습니다. Lbr3_j0.obj 기반 Arm 을위해사용되는메쉬파일 Lbr3_j1.obj link1 부터 link4 까지의 Arm 을위해사용되는메쉬파일 Lbr3_j5.obj link5 를위해사용되는메쉬파일 Lbr3_j6.obj 구형 link6 을위해사용되는메쉬파일 그림 4 캡술모양 Lbr3_j1.obj 파일은유사한링크들의시각적표현을위해회전되고변환되어사용됩니다. Arm 의프로그램화된구현 아래단계에서보여지는메소드는맨처음의기반 Arm 과첫번째 Arm 두개를연결시키는조인트에필요한메소드입니다. void CreateBase(Joint joint) PhysicsJoint commonjoint = (PhysicsJoint)joint; float mass = 1; float radius = 0.03f; CapsuleShape baseshape = new CapsuleShape(new CapsuleShapeProperties(mass, new Pose(new Vector3(0, radius + 0.015f / 2, 0)), radius, 0.015f)); 54
// create connect point for base. Top and center of its shape. commonjoint.state.connectors[0] = new EntityJointConnector(null, new Vector3(0, 0, 1), new Vector3(0, 1, 0), // joint rotates around the Y(vertical axis) new Vector3(0, baseshape.capsulestate.radius * 2 + baseshape.capsulestate.dimensions.y, 0)); ArmLinkEntity link = new ArmLinkEntity("base", "lbr3_j0.obj", new Pose(new Vector3(0, -radius - 0.005f, 0), TypeConversion.FromXNA(xna.Quaternion.CreateFromAxisAngle(new xna.vector3(0, 1, 0), 0)))); link.state.pose.position = State.Pose.Position; link.upperjoint = commonjoint; _baselink = link; _baselink.capsuleshape = baseshape; _links.add(link); ArmLinkEntity _arm1link; /// <summary> /// Link 1 /// </summary> [DataMember] public ArmLinkEntity Arm1Link get return _arm1link; set _arm1link = value; void CreateArm1(Joint joint) PhysicsJoint commonjoint = (PhysicsJoint)joint; commonjoint.state.connectors[1] = new EntityJointConnector(null, new Vector3(0, 0, 1), new Vector3(0, 1, 0), new Vector3(0, 0, 0)); ArmLinkEntity link = new ArmLinkEntity("arm1", "lbr3_j1.obj", new Pose(new Vector3(0, -MIDPOINT_Y, 0), TypeConversion.FromXNA(xna.Quaternion.CreateFromAxisAngle(new xna.vector3(0, 1, 0), 0)))); link.state.pose.position = State.Pose.Position; _arm1link = link; _links.add(link); 아래그림은기반부분과첫번째 Arm 의연결상태를물리적관점에서표현한그림입니다. 둘다 55
모두조인트축이빨간색화살표로표시됩니다. 그림 5 기반부분과첫번째 Arm 연결 void CreateArm2(Joint joint) PhysicsJoint commonjoint = (PhysicsJoint)joint; commonjoint.state.connectors[0] = new EntityJointConnector( null, new Vector3(0, 1, 0), new Vector3(0, 0, -1), new Vector3(0, ARM_LENGTH2, 0)); commonjoint.state.connectors[1] = new EntityJointConnector( null, new Vector3(0, 1, 0), new Vector3(0, 0, -1), new Vector3(0, 0, 0)); ArmLinkEntity link = new ArmLinkEntity( "arm2", "lbr3_j1.obj", new Pose(new Vector3(0, MIDPOINT_Y, 0), Quaternion.RotationAxis(new Vector3(1, 0, 0), (float)math.pi))); link.state.pose.position = State.Pose.Position; _arm2link = link; _links.add(link); 위의코드에서두번의 new EntityJointConnector 호출은정상적인엔터티연결을위해매우중요한부분입니다. 해당객체내에서두번째와세번째파라메터로사용된값들은조인트들의방향을나타내며, 각각법선방향과로컬축방향을나타냅니다. 56
Step 4: 튜토리얼실행 튜토리얼예제는아래와같이실행될수있습니다. MSRS 실행창에서아래의명령어를실행하시기바랍니다. bin\dsshost /port:50000 /tcpport:50001 /manifest:"samples\config\simulationtutorial4.manifest.xml" 또한웹브라우저를실행하여아래의연결주소를통해실행상태를확인할수있습니다. http://localhost:50000/directory 그림 6 디렉토리서비스 위의서비스목록중에서 /simulatedlbr3 항목을클릭하면, 다음과같은개별조인트들의상태값을확인할수있습니다. 57
그림 7 시뮬레이션 Arm 의상태 초기 Obj 파일에대한변환 시뮬레이션파일은자동으로 Obj 파일을이진파일형태로변환합니다. 따라서맨처음실행될때변환에따른시간지연이발생할수있습니다. 58
그림 8 시뮬레이션으로표현된 Arm 이번튜토리얼을실행하면자동으로 SimpleDashboard 서비스가같이실행됩니다. SimpleDashboard 실행후서버명에 localhost 를입력하고 Connect 버튼을클릭합니다. 59
그림 9 SimpleDashboard 를실행한화면 화면하단에서 Articulated Arm 영역에있는 Connect 버튼을클릭하면, 우측에관련된조인트목록이표시됩니다. 60
그림 10 SimpleDashboard 에서조인트목록을가져온결과 목록에표시되는 Joint 항목들을선택한후 Angle 부분에각도값 (-90 ~ 90) 을입력합니다. 각도값입력후 Apply Changes 버튼을클릭합니다. 1. Joint1 에대해 50 값을입력후 Apply Changes 버튼을클릭합니다. 2. Joiny0 에대해 -90 도값을입력후 Apply Changes 버튼을클릭합니다. 3. 아래와같이 Arm 이이동후블록이차례로넘어지는결과를확인할수있습니다. 61
그림 11 시뮬레이션 Arm 의실행결과 62
시뮬레이션튜토리얼 5 (C#) 기하학적엔터티생성 이번튜토리얼에서는간단한기하학적엔터티생성에대한예제와이러한방법을이용하여복잡한엔터티를구현하는방법에대해소개합니다. 그림 1 렌더링된엔터티들 63
그림 2 엔터티들의물리적화면표시 시작하기 이번튜토리얼은 samples\simulationtutorials\tutorial5 폴더에소스코드가존재하며, SimulationTutorial5.csproj 파일을 Visual Studio 에서로드하여시작할수있습니다. Step 1: 서비스생성과시작 이번튜토리얼은이전의튜토리얼에서와같이 PopulateWorld 를호출하고여러개의엔터티를시뮬레이션공간상에추가하는방식으로진행됩니다. 이번튜토리얼의목적은기하학적엔터티를생성하는과정을분석하는것이기때문에, 대부분의작업은생성과초기화하는과정에서진행됩니다. private void PopulateWorld() // skydome and sun AddSky(); // basic ground AddGround(); // add simple 3D models with basic physics AddSimpleModels(); // single shapes from physics (with color or texture) 64
AddColoredShapes(); AddTexturedShapes(); // multi-part entities (with or without 3D model) AddFurniture(); // geometric Pioneer robot AddModularRobot(new Vector3(0.0f, 0.5f, 1.0f)); // convex shape from 3D model AddConvexMesh("table_02.obj", new Vector3(-0.5f, 1f, -2.5f)); AddConvexMesh("street_cone.obj", new Vector3(0.7f, 1f, -2.5f)); // triangle shape from 3D model AddTriangleMesh("table_02.obj", new Vector3(-0.5f, 0f, -1f)); AddTriangleMesh("street_cone.obj", new Vector3(0.7f, 0.37f, -1f)); 엔터티 시뮬레이터를효과적으로사용하기위해서는다양한엔터티들이어떻게생성되는지에대해이해하는것이중요합니다. 이번튜토리얼에서는다양한엔터티들이아래의정의에따라언급될예정입니다. 물리엔터티 : 시뮬레이션에대한물리엔진에의해사용되는물리객체의기본표현, 코드에서는 Ageia 물리엔진에의해와이어프레임모양으로나타내어집니다 (Sphere, Capsule, Box, HeightField, ConvexMesh, TriangleMesh) 렌더링엔터티 : 비주얼피드백을위해사용되는 3D 삼각모델. 코드에서는 VisualEntityMesh 클래스의인스턴스입니다. 시뮬레이션엔터티 : 시뮬레이터에서의기본엔터티. 단일시뮬레이션엔터티는물리엔터티를가지지않거나또는여러개의물리엔터티를가질수있고, 또한렌더링엔터티도가지지않거나또는여러개의렌더링엔터리를가질수있습니다. 코드에서는 VisualEntity 클래스 ( 또는이클래스로부터파생된클래스 ) 의일반적인인스턴스입니다. Step 2: Sky, Light 및 Lighting 엔터티 Sky SkyEntity 는시뮬레이션공간상에서하늘형상을표현하는시뮬레이션엔터티입니다. 이엔터티는기본적으로렌더링만을위해서사용되기때문에물리적으로대응되는대상을가지지않습니다. SkyEntity 는 Sun 과 Sky Dome 의복합으로구성됩니다. Sun 은직접적인빛이며벡터는빛이들어오는방향을나타내고, 빛의강도와색의농담을위한색상이지정됩니다. Sky Dome 은두개의 Cubemap 이미지파일을사용하여렌더링된전체적인공간을포함하는큰구를표현합니다. AddSkey() 메소드에서는아래와같이구현됩니다. // Add a sky using a static texture. We will use the sky texture // to do per pixel lighting on each simulation visual entity SkyDomeEntity sky = new SkyDomeEntity("skydome.dds", "sky_diff.dds"); SimulationEngine.GlobalInstancePort.Insert(sky); // Add a directional light to simulate the sun. 65
LightSourceEntity sun = new LightSourceEntity(); sun.state.name = "Sun"; sun.type = LightSourceEntityType.Directional; sun.color = new Vector4(1.0f, 1.0f, 1.0f, 1f); sun.direction = new Vector3(1.0f, -0.6f, 0.1f); SimulationEngine.GlobalInstancePort.Insert(sun); Cubemap 은 6 개의이미지로구성된텍스쳐입니다. 큐브의각면을위해하나의이미지씩사용합니다. 이러한맵의목적은환경을둘러싸고있는것들에대한시각적인표현을나타내며, 배경을표현하기위해사용됩니다. 또한이맵들은보다복잡한 lighting 의계산을위해서도사용됩니다. 이러한맵에는다양한형태의포멧이있습니다 : spherical, double ellipsoid, meridianparallel. Cubemap 은비디오카드에의해하드웨어적으로지원되는단순한포멧이며, Microsoft DirectX texture 파일 (.dds) 에직접저장될수있습니다. SkyEntity 는두개의 CubeMap 을사용합니다. 첫번째것은하늘을시각적으로표현하기위해사용되며아주세부적이고 Sky Dome 을시각화한것입니다. 두번째것은시뮬레이터엔터티들의이미지기반 lighting 환경을수행하기위해사용됩니다. 표면의각점들은해당법선 (normal) 방향에있는 Sky 의영역에해당되는부분으로부터빛을받습니다. 이러한종류의 lighting 표현은확산효과를나타냅니다. 환경맵은무한히먼환경을표현합니다. 즉, 두엔터티의상대적인위치는하늘로부터빛을표현하는데있어서중요하지않습니다. 만약두개의표면이동일한방향을가진다면, 그들은상대적인위치에관계없이동일한빛을받을것입니다. 대낮의하늘과밤의별들은관측자가이동하더라도변하지안는것을볼수있기때문에무한한거리의개념을적용하는있어서좋은예입니다. Cubemap 을다루기위한툴은 ATI 개발자웹사이트 (http://www.ati.com/developer/cubemapgen/index.html) 에서찾을수있습니다. 이툴은또한확산되는빛의계산에있어서하위의 cubemap 을만들어내기위해사용될수있습니다. Sky dome 으로사용가능한 Cubemap 은인터넷에서쉽게구할수있습니다. (Codemonsters 웹사이트 : http://www.codemonsters.de/html/textures_cubemaps.html). Prof. Paul Debevec 의홈페이지 (http://www.debevec.org/) 에서는 Cubemap 과 HDR sky probe( 다른포멧의 Cubemap) 들을구할수있습니다. Light Light 는아래와같이정의됩니다. Entity 를정의하는하나의이름 (String) 빛의행위를정의하는타입 (enum LightEntityType). Directional 과 Omni 타입을지원합니다. 빛의강도와농도를나타내는색상 (Vector4) 빛의위치와방향을정의하는 Pose (Pose) Directional Lights ㄴ항상동일한방향을가지고빛을비추는것을나타내며태양을좋은예제로볼수있습니다. Omni light 는해당위치에서모든방향으로빛을뿌리는것을나타냅니다. 전구와양초가좋은예가될수있습니다. 66
그림 3 - Directional Light 그림 4 - Omni Light Light 를시뮬레이션에추가하기위해서는 LightEntity 를생성한후에시뮬레이션엔진에추가해야합니다. Light 속성은수정되거나삭제될수있으며, 이름으로엑세스할수있습니다. // adding a new omni light LightSourceEntity mylight = new LightSourceEntity(); mylight.state.name = "RedLight"; mylight.color = new Vector4(1f, 0.5f, 0.5f, 1f); mylight.state.pose.position = new Vector3(10, 6, -10); mylight.type = LightSourceEntityType.Omni; SimulationEngine.GlobalInstancePort.Insert(mylight); SkyEntity 는 light 를포함하고있으며, 이러한 light 는 Sky 가시뮬레이션엔진에추가되자마자 Active light 들의리스트에추가된다는것에유의하시기바랍니다. Lighting 개요 PC 가표현하는색상은사람이인지하는대역에비해매우제한되어있습니다. 색상들은기본적으로 red-green-blue 값들의조합으로표시됩니다. Vector4 가색상을표현하기위해사용되며, 이중앞의 3 개의벡터값은 0.0 과 1.0 사이의값으로 red, green, blue 값을표현하기위해사용되고 32 비트실수형으로표시됩니다. Black 은 [0,0,0,1] 로표시되고, white 는 [1,1,1,1] 로표시됩니다. 마지막항목은투명도를결정하는 alpha channel 값을나타냅니다. 해당값을 1 로설정하면툴투명하게처리됩니다. 빛이공간에추가되고, 색상을통해빛의강도와농도가결정되면, 객체의색상은해당객체표면의색상과해당객체가 light 로부터받는빛과곱해져서표현됩니다. 67
Step 3: 파일로부터 3D Mesh 파일사용하기 3D 모델파일이가능하다면, 해당파일이름을지정하여 3D 모델을렌더링엔터티로사용하여시뮬레이션엔터리를생성하는것이가능합니다. 현재지원되는포멧은 Wavefront Obj (.obj) 포멧입니다. 아래의경우에서는메쉬파일을사용하는예를보여줍니다. private void AddSimpleModels() SingleShapeEntity table_a = new SingleShapeEntity( new BoxShape( new BoxShapeProperties( 20.0f, // mass in kilograms. new Pose(new Vector3(0.0f, 0.345f, 0.0f)), // relative pose new Vector3(1.2f, 0.7f, 1.2f))), // dimensions new Vector3(-0.5f,1f,-4.0f)); table_a.state.assets.mesh = "table_02.obj"; table_a.state.name = "table_angle"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(table_a); SingleShapeEntity cone = new SingleShapeEntity( new BoxShape( new BoxShapeProperties( 0.5f, // mass in kilograms. new Pose(), // relative pose new Vector3(0.45f, 0.7f, 0.45f))), // dimensions new Vector3(0.7f,2f,-4.0f)); cone.state.assets.mesh = "street_cone.obj"; cone.state.name = "StreetCone"; // Insert entity in simulation. SimulationEngine.GlobalInstancePort.Insert(cone); 시뮬레이션에서는 Physics 가엔터티의위치를나타내기때문에, 3D 모델이물리적표현에대해중심이일치하도록유의할필요가있습니다. [...] new BoxShape( new BoxShapeProperties( 20.0f, // mass in kilograms. new Pose(new Vector3(0.0f, 0.345f, 0.0f)), // relative pose [...] 불행히도, OBJ 파일포멧은많은수의하위포멧으로구성되고텍스트기반으로되어있어서, 성능상에문제가있습니다. 이러한문제를해결하기위해맨처음파일이로드될때, 시뮬레이션내부에서이러한파일을.bos 이름을가지는바이너리형태로변환되어저장됩니다. Step 4: 물리엔터티로부터 Geometric Mesh 사용하기 68
Simple Shape 들 AddColoredShapes 와 AddTexturedShapes 메소드는렌더링엔터티들이해당물리적대상으로부터알마나간단히얻어질수있는지를보여주는예입니다. 그림 5 랜더링된엔터티 69
그림 6 물리적표현의엔터티 아래의예제에서는 SingleShapeEntity 가 physical sphere shape 을통해생성됩니다. 3D 파일이생성되지안았으며, 시뮬레이터가메쉬형태를자동으로생성합니다. 이러한경우 diffuse 색상을임의로지정할수있으며, 만약별도로색상을지정하지않으면, 시뮬레이터가자동으로지정합니다. //---- bluish sphere ---- SphereShapeProperties csphereshape = null; SingleShapeEntity csphereentity = null; csphereshape = new SphereShapeProperties(10f, new Pose(), 0.8f); csphereshape.material = new MaterialProperties("bphere", 0.5f, 0.4f, 0.5f); csphereshape.diffusecolor = new Vector4(0.4f, 0.3f, 0.7f, 1.0f); csphereentity = new SingleShapeEntity(new SphereShape(cSphereShape), new Vector3(-2.0f, 1.0f, -3.0f)); csphereentity.state.name = "bluesphere"; SimulationEngine.GlobalInstancePort.Insert(cSphereEntity); 유사한방식으로텍스쳐를적용할수있으며, 아래의예제는텍스쳐파일을엔터티에적용한예입니다. //---- wooden sphere ---- SphereShapeProperties tsphereshape = null; 70
SingleShapeEntity tsphereentity = null; tsphereshape = new SphereShapeProperties(10f, new Pose(), 0.8f); tsphereshape.material = new MaterialProperties("tphere", 0.5f, 0.4f, 0.5f); tsphereentity = new SingleShapeEntity(new SphereShape(tSphereShape), new Vector3(-4.0f, 1.0f, -3.0f)); tsphereentity.state.assets.defaulttexture = "Wood_cherry.jpg"; tsphereentity.state.name = "texsphere"; SimulationEngine.GlobalInstancePort.Insert(tSphereEntity); 위의예제에서는 tsphereentity.state.assets.defaulttexture 를사용하여텍스쳐맵이엔터티전체에걸쳐적용되었습니다. 또한특별한 Shape 에한하여 tsphereshape.texturefilename 을사용하는것도가능합니다. 엔터티내에서는오직물리 Shape 만있기때문에, 결과는변경되지않습니다. 그러나두개이상의물리 Shape 을포함하고있는시뮬레이션엔터티들에대해서는적용되지않을수있습니다. 간단한 Shape 구성 아래그림에서첫번째객체는튜토리얼 2 번에서사용되는엔터티와동이합니다. 이 TableEntity 는 MultiShapeEntity 로부터파생되었으며, 여러개의물리엔터티들로정의되었습니다. 그림 7 - 렌더링된엔터티 71
그림 8 물리적표현의엔터티 만약 3D 메쉬파일이정의된다면, 시스템은해당물리적상태에대응하는단일의기하학적엔터티를생성합니다. 그리고메쉬가정의되지않는다면, 시스템은각각의물리적 shape 별로기하학적엔터티를생성합니다. 결과는위에서각각두번째테이블과세번째테이블로표현됩니다. 또한 TableEntity 에대해텍스쳐파일을정의함으로써렌더링된엔터티전체에대해텍스쳐를적용할수있습니다. 두번째테이블에서보여지는것처럼, 모든 Shape 은동일한텍스쳐를가집니다. // create an instance of the table custom entity // using physics-derived rendering entity and a single texture TableEntity physentityt = new TableEntity(new Vector3(2.5f, 1.0f, -2.0f)); physentityt.state.name = "table_phys_t"; physentityt.state.assets.defaulttexture = "cellwall.jpg"; SimulationEngine.GlobalInstancePort.Insert(physEntityT); 반대로, 엔터티내부에각 Shape 을특성화시킬필요가있는데, 이러한것은 DefaultTexture 할당을생략하고테이블에사용되는각 Shape 에대해텍스쳐나색상을지정하므로써가능합니다. 세번째테이블의각 Shape 이평평하지않은외관을가지면서색상이변해가는것을확인할수있습니다. // add a shape for the table surface BoxShape tabletop = new BoxShape( new BoxShapeProperties(10, new Pose(new Vector3(0, tableheight, 0)), 72
); new Vector3(tableWidth, tablethinkness, tabledepth)) tabletop.state.texturefilename = "Wood_cherry.jpg"; // add a shape for the left leg body BoxShape tableleftleg = new BoxShape( new BoxShapeProperties(2, // mass in kg new Pose( new Vector3(-tableWidth / 2 + legoffset, tableheight / 2, 0)), new Vector3(legThickness, tableheight, tabledepth / 4)) ); tableleftleg.state.diffusecolor = new Vector4(0.8f, 0.8f, 0.8f,1.0f); [...] BoxShapes = new List<BoxShape>(); BoxShapes.Add(tableTop); BoxShapes.Add(tableLeftLeg); 파라메터에서의우선순위 여러개의파라메터가동시에적용된다면, 일부충돌이발생할수있습니다 (color, per-shape texture, per-entity texture). 이러한경우아래의룰이적용됩니다. 로봇 색상과텍스쳐파라메터는엔터티가추가되지전에설정될수있습니다. 3D 메쉬가제공된다면, 색상과텍스쳐들은메쉬에서정의되고이러한값들이사용됩니다. 각각의엔터티별텍스쳐가정의된다면, 각각의 Shape 별색상과텍스쳐는무시됩니다. 각 Shape 텍스쳐는각 Shape 색상에우선합니다. 만약, 색상이나텍스쳐가정의되지않았다면, 객체는 grey (RGB [0.5f,0.5f,0.5f,1.0f]) 값으로표시됩니다. 색상과텍스쳐에대한고려는로봇객체에대해서도동일하게적용됩니다. 3D 메쉬파일이없어도로봇구성이가능하여, 기본적인물리객체들의조합으로구성이가능합니다. AddModularRobot 함수는튜토리얼 2 번에서소개되었던 Pioneer3 로봇을생성하지만, 메쉬파일이없기때문에기본적인물리 Shape 들로만구성이됩니다. 각 Shape 에대한색상을지정함으로써원본로봇과비슷하게로봇을꾸밀수있습니다. [...] Pioneer3DX robotbaseentity = new Pioneer3DX(position); // specify color. 0.3f, 1f); robotbaseentity.chassisshape.state.diffusecolor = new Vector4(0.7f, 0.3f, [...] 73
// Create a Laser Range Finder Entity. // Place it 30cm above base CenterofMass. LaserRangeFinderEntity laser = new LaserRangeFinderEntity( new Pose(new Vector3(0, 0.30f, 0))); laser.state.name = "LaserRangeFinder"; laser.laserbox.state.diffusecolor = new Vector4(0.3f,0.3f, 0.7f, 1f); [...] 그림 9 렌더링된엔터티 74
그림 10 물리적화면으로표현된엔터티 이예제에서는로봇을단순히박스들로만구성하였지만, 테이블과는달리각엔터티들이좀더정확한표현을가질수있도록여러개의 Shape 들로구성이되었습니다. 이러한방식으로 3D 메쉬파일이없어도로봇을간단한 Shape 들만의구성으로쉽게만들수있습니다. Step 5: Terrain 엔터티사용 일부시뮬레이션에있어서, 간단한바닥만으로는충분하지않을수있습니다. TerrainEntity 는각각의높이를지정한데이터소스로서비트맵을사용하여보다더복잡한바닥을생성할수있습니다. Terrain 을다루고렌더링하기위한많은데이터구조가있습니다만, 정밀한시뮬레이션을위하여, Robotics Studio 에서는 Ageia 데이터구조와유사한구조를사용합니다. Ageia HeightFieldShape 은최적화된메모리공간과충돌관리기능을제공하는샘플들의그리드를사용하여 heightfield 를표현합니다. Terrain 은렌더링에대해물리 Primitive 와균일한메쉬로서 HeightFieldShape 을사용하여구성되며, 해당텍스쳐가 Terrain 의맨위에매핑됩니다. private void AddGround() TerrainEntity ground = new TerrainEntity( "terrain.bmp", "terrain_tex.jpg", new MaterialProperties("ground", 0.2f, // restitution 0.5f, // dynamic friction 0.5f) // static friction ); SimulationEngine.GlobalInstancePort.Insert(ground); 엔터티는비트맵을로드하고높이값으로서픽셀값을사용하여 height field 를구성합니다. 가능한높이의범위가 0 부터 255 사이의정수형이기때문에, 약간블록화된형태로바닥이표시 75
가됩니다. 이러한이유로인해, 픽셀값은오프셋 (128 는높이 0 을나타냄 ) 을가지며, scale 을다운시켜 (10 으로나눈값이 10cm 의해상도를가짐 ) 사용합니다. 샘플들간의거리는 1 미터로가정을합니다. 정확히작동하게하기위하여, Terrain 비트맵은 32 배수에 1 을더한차원으로구성됩니다 ( 예 129*129, 65*257). 그림 11 렌더링된 Terrain 엔터티 76
그림 12 물리적화면으로표현된 Terrain 엔터티 로딩된 height field 는 Chunk 로분할되며, 각 Chunk 에대해물리적 HeightField 와해당되는메쉬가생성됩니다. Field 를 Chunk 로분할하여임의의크기의 Terrain 을만드는것이가능합니다. 단일 HeightFieldShape 는크기가제한되어있지만임의의숫자의 HeightFieldShape 들을사용할수있습니다. 또다른장점은렌더링시간을들수있으며, 이를통해 Frustum Culling 을수행할수있고현재의 View 안에존재하지않는모든 Chunk 들을렌더링하는것을방지할수있습니다. 추가적으로, Chunk 구조는 Level-Of-Detail (LOD) 접근법과같은보다더유연한렌더링전략을구현하기위해수정할수도있습니다. TerrainEntity 대신에, TerrainEntityLOD 를사용하는것도가능하며이시뮬레이션엔터티는그려지는삼각형의수를감소시키기위한대략적인 Level-Of-Detail 접근법을사용합니다. 각 Chunk 는카메라와의거리에의존하는 Detail Level 의렌더링과연관되며, 이 Level 은각프레임단위로갱신됩니다. 개발자들은 Terrain 데이터구조를각개발자들의요구에맞게수정할수있습니다. 이러한용도로만들어진두개의엔터티들에대한소스코드가 entities.cs 에포함되어있으며, 이파일은 samples\simulation\entities\ 폴더에있습니다. Terrain 에대한비트맵을생성하기위해, Windows 의그림판프로그램이나또는인터넷에서구할수있는비디오게임용의다양한 Terrain 편집기를사용할수있습니다. 이러한편집기의대부분은 heightfield 를이미지형태로추출할수있습니다. 비트맵의또다른대안으로서, 엔터티는 GridFloat 파일로부터로딩하는것을지원합니다. 이파일은 GIS 포멧으로서지형적데이터를표현하기위해사용됩니다. 이포멧은상당히단순하며데이터크기와속성에대한정보를헤더정보로포함하고헤더파일 (.hdr) 과그리드상에정렬되어있는 height 샘플들의리스트를포함하고있는데이터파일 (.flt) 로구성됩니다. 이러한포멧을이용한다면, 실제기하학적으로측정된 77
데이터를등록시킬수있습니다. 이방법은미국의 United States Geological Survey (USGS) 웹사이트로부터데이터를다운로드받을때선택가능한포멧중의한가지입니다. USGS 의 "Seamless" 웹페이지에서 USGS 데이터베이스중에서임의지역을선택한후, 포멧을선택하여웹기반방식으로데이터를자유롭게다운로드받을수있습니다. Step 6: 기하학적엔터티로부터물리엔터티얻기 어떠한경우에서는간단히그대로사용가능한기하학적기능을제공하는것이더편리할수있으며, Ageia 사의물리엔진은이러한것을위해서내장된기능을제공합니다. 즉, 기하학적엔터엔터부터시작하여, 물리엔진은물리적표현으로서사용가능한근사한형태의 Convex Hull 을생성해낼수있습니다. private void AddConvexMesh(string name, Vector3 pos) Shape cshape = null; SimplifiedConvexMeshEnvironmentEntity centity = new SimplifiedConvexMeshEnvironmentEntity( pos, name, cshape); centity.state.massdensity.mass = 30; centity.state.name = "Convex mesh:" + name + ":" + Guid.NewGuid().ToString(); SimulationEngine.GlobalInstancePort.Insert(centity); 3D 메쉬가로드되고엔터티를렌더링하기위해사용됩니다. 생성된물리엔터티는 Ageia 엔진에의해사용됩니다. 물리적형태로서원래의기하학적데이터를사용할수있는데, 이러한물리적엔터티는엔터티를렌더링할때의동일한삼각형들을사용하여구성됩니다. 이러한방식을통해서만들어지는결과는 3D 모델데이터에의정교함과유사한정도로만들어질수있습니다. 엔터티들은세밀한충돌을감지할수있으나상당히높은컴퓨터연산을요구합니다. 그럼에도불구하고, 이러한경우에서는해당메쉬가이동이불가능한엔터티로서만사용될수있습니다. 모든물리객체들은엔터티들과충돌할때정확하게반응을해야하지만, 만약 mass 값을 0 으로설정하면, 해당엔터티들은움직이지않게됩니다. private void AddTriangleMesh(string name, Vector3 pos) Shape tshape = null; TriangleMeshEnvironmentEntity tentity = new TriangleMeshEnvironmentEntity( pos, name, tshape); tentity.state.massdensity.mass = 0f; tentity.state.name = "Triangle mesh:" + name + ":" + Guid.NewGuid().ToString(); SimulationEngine.GlobalInstancePort.Insert(tentity); 위의예제에서는두개의 3D 모델데이터가 AddSimpleModels 메소드에서사용되었습니다. 아래의화면을통해서도확인할수있듯이, 주어진 3D 모델데이터에대해물리형상들이각각다른수준의근사치를가질수있음을확인할수있습니다. ( 아래그림에서는매우조잡한형태의박스 78
형상과, Convex Hull 형상, 그리고물리적충돌엔터티를구현하기위해사용된기하학적형상등의예제를볼수있습니다 ). 그림 13 렌더링된엔터티들 79
그림 14 물리적형태로표현된엔터티들 주의사항 이들두가지형태의연산들은상당히복잡하고완성되기위해상당한시간이소요될수있습니다 ( 예를들어감지할수있을정도의몇초정도 ). 하지만첫번째실행후에는결과가바이너리파일형태로원래의메쉬파일경로와동일한경로안에저장되기때문에, 두번째실행부터는빠르게샐행됩니다. (.convex.physics.bin 또는.triangle.physics.bin 와같이해당파일뒤에붙습니다 ). 이러한방식에있어서, 한가지문제점은이러한연산들은잘정의된메쉬파일이입력될때가능하다는부분입니다. Flipped 삼각형, Topological 불균형, 또는 self-intersecting 된부분과같은문제를표현하는메쉬들에대해서는 Convex 형태생성이안될수도있습니다. 이러한경우에있어서는엔터티들이시뮬레이터에안나타날수있습니다. 삼각모양의생성은이러한유형의문제들보다는좀더잘작동되는편이나기타일부 3D 모델에대해서는여전히실행이되지안을수있습니다. Step 7: 튜토리얼실행 프로젝트를빌드한후, Robotics Studio command 창에서아래와같이실행시킬수있습니다. bin\dsshost /port:50000 /tcpport:50001 /manifest:"samples\config\simulationtutorial5.manifest.xml" 80
시뮬레이션튜토리얼 6 (C#) 시뮬레이션편집기 이번튜토리얼에서는 Visual 시뮬레이션환경툴을활용하여, 엔터티들을생성하거나수정하는방법들에대해소개합니다. 시작하기 이번튜토리얼을위한 C# 코드는없으며, 시뮬레이터환경툴만이사용됩니다. Step 1 : 시뮬레이터실행 시작메뉴에서 MSRS 메뉴를선택한후, Visual Simulation Environment 하부에있는 Basic Simulation Environment 메뉴를선택하여실행합니다. 그림 1 - Microsoft Visual 시뮬레이션환경 시뮬레이션이실행되고나면마우스를움직여서카메라의방향을움직일수있습니다. 단, 마우스를움직이는것은카메라의방향만변경시킬뿐, 카메라자체의위치를변경시키지는못합니다. 카메라의위치를변경시키기위해서는 W 키를눌러앞으로이동시키거나, S 키를눌러뒤로이동합니다. A 와 D 키는각각왼쪽과오른쪽으로이동시키는기능을수행하며, Q 와 E 키는각각위또는아래로이동시키는기능을수행합니다. 파일메뉴에서 Open 메뉴를선택한후 samples\config\lego.nxt.tribot.simulationenginestate.xml 파일을오픈합니다. 81
그림 2 - LEGO NXT Tribot 예제를오픈한결과 Open Manifest 메뉴는해당매니페스트파일을실행시키며, Capture Image 메뉴는현재의실행상태를이미지파일로저장합니다. 기타각각의메뉴에대한대략적인설명은시뮬레이션사용자가이드부분에포함되어있는비주얼시뮬레이션환경메뉴페이지를참고바랍니다. Settings 메뉴에서는렌더링설정에대한상세한옵션이제공됩니다. 이러한설정항목은 config\simulationeditor.config.xml에저장되어있어서, 다음번실행시에도그대로적용됩니다. 82
그림 3 - Graphics Settings 다이얼로그 Exposure 는보여지는화면의밝기를조정합니다. 보다작은값은어둡게만들고큰값을밝게만듭니다. 그래픽카드가 anti-aliasing 을지원할경우, Antialiasing 값을설정할수있습니다. 이러한모드의적용은그래픽처리성능을약간저하시킬수있으나, 아래그림에서와같이적용후에는각면이만나는부위가계단현상처럼보이지않고좀더매끈한형태로표시됩니다. 그림 4 Antialiasing 가지원되지않는경우 83
그림 5 Antialiasing 가지원되는경우 Rotation Movement Scale 과 Translation Movement Scale 은시뮬레이터에서마우스와키보드의반응속도를지정합니다. 높은값일수록동일한입력조건에대해서좀더빨리인터럽을발생시켜더빨리회전과변환이이루어지도록합니다. Quality Level 은사물들에서의빛의처리상태를설정합니다. 높은값을수록보더더정교하게처리되나, 높은연산을요구하므로, [recommended] 로표시되어있는항목보다높일경우, 렌더링이적용되지않을수있습니다. 이부분은컴퓨터그래픽카드에서지원하는 Pixel Shader 버전과연관이있으며, 버전이낮을경우지원이되지않을수있습니다. Physics 메뉴에서는 Physics 엔진을활성화하거나또는비활성화시킬수있으며, F3 키에의해서도설정이가능합니다. Settings... 메뉴에서는메인카메라를하나의사물로활용하는옵션과중력을설정하는기능을지원합니다. 만약카메라를강체로활용하는것으로체크한다면, 메인카메라는하나의강체로작용하며, 카메라를이동시켜서다른사물과충돌하게하여다른사물을움직이게할수있습니다. 그림 6 - Physics Settings 다이얼로그 또한중력값을변경하게되면, 다양한현상을만들수있습니다. 예를들어우주에서와같은무중력상태를만들고자할경우에는값을 0 으로설정하면되며, 반대로값을 0 보다큰값으로설정하면, 위쪽으로중력이작용하여, 사물들이모두위로날아가버리는현상을확인할수있습니다. 84
그림 7 Gravity 를 0 으로설정하였을경우의무중력상태 Mode 메뉴는시뮬레이션의실행모드와편집모드를토글로선택하며, F5 키를통해서도수행가능합니다. Edit 모드의실행결과는아래와같습니다. 그림 8 - Edit 모드 85
왼쪽상단은엔터티윈도우창이며, 왼쪽하단은속성창입니다. 편집모드에서는각각의엔터티들에대해복사, 잘라내기또는붙여넣기등의기능을수행할수있습니다. 또한이미설정되어있는값도변경이가능한데, Sky 엔터티를선택한후, 속성창에서 VisualTexture 항목에설정되어있는텍스쳐맵을 Directions.dds 값을선택하여변경하면아래와같이하늘이변경된것을볼수있습니다. 그림 9 - Sky Texture 변경 또한각엔터티의 Rotation 또는 Position 항목을선태한후, Ctrl 키를누르고마우스를움직여서해당엔터티를직접원하는형태로회전시키거나옮겨놓을수있습니다. 그리고 Ctrl+C 키를눌러서각엔터티를복사한후 Crtl+V 를눌러붙여넣기해놓을수있습니다. 또한크기값도변경이가능한데, 예를들어첫번째튜토리얼샘플을로드한후, 속성창에서 BoxShape 의 Height 값을 1 에서 2 로변경하였을경우, 아래와같이크기가변경되는것을확인할수있습니다. 86
그림 10 Shape 의속성변화 Sun 과같은조명과관련된엔터티의경우에는빛을제어하기위한매우세부적인속성항목들이존재합니다. Sun 은 LightSourceEntity 로생성된객체이며, LightProperites 안에세부적인속성항목들을가집니다. 중요한속성항목으로서 Type 이있는데, Type 값으로 Omni, Directional, 그리고 Spot 항목이선택가능합니다. Omni 는하나의지정된지점에서모든방향으로빛을비추는옵션이며, Directional 은특정방향에서모든사물에빛을비추는옵션입니다. Spot 은오직 Umbra 또는해당 Cone 영역에만빛을비추는옵션입니다. Umbra 값이커지면 Cone 의영역이넓어집니다. Spot 은또한 position 과 rotation 속성을통해방향과위치를지정할수있습니다. SpotUmbra 값을 45 로설정하였을경우, 바닥에빛이비추어지는영역이작아지는것을볼수있을것입니다. 또하나의유용한속성으로 CastsShadows 가있습니다. 이값을 True 로설정하면, 모든사물에그림자가만들어지는것을볼수있습니다. 이러한속성의설정은다양한시뮬레이션결과를만들어내기는하나사전에미리계산하는작업이많이수행되어성능저하를유발할수있습니다. 87
그림 11 - Spotlight 에서의 Shadows Cast 시뮬레이션상의사물에대해서다양한색상옵션또한세부적인설정이가능합니다. 해당엔터티에대해 Mesh 항목을세부적으로확장선택해나가면, Ambient, Diffuse, Specular, 그리고 Power 속성값들을볼수있습니다. Ambient 값을 1, 0, 1, 1 로설정하면강한농도를가진핑크색으로엔터티가표현되는것을볼수있습니다. 각엔터티의외부표현방식을쉽게수정하는방법은 RenderingMaterial 에표시되어있는버튼을눌러실행시킬수있는 material editor 를이용하는방법입니다. 이편집기에서는 ambient, diffuse, specular, power (shininess) 속성들을손쉽게수정하는기능을제공합니다. 88
그림 12 - Material editor 시뮬레이션편집모드에서는또한새로운엔터티를추가해놓을수있습니다. Entity 메뉴에서 New Entity 를선택하면아래와같은다이얼로그창이보여지며, 적절한값의선택을통해새로운엔터티를추가시킬수있습니다. 그림 13 - New Entity 다이얼로그 Type 에서 Pioneer3DX 를선택하고이름을 "MyRobot" 로지정한후 OK 버튼을클릭하면, 또 Pioneer3DX 의세부적인옵션을지정하는또다른창이표시됩니다. 이창에서 Position 벡터값을 0, 0, 0 으로지정합니다. 89
이번에는 New Entity 메뉴를다시실행하여 LaserRangeFinderEntity 를선택하고해당이름을 lrf 로지정합니다. 또한 Positon 을 (0, 0.3, 0) 으로지정합니다. Lrf 엔터티를선택한후, InitError 속성을확인해보면, 해당엔터티가오직 Child 이어야한다는오류메시지를확인할수있을것입니다. 따라서, Lrf 엔터티를선택한후 Ctrl+X 를눌러잘라내기합니다. 그리고앞서생성한 Pioneer3DX 로봇을선택한후붙여넣기하면, 자동으로 Lrf 가해당로봇에 Child 로서추가됩니다. 그림 14 - Pioneer3DX 에추가된 Laser Range Finder 기타다른속성들에대해서도프로그램개발작업에서지정되는것과동일하게값을지정하여, 직접코드로개발한것과동일한작업들을수행할수있습니다. 즉, ServiceContract 등의속성등을 SimulatedDifferentialDrive 값으로지정함으로써, SimpleDashboard 등을통하여로봇을직접제어할수있도록할수있습니다. 90