ODE V0.5 사용자 가이드

From IT도서관

Jump to: navigation, search

목차

소개

Open Dynamics Engine (ODE)는 관절식 강체 동역학을 시뮬레이션하는 상용 수준의 공개 라이브러리로 차량이나 다리가 달린 생물체, 또는 가상 현실 환경에서 움직이는 물체들을 시뮬레이션하는 데 적합하다. ODE는 빠르고, 유연하며 견고합니다. 또한 자체 충돌검사 기능도 가지고 있습니다. 현재 러셀 스미스씨가 ODE를 개발 중이고, 몇 명의 공헌자들의 도움을 받고 있습니다.

'강체 시뮬레이션'의 뜻을 모르시는 분들은 물리 SDK 란?을 읽어보시기 바랍니다.

이 문서는 ODE 버전 0.5의 사용자 가이드입니다. 버전 번호가 낮음에도 불구하고 ODE는 상당히 완성되고 안정적입니다.

특징

ODE는 관절로 연결된 강체 구조들을 시뮬레이션하는 데 훌륭한 라이브러리입니다. 관절로 연결된 강체구조란 다양한 모양의 강체들이 다양한 종류의 관절들을 통해 연결되어 있는 구조입니다. 바퀴가 차체에 연결된 차량, 다리가 몸에 연결되어 있는 생물, 또는 물체더미들이 좋은 예들입니다.

ODE는 상호작용(interactive) 실시간 시뮬레이션에 사용할 수 있도록 설계되었습니다. 변덕스러운 가상현실 환경에서 이동중인 물체들을 시뮬레이션하는 데 특히 훌륭합니다. ODE는 빠르고 견고하고 안정적이기 때문입니다. 심지어는 사용자가 시뮬레이션 도중에 시스템의 구조를 바꿀 수 있는 완벽한 자유를 가집니다.

ODE는 매우 안정적인 적분기(integrator)를 사용하므로 시뮬레이션 오류가 커져도 제어를 잃지 않습니다. 이것의 물리적 의미는 시뮬레이션되는 시스템은 어떠한 이유로도 "폭주(explode)"해서는 안된다는 뜻입니다. (다른 시뮬레이터에서는 사용자의 부주의로 인해 폭주하는 일이 많이 일어납니다.) ODE는 물리적 정확성보다는 속도와 안정성을 중시합니다.

ODE는 딱딱한 충돌을 가집니다. 이것은 두 강체가 충돌할 때는 항상 특수한 비-관통 제약(non-penetration constraint)을 사용한다는 것을 의미합니다. 많은 다른 시뮬레이터들은 접촉을 표현하기 위해 가상 용수철을 사용합니다. 이것은 제대로 작동하기 어렵고 오류가 나기 쉽습니다.

ODE는 자체 충돌 검사 시스템을 가지고 있습니다만 이것 대신에 자신만의 충돌 검사 시스템을 만들어 사용할 수도 있습니다. 현재의 충돌 기본 요소는 구, 상자, 뚜껑 달린 원통, 평면, 반직선 그리고 삼각형 메쉬(mesh)입니다. (충돌 물체에 대해서는 나중에 더 자세히 설명하겠습니다.) ODE의 충돌 시스템은 '공간(space)'이라는 개념을 통해서 잠재적인 충돌 가능성이 있는 물체들을 빠르게 식별할 수 있게 해 줍니다.

ODE의 특징은 다음과 같습니다.

  • 임의 질량 분포를 가진 강체.
  • 관절 타입: 구상, 경첩, 미닫이, 경첩-2, 고정, 각모터, 십자축 관절.
  • 충돌 기본 요소: 구, 상자, 뚜껑 달린 원통, 평면, 반직선, 삼각형 메시.
  • 충돌 공간: 쿼드 트리(사분 트리), 해시(hash) 공간, 단순 공간
  • 시뮬레이션 방식: 운동 방정식은 Trinkle/Stewart와 Anitescu/Potra에 따른 라그랑지 승수 속도 기반 모델(Lagrange multiplier velocity based model)에서 파생되었습니다.
  • 1차 적분기를 사용 중입니다. 이것은 빠르지만, 계량 공학에 대해서는 아직 충분히 정확하지 않습니다. 나중에 더 높은 차수의 적분기가 등장할 것입니다.
  • 시간 스텝(step) 함수 선택: 표준 "큰 행렬(big matrix)" 방식 또는 새로운 반복 QuickStep 방식을 사용할 수 있습니다.
  • 접촉과 마찰 모델: 비록 ODE가 쿨롱 마찰 모델에 대한 더 빠른 근사를 구현하지만, 이것은 Baraff가 기술한 Dantzig LCP solver에 기반을 두고 있습니다.
  • 순수 C 인터페이스를 사용 (하지만 ODE는 거의 C++로 작성하였음).
  • C인터페이스를 바탕으로 한 C++ 인터페이스를 가짐.
  • 많은 단위 검사, 그리고 항상 코드를 추가 작성.
  • 플랫폼 한정적인 최적화들.
  • 미쳐 언급하지 못한 기타 특징들...

ODE 라이센스

ODE is Copyright © 2001-2004 Russell L. Smith. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the terms of EITHER:

  1. The GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The text of the GNU Lesser General Public License is included with this library in the file LICENSE.TXT.
  2. The BSD-style license that is included with this library in the file LICENSE-BSD.TXT.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files LICENSE.TXT and LICENSE-BSD.TXT for more details.

ODE 커뮤니티

ODE에 대해 궁금한 점이나 제안하실 점이 있으신 분들이 계신가요? 아니면 그냥 많은 도움이 필요하신 분들이라도? 그렇다면 ODE 메일링 리스트에 글을 남겨주세요.

ODE 설치와 사용법

ODE 설치하기

단계 1: ODE 압축을 푼다.

단계 2-4 (윈도우): 윈도우즈에서 MSVC를 사용 중 이라면, 배포판의 VC6 하위 디렉토리에서 워크 스페이스와 프로젝트 파일들을 사용할 수 있다.

단계 2: GNU make 툴을 얻는다. 많은 유닉스 플랫폼들은 make 또는 gmake를 가지고 있다. 윈도우즈용 GNU make는 여기에서 구할 수 있다.

단계 3: 파일에서 설정을 편집하라. 지원되는 플랫폼 리스트가 그 파일에 나와있다.

단계 4: 환경 설정하고 ODE와 그래픽 테스트 프로그램을 빌드하기 위해 GNU make를 실행하라. 환경 설정 과정은 include/ode/config.h 파일을 생성한다.

단계 5: ODE 라이브러리를 시스템에 설치하기 위해서, lib/ 와 include/ 디렉토리를 적절한 곳으로 복사하라, 즉 유닉스에서는:

  • include/ode/ --> /usr/local/include/ode/
  • lib/libode.a --> /usr/local/lib/libode.a

MacOS X 에서 ODE 테스트를 빌드하고 실행하기

ODE는 시뮬레이션되는 장면을 렌더링하기 위해 XWindows와 OpenGL를 사용한다. 예제를 빌드하기 위해서 Apple X11 서버와 X11SDK를 설치해야한다(일반 개발 툴도 함께 설치한다).

이런 것들은 Apple로부터 얻을 수 있다. http://www.apple.com/macosx/x11. 주의: SDK 페이지의 우측 하단에 조그만 링크가 있다.

소프트웨어가 설치되면 일반적인 빌드 지시들을 따르라.

ODE는 X11를 사용하기 때문에 X11 서버를 실행해야한다 (설치를 했으면, Applications 폴더에 있을 것이다).

X11 서버가 기본으로 여는 XTerm에서 테스트 프로그램을 실행하면, 잘 작동이 될 것이다. 그러나 만약 MacOs X Terminal에서 실행한다면, 환경 변수 DISPLAY를 정의할 필요가 있다. DISPLAY가 정의되지 않으면, "cannot open X11 display" 라는 메시지를 보게 될 것이다.

boxstack 테스트 프로그램을 실행하는 예

   cd ode/test
   DISPLAY=:0.0 ./test_boxstack.exe

쉘 시작 스크립트에서 환경 변수를 정의할 수 있다.

ODE 사용하기

ODE의 사용법을 이해하는 가장 좋은 방법은 test/example안에 있는 프로그램들을 보는 것이다. 다음 사항에 주의하라:

  • ODE를 사용하는 소스 파일은 단 하나의 헤더 파일만을 포함 시키면 된다:
  1. include <ode/ode.h>

여기서 ode 디렉토리는 실제로 ODE 배포본의 include/ode 이다. 이 헤더 파일은 ode 디렉토리 안의 다른 파일들을 포함할 것이다. 그러므로 컴파일러의 include 경로를 설정해 줄 필요가 있다. 가령 리눅스에서는

gcc -c -I /home/username/ode/include myprogram.cpp

  • ODE는 dWorldStep함수와 함께 사용될 때, 임시 값들을 저장하기 위해 스택을 적극 활용한다. 매우 큰 시스템에 대해 수 메가 바이트의 스택이 사용될 수 있다. 특히 윈도우즈에서 알수없는 out-of-memory 오류나 데이타 훼손을 경험하였다면, 스택의 크기를 늘리거나 dWorldQuickStep으로 바꿔라.

개념

배경

[이 장에서 강체 동역학과 시뮬레이션에 대해 배경 지식을 작성하는 동안 Baraff의 훌륭한 SIGGRAPH tutorial를 참고하라].

강체

시뮬레이션의 관점에서보면 강체(rigid body)는 다양한 속성을 가진다. 몇가지 속성은 시간에 따라 변한다:

  • 강체의 기준점(point of reference)의 위치 벡터 (x,y,z). 현재 기준점은 강체의 질량 중심(center of mass)과 일치해야 한다.
  • 기준점의 선속도, 벡터 (vx,vy,vz).
  • 강체의 회전, 사원수 (qs,qx,qy,qz) 또는 3x3 회전 행렬로 표현.
  • 각속도 벡터 (wx,wy,wz), 시간에 따라 회전이 어떻게 변하는 지를 표현.

나머지 강체 속성들은 시간에 따라 항상 일정하다:

  • 강체의 질량.
  • 기준점에 관한 질량의 중심의 위치. 현재 구현에서는 질량의 중심과 기준점이 일치해야 한다.
  • 관성 행렬(inertia matrix). 이것은 강체의 질량이 질량 중심 주위로 어떻게 분산되는지를 나타내는 3x3 행렬이다.

개념적으로 각 강체는 x-y-z 좌표틀(coordinate frame)을 포함하고 있다. 그림1에서 보듯이, 좌표틀은 강체와 함께 움직이고 회전한다.


body.jpg

그림 1: 강체 좌표틀.

이 좌표틀의 원점은 강체의 기준점이다. ODE에서 어떤 값들(벡터, 행렬 등)은 강체 좌표틀에 관련이 있고, 나머지 값들은 전역 좌표틀에 관련이 있다.

강체의 모양(shape)은 동역학적인 속성이 아니다(다양한 질량 속성에 영향을 주는 점을 제외한다). 강체의 상세한 모양은 충돌 검사에서만 고려된다.

섬과 비활성화된 강체

강체들은 관절에 의해 서로 연결된다. "섬(island)"은 분리될 수 없는 강체들의 그룹이다 - 바꿔 말하면 각 강체는 섬안의 다른 모든 강체들과 연결되어 있는 것이다. 시뮬레이션 스텝이 진행될 때 월드 안의 각 섬은 따로따로 처리된다. 이것을 알아두는 것이 도움이 된다: 시뮬레이션안에 N 개의 비슷한 섬들이 존재한다면, 스텝 계산 시간은 O(N)이 될 것이다.

각각의 강체는 활성화또는 비활성화될 수 있다. 비활성화된 강체는 사실상 "꺼진" 상태이고, 시뮬레이션 스텝동안 갱신되지 않는다. 강체가 정지한 상태이거나 시뮬레이션과 무관할 때 강체를 비활성화하는 것은 계산 시간을 절약하는 효과적인 방법이다.

섬 안에 활성화된 강체가 하나라도 있다면, 섬 안의 모든 강체가 다음 시뮬레이션 스텝에서 활성화될 것이다. 따라서 섬을 효과적으로 비활성화하기 위해서, 섬 안의 모든 강체가 비활성화되어야 한다. 만약 비활성화된 섬이 다른 활성화된 강체와 충돌하게 된다면, 접촉 관절이 그 활성화된 강체를 섬에 연결시킴에 따라 전체 섬이 활성화될 것이다.

적분

시간의 흐름에 따라 강체 시스템을 시뮬레이션하는 과정을 적분(integration)이라고 한다. 각 적분 step은 주어진 스텝 크기만큼 현재 시간을 진행시킨다. 그리고 새로운 시간 값에 대해 모든 강체의 상태를 조정한다. 적분기로 작업할 때 고려해야 할 점이 2가지가 있다:

  • 얼마나 정확한가? 즉, 시뮬레이션되는 시스템의 행동이 실세계에서 일어나는 것과 얼마나 가깝게 일치하는가?
  • 얼마나 안정적인가? 즉, 계산 오류는 항상 시뮬레이션되는 시스템의 완전히 비-물리적인 행동을 야기한다. (즉, 아무 이유없이 시스템이 "폭주"한다)

ODE의 현재 적분기는 매우 안정적이지만, 스텝 크기가 작지 않으면 정확성이 떨어질 수 있다. 대부분의 ODE 사용에 있어서 이것은 문제가 되지 않는다 -- ODE의 행동은 거의 모든 경우에 완벽하게 현실적으로 보인다. 그러나 ODE는 앞으로 정확성 문제가 완전히 해결되지 않는한 계량 공학의 용도로 사용되어서는 안된다.

힘 누적기

각 적분기 스텝 사이에서 사용자는 강체에 힘을 적용하는 함수들을 호출할 수 있다. 이 힘들은 강체 오브젝트 안의 "힘 누적기(force accumulators)"에 추가된다. 다음 적분기 스텝이 일어날 때, 적용된 모든 힘의 합이 강체를 미는 데 사용될 것이다. 힘 누적기는 각 적분기 스텝이 끝나면 0으로 설정된다.

관절과 구속

실제 생활에서 관절(joint)은 경첩(hinge)과 같은 것이다. 관절은 두 오브젝트를 연결하는 데 쓰인다. ODE에서 관절도 이와 매우 유사하다: 이것은 두 강체 사이에 강제된 하나의 관계이다. 관절은 강체가 서로에 대해 특정한 위치와 회전을 가질 수 있도록 한다. 이 관계를 구속(constraint)이라 한다 -- 관절과 구속이라는 말은 서로 바꿔가며 자주 사용된다. 그림2는 3개의 다른 구속 타입을 보여준다.

joints.jpg

그림 2: 3가지 다른 구속 타입들.

첫번째 관절은 구상 관절(ball and socket joint)이다. 구상 관절은 하나의 강체 "공(ball)"과 또다른 하나의 강체 "소켓(socket)"이 같은 위치에 있도록 한다. 두번째 관절은 경첩 관절(hinge joint)이다. 경첩 관절은 경첩의 두 부분이 같은 위치에 있도록 그리고 경첩축을 따라 정렬되도록 제한한다. 세번째 관절은 미닫이 관절(slider joint)이다. 미닫이 관절은 "피스톤(piston)"과 "소켓(socket)"을 동일 선상에 오도록 구속하고, 추가로 두 강체가 같은 회전을 갖도록 구속한다.

적분기가 한 스텝을 거칠 때마다 모든 관절들이 영향을 주는 강체들에 구속력(constraint forces)을 적용하는 것이 허락된다. 이러한 힘들은 강체가 모든 관절 관계를 유지하면서 움직이도록 계산된다.

각 관절은 그것의 기하구조를 제어하기 위한 수많은 매개 변수들을 가진다. 구상 관절의 위치 벡터가 그 예다. 관절 매개 변수들을 설정하는 함수들은 강체 좌표계(body-relative coordinates)가 아닌 전역 좌표계(global coordinates)를 받아들인다. 그러므로 관절이 연결하는 강체는 관절이 부착되기 전에 올바른 위치에 있어야 한다.

관절 그룹

관절 그룹(joint group)은 관절들을 하나의 그룹으로 보관하는 특별한 컨테이너(container)이다. 관절들은 그룹에 추가될 수 있고, 관절들이 더이상 필요없을 때 한번의 함수 호출로 관절들의 전체 그룹이 매우 빨리 소멸할 수 있다. 그러나, 그룹내의 각각의 관절들은 전체 그룹이 비워질 때까지 소멸할 수 없다.

이것은 접촉 관절들(contact joints)에 가장 유용하다. 접촉 관절들은 매 시간 스텝마다 그룹으로부터 추가 및 삭제된다.

관절 오류와 오류 감쇠 매개 변수 (ERP; error reduction parameter)

관절이 두 강체에 부착될 때, 강체들은 서로에 대한 특정한 위치와 회전을 갖도록 요구된다. 그러나 관절 구속이 충족되지 않는 위치에 관절이 있을 수도 있다. 이러한 "관절 오류"는 다음 두가지 방식으로 일어날 수 있다:

  1. 사용자가 다른 강체의 위치/회전을 올바로 설정하지 않고 강체의 위치/회전을 설정한 경우
  2. 시뮬레이션 도중, 오류가 점점 증가하여 원하는 위치로부터 강체가 떨어져 있는 경우

그림 3은 구상 관절의 오류를 보여준다. 공과 소켓이 정렬되어 있지 않다.

ball-and-socket-bad.jpg

그림 3: 구상 관절의 오류 예.

관절 오류를 줄이는 메카니즘이 있다: 각 시뮬레이션 스텝동안 각 관절이 강체를 올바른 위치로 오도록 끌어당기는 특별한 힘을 적용하는 것이다. 이 힘은 오류 감쇠 매개 변수(ERP; error reduction parameter)에 의해 제어된다. ERP의 값은 0과 1 사이이다.

ERP는 다음 시뮬레이션 스텝중에 고칠 관절 오류 비율을 나타낸다. ERP=0는 어떠한 조정력도 적용되지 않고 시뮬레이션을 진행함에따라 강체들은 결국 떨어지게 될 것이다. ERP=1이면, 시뮬레이션은 다음 시간 스텝에서 모든 관절 오류를 고치려고 할 것이다. 그러나 ERP=1로 설정하는 것은 바람직하지 않다. 관절 오류는 다양한 내부적 근사에 의해 완벽하게 고쳐질 수 없기 때문이다. ERP를 0.1에서 0.8사이의 값으로 설정하는 것이 바람직하다 (0.2는 기본값).

시뮬레이션에서 대부분의 관절에 영향을 미치는 전역 ERP 값을 설정할 수 있다. 그러나 어떤 관절들은 다양한 특성을 제어하는 지역 ERP값을 갖기도 한다.

부드러운 구속과 구속 힘 혼합 (CFM; constraint force mixing)

대부분의 구속은 본질적으로 "딱딱하다(hard)". 이것은 구속이 결코 변경될 수 없는 조건을 나타낸다는 의미이다. 예를 들면, 공은 항상 소켓안에 있어야 하고, 경첩의 두 부분은 항상 동일 선상에 정렬되어 있어야 한다. 실제로 구속은 시스템에 의도하지 않은 오류의 발생으로 변경될 수 있다. 그러나 오류 감쇠 매개 변수(error reduction parameter)가 이러한 오류들을 바로잡기 위해 설정될 수 있다.

모든 구속이 딱딱한 것은 아니다. 어떤 "부드러운(soft)" 구속들은 변경 가능하도록 설계되었다. 예를 들면, 오브젝트들의 충돌로 인해 관통되지 않게 하는 접촉 구속은 기본적으로 딱딱하기 때문에 마치 충돌 표면이 강철로 만들어진 것처럼 보인다. 그러나 좀더 부드러운 재질을 시뮬레이션하기 위해 부드러운 구속을 사용하면, 두 강체가 충돌할 때 어느정도 자연스러운 관통이 허용된다.

딱딱하고 부드러운 구속들의 구별을 제어하는 두가지 매개 변수가 있다. 첫번째 매개 변수는 이미 소개된 오류 감쇠 매개 변수(ERP)이다. 두번째 매개 변수는 구속 힘 혼합(CFM; constraint force mixing)이다. CFM은 아래에서 설명한다.

구속 힘 혼합 (CFM)

다음은 CFM에 대한 다소 기술적인 설명이다. CFM이 실제로 어떻게 사용되는 지에만 관심이 있다면 다음 절로 넘어가라. 전통적으로 모든 관절에 대한 구속 방정식은 다음과 같다.


J * v = c

여기서 v는 강체의 속도 벡터이고, J는 관절이 시스템으로부터 제거한 모든 자유도에 대한 1개의 행을 가진 "야코비언(Jacobian)" 행렬이다. c는 우측 벡터이다. 다음 시간 스텝에서, 관절 구속을 유지하기 위해 강체에 적용된 힘은 다음과 같다.


force = JT * lambda

ODE는 여기에 새로운 것을 추가한다. ODE의 구속 방정식은 다음과 같다.

J * v = c + CFM * lambda

여기서 CFM는 대각 행렬(diagonal matrix)이다. CFM는 구속의 결과로 발생하는 구속력을 혼합한다. 0이 아닌 (양의) CFM값은 본래의 구속 방정식을 CFM과 구속에 필요한 복원력(restoring force) lambda의 곱에 비례하는 양 만큼 변경한다.

(J M-1 JT + CFM/h) lambda = c/h

따라서 CFM는 단순히 본래 시스템 행렬의 대각 행렬을 추가한다. 양의 CFM값을 사용하는 것은 시스템의 특이성을 없애는 추가 이점을 가지고 있다. 따라서 인수 분해의 정확도가 개선된다.

ERP와 CFM의 사용법

ERP와 CFM은 많은 관절들에서 독립적으로 설정될 수 있다. 관절(또는 관절 한계)의 스펀지 같은 푹심함과 용수철 같은 탄력성을 조절하기 위해 접촉 관절들, 관절 한계들 그리고 다양한 곳들에서 설정될 수 있다.

CFM이 0으로 설정되면, 구속이 딱딱할 것이다. CFM이 양수로 설정되면, "밀어붙여서" 구속을 변경할 가능성이 있다. 바꿔말하면 구속이 부드러워 질 것이다. CFM이 증가함에따라 부드러움도 증가한다. 여기서 실제로 일어나는 것은 구속이 CFM과 구속에 필요한 복원력의 곱에 비례하는 양 만큼 변경된다는 것이다. CFM을 음수로 설정하는 것은 불안정성과 같은 바람직하지 못한 나쁜 결과를 가질 수 있다. CFM을 음수로 설정하지 말라.

ERP와 CFM의 값을 조정함으로써 다양한 효과를 얻을 수 있다. 예를 들면, 용수철과 같은 구속을 시뮬레이션할 수 있다. 이러한 구속에서는 두 강체가 마치 용수철로 연결된 것처럼 흔들거린다. 아니면 진동없는 스펀지같은 구속을 시뮬레이션할 수 있다. 사실, ERP와 CFM는 용수철이나 완충 구속과 같은 효과를 갖기 위해 선택될 수 있다. 용수철 상수(spring constant) kp와 감쇠 상수(damping constant) kd가 있으면 대응하는 ODE 상수는:

ERP = h kp / (h kp + kd) CFM = 1 / (h kp + kd)

여기서 h는 stepsize이다. 이 값들은 불명확한 1차 적분으로 시뮬레이션되는 용수철-완충 시스템과 같은 효과를 가져다준다.

CFM을 증가하는 것, 특히 전역 CFM을 증가하는 것은 시뮬레이션에서 수치적 오류를 줄여줄 수 있다. 사실, 시스템이 오동작을 하고 있다면, 제일 먼저 해봐야 할 것이 전역 CFM을 증가시키는 것이다.

충돌 처리

[충돌 처리에 대해 써야 할 게 너무 많다.]

강체들 끼리의 충돌과 강체와 정적 환경의 충돌은 다음과 같이 처리된다:

  1. 각 시뮬레이션 스텝 전에, 사용자는 충돌 중인 것들을 알아내기 위해 충돌 검사 함수들을 호출한다. 이 함수들은 접촉점 리스트를 리턴한다. 각 접촉점은 공간에서의 위치, 표면 법선 벡터, 그리고 관통 깊이를 명시한다.
  2. 특별한 접촉 관절이 각 접촉점에 생성된다. 접촉 관절은 접촉에 대해서 추가 정보가 주어진다. 예를 들면, 접촉면이 얼마나 탄력적이거나 부드러운지에 대한 정보와 다양한 다른 속성들에 대한 정보들을 제공한다.
  3. 접촉 관절들은 관절 "그룹"에 넣어진다. 접촉 그룹은 관절들이 매우 빠르게 시스템에서 추가 및 삭제될 수 있게 해준다. 접촉이 늘어날 수록 시뮬레이션 속도가 느려지므로, 접촉점의 수를 제한하는 다양한 전략을 사용한다.
  4. 하나의 시뮬레이션 스텝을 거친다.
  5. 모든 접촉 관절들이 시스템에서 제거된다.

내부 충돌 함수들이 반드시 사용될 필요는 없다 - 올바른 접촉점 정보를 제공하기만 하면 다른 충돌 검사 라이브러리들을 사용할 수도 있다.

전형적인 시뮬레이션 코드

전형적인 시뮬레이션은 이와 같이 진행할 것이다.:

  1. 동역학 월드를 생성한다.
  2. 동역학 월드 안에 강체들을 생성한다.
  3. 모든 강체들의 상태(위치 등)를 설정한다.
  4. 동역학 월드 안에 관절들을 생성한다.
  5. 강체에 관절을 부착한다.
  6. 모든 관절들의 매개 변수들을 설정한다.
  7. 필요하면, 충돌 월드와 충돌 기하 구조 오브젝트들을 생성한다.
  8. 접촉 관절들을 보관할 한 개의 관절 그룹을 생성한다.
  9. 루프:
    1. 필요에 따라 강체들에 힘을 적용한다.
    2. 필요에 따라 관절 매개 변수들을 조정한다.
    3. 충돌 검사를 호출한다.
    4. 모든 충돌점에 대해 접촉 관절을 생성하고, 접촉 관절 그룹에 추가한다.
    5. 하나의 시뮬레이션 스텝을 거친다.
    6. 접촉 관절 그룹 안에 있는 모든 관절들을 제거한다.
  10. 동역학 월드와 충돌 월드를 소멸시킨다.

물리학 모델

ODE에서 사용되는 다양한 방법과 근사들이 이 절에서 논의된다.

마찰 근사

[여기에 그림이 몇 장 더 필요하다.]

쿨롱 마찰 모델은 단순하지만, 접촉점에서 마찰을 모델링하기에 효과적인 방법이다. 이것은 접촉점에 나타는 수직력과 접선력 사이의 단순한 관계이다(이러한 힘들을 설명하는 접촉 관절 절을 보라). 법칙은 이렇다:

| fT | <= mu * | fN |

여기서 fN과 fT는 각각 수직력과 법선력 벡터들을 나타낸다. 그리고 mu는 (보통 약 1.0 정도의) 마찰 계수이다. 이 방정식은 "마찰 원뿔"을 정의한다. - fN를 축으로하고 접촉점을 정점으로 하는 원뿔을 상상하라. 전체 마찰력 벡터가 원뿔내에 있으면, 접촉은 "끈적임 모드(sticking mode)"이고 마찰력은 접촉면들이 움직이는 것을 충분히 방지한다. 힘 벡터가 원뿔의 표면 상에 있으면, 접촉은 "미끄러짐 모드(sliding mode)"이고 마찰력은 접촉면들이 미끄러지는 것을 막지 못한다. 따라서 매개 변수 mu는 수직력에 대한 접선력의 최대 비율을 나타낸다.

ODE의 마찰 모델은 효율성을 이유로한 마찰 원뿔에 대한 근사이다. 현재 선택할 수 있는 2개의 근사법들이 있다:

  1. mu의 의미는 접촉시에 접선의 마찰 방향들 중 한쪽에서 나타날 수 있는 최대한의 마찰 (접선)력을 나타내는 것으로 변경된다. 이것은 수직력과는 관계없기 때문에 비현실적이긴하지만, 유용하게 사용될 수 있고 계산 비용이 가장 싸다. 이 경우에 mu는 시뮬레이션에서 적절히 선택되어야 하는 힘한계이다.
  2. 마찰 원뿔은 첫번째와 두번째 방향에 정렬된 마찰 피라미드에 의해 근사된다. [여기에 그림이 한장 필요하다.] : 우선 ODE는 모든 접촉이 마찰이 없는 것처럼 가정하고 수직력들을 계산한다. 그리고 마찰 (접선)력에 대해 최대 한계 fm를 계산한다.

fm = mu * | fN |

이러한 고정된 한계들을 가진 전체 시스템에 대해 해를 구한다. 이것은 mu가 고정되지 않는 진짜 마찰 피라미드와 다르다. 이 근사법은 mu를 일반적인 쿨롱 마찰 계수와 같은 비율로 사용한다. 따라서 mu는 특정 시뮬레이션에 관계없이 약 1.0의 일정한 값으로 설정될 수 있다.

자료형과 규칙

기본 자료형

ODE 라이브러리는 단정도 또는 배정도 실수를 사용하여 빌드될 수 있다. 단정도는 더 빠르고 메모리도 더 적게 사용하지만, 시뮬레이션이 수치적 오류를 더 많이 갖게 되어 눈에 보일 정도의 문제를 야기할 수 있다. 단정도를 사용하면 정확성과 안정성이 더 떨어질 것이다.

[어떤 요인들이 정확성과 안정성에 영향을 주는 지를 설명해야 한다].

부동 소수점 데이타 타입은 dReal이다. 자주 사용되는 다른 타입들은 dVector3, dVector4, dMatrix3, dMatrix4, dQuaternion 등이 있다.

오브젝트와 ID

많은 종류의 오브젝트를 생성할 수 있다:

  • dWorld - 동역학계
  • dSpace - 충돌 공간
  • dBody - 강체
  • dGeom - 기하 구조 (충돌용)
  • dJoint - 관절
  • dJointGroup - 관절 그룹

오브젝트를 생성하는 함수들은 ID를 리턴한다. 오브젝트 ID 타입으로 dWorldID, dBodyID 등이 있다.

인자 규칙

모든 벡터3에 대한 "set" 함수는 벡터3 (x, y, z) 또는 x, y, z 값을 별개의 인자로 받는다.

모든 벡터3에 대한 "get" 함수는 dReal 배열에 대한 포인터를 인자로 받는다.

더 큰 벡터는 dReal 배열에 대한 포인터로 제공되고, 리턴된다.

모든 좌표값들은 특별히 명시하지 않는 한 전역틀에 속한다.

C 대 C++

ODE 라이브러리는 C++로 작성되었지만, 인터페이스는 클래스가 아닌 단순한 C 함수들로 이루어져있다. 왜 그런가?

  • C 인터페이스만 사용하는 것이 더 간단하다 – C++ 의 특징들은 ODE에 큰 도움이 되지 않는다.
  • C++의 이름 변경과 많은 컴파일러들에서 나타나는 실시간 지원 문제를 예방한다.
  • ODE를 사용하기위해 C++의 별난 문법과 친해질 필요가 없다.

디버깅

ODE 라이브러리는 "디버깅(debugging)" 또는 "릴리즈(release)" 모드로 컴파일될 수 있다. 디버깅 모드는 더 느리지만, 함수 인자를 검사하고 내부 일관성을 확보하기 위해 많은 실시간 검사를 행한다. 릴리즈 모드는 더 빠르지만, 어떤 검사도 하지 않는다.

월드

월드 오브젝트는 강체들과 관절들의 컨테이너이다. 다른 월드에 있는 오브젝트들은 상호작용할 수 없다. 예를 들면, 2개의 다른 월드에 속한 강체들은 충돌 할 수 없다.

월드 안의 모든 오브젝트들은 같은 시간 지점에 존재한다. 따라서 별개의 월드를 사용하는 한가지 방법은 시스템들을 다른 비율로 시뮬레이션하는 것이다.

대부분의 응용 프로그램들은 오직 하나의 월드만을 필요로 할 것이다.

dWorldID dWorldCreate();
새로운 비어있는 월드를 하나 생성하고 ID를 리턴한다.
void dWorldDestroy (dWorldID);
월드를 소멸시키고 그 안의 모든 것을 소멸시킨다. 모든 강체와 관절 그룹의 일부분이 아닌 모든 관절들을 포함한다. 관절 그룹에 속한 관절들은 비활성화될 것이다. 그리고 dJointGroupEmpty를 호출하여 소멸될 수 있다.
void dWorldSetGravity (dWorldID, dReal x, dReal y, dReal z);
void dWorldGetGravity (dWorldID, dVector3 gravity);
월드의 전역 중력 벡터를 설정/리턴한다. 단위는 m/s/s, 따라서 +z를 위로 가정했을 때 지구의 중력 벡터는 (0,0,-9.81)이다. 기본값은 중력이 없는 상태, 즉 (0,0,0)이다.
void dWorldSetERP (dWorldID, dReal erp);
dReal dWorldGetERP (dWorldID);
전역 오류 감쇠 매개 변수(ERP; error reduction parameter) 값을 설정/리턴한다. ERP는 각 시간 스텝에서 오류를 얼마나 많이 정정할 수 있는 지를 제어한다. 값의 범위는 일반적으로 0.1에서 0.8이다. 기본값은 0.2이다.
void dWorldSetCFM (dWorldID, dReal cfm);
dReal dWorldGetCFM (dWorldID);
전역 구속 힘 혼합(CFM; constraint force mixing) 값을 설정/리턴한다. 일반적인 값은 10-9에서1 사이에 있다. 단정도를 사용하면 기본값이 10-5이고, 배정도를 사용하면 10-10이다.
void  dWorldSetAutoDisableFlag (dWorldID, int do_auto_disable);
int   dWorldGetAutoDisableFlag (dWorldID);
void  dWorldSetAutoDisableLinearThreshold (dWorldID, dReal linear_threshold);
dReal dWorldGetAutoDisableLinearThreshold (dWorldID);
void  dWorldSetAutoDisableAngularThreshold (dWorldID, dReal angular_threshold);
dReal dWorldGetAutoDisableAngularThreshold (dWorldID);
void  dWorldSetAutoDisableSteps (dWorldID, int steps);
int   dWorldGetAutoDisableSteps (dWorldID);
void  dWorldSetAutoDisableTime (dWorldID, dReal time);
dReal dWorldGetAutoDisableTime (dWorldID);

새로 생성되는 강체에 대한 기본 자동-비활성화 매개 변수들을 설정/리턴한다. 자동-비활성화 특성에 대해 6.5절을 보라. 기본 매개 변수들은 다음과 같다:

  • AutoDisableFlag = disabled
  • AutoDisableLinearThreshold = 0.01
  • AutoDisableAngularThreshold = 0.01
  • AutoDisableSteps = 10
  • AutoDisableTime = 0
void dWorldImpulseToForce (dWorldID, dReal stepsize,
              dReal ix, dReal iy, dReal iz, dVector3 force);

강체에 선 또는 각 충격량을 적용하고 싶을 때, 힘이나 돌림힘 대신에 원하는 충격량을 힘/돌림힘 벡터로 변환하기 위해 이 함수를 사용할 수 있다.
이 함수는 원하는 충격량을 (ix,iy,iz)로 설정하고, 변환한 힘을 force 벡터에 넣는다. 현재의 알고리즘은 단순히 충격량을 stepsize로 나누기만 한다. 여기서 stepsize는 다음 스텝의 크기이다.
이 함수는 dWorldID를 받는다. 향후에 힘 계산이 월드의 속성으로 설정된 적분기 매개 변수들에 좌우될지도 모르기 때문이다.
void dCloseODE();
ODE에 의해 사용된 메모리를 해제한다. 프로그램 종료 전에 사용하면 된다.

스텝 함수

void dWorldStep (dWorldID, dReal stepsize);
월드를 한 스텝 진행한다. 이것은 m3 차수의 시간을 소모하고 m2 차수의 메모리를 사용하는 "큰 행렬(big matrix)" 법을 사용한다. 여기서 m는 전체 구속 행 수이다.
커다란 시스템에 대해서 이것은 아주 많은 메모리를 사용하고 매우 느릴 것이다. 하지만 현재 가장 정확한 방법이다.


void dWorldQuickStep (dWorldID, dReal stepsize);
월드를 한 스텝 진행한다. m*N 차수의 시간과 m 차수의 메모리를 소모하는 반복적인 방법을 사용한다. 여기서 m는 전체 구속 행 수이고 N는 반복횟수이다.
커다란 시스템에 대해서 이 함수가 dWorldStep보다 훨씬 더 빠르다. 하지만 정확성은 더 떨어진다.
QuickStep는 오브젝트 더미들에 좋은 성능을 발휘한다. 특히 자동-비활성화 특성이 함께 사용되면 더 좋다. 그러나 근-특이 시스템(near-singular systems)들에 대해서는 취약한 정확도를 가진다. 고-마찰 접촉, 모터, 특정 관절구조를 사용할 때 근-특이 시스템이 나타날 수 있다. 예를 들면, 다수의 다리를 가진 로봇이 땅위에 서있을 때 근-특이성을 가진다.
QuickStep의 부정확성 문제를 극복하기 위한 방법들이 있다:
  • CFM을 늘린다.
  • 시스템에서 접촉 수를 줄인다(즉, 로봇이나 생물의 발에 대한 최소한의 접촉 수를 사용한다).
  • 접촉시 과도한 마찰을 사용하지 않는다.
  • 적절한 접촉 미끄러짐을 사용한다.
  • 운동학 루프를 피한다(그러나, 운동학 루프는 다리가 달린 생물체에서는 피할 수가 없다).
  • 과도한 모터 힘을 사용하지 않는다.
  • 속도 기반 모터 대신 힘 기반 모터를 사용한다.
QuickStep의 반복 횟수를 증가하는 것이 조금 도움이 될 것이다. 하지만 시스템이 특이점에 가깝다면 별 도움이 되지 않을 것이다.
void dWorldSetQuickStepNumIterations (dWorldID, int num);
int dWorldGetQuickStepNumIterations (dWorldID);
QuickStep 함수가 스텝 당 실행하는 횟수를 설정/리턴한다. 더 많은 반복은 더 정확한 해를 줄 것이다. 하지만 계산 시간은 더 오래 걸릴 것이다. 기본값은 20이다.

접촉 매개 변수

void dWorldSetContactMaxCorrectingVel (dWorldID, dReal vel);
dReal dWorldGetContactMaxCorrectingVel (dWorldID);
접촉이 생성하는 허락된 최대 정정 속도를 설정/리턴한다. 기본값은 무한대이다. 이 값을 줄이는 것은 깊게 묻힌 오브젝트들이 "튀는(popping)" 것을 방지하는 데 도움을 줄 수 있다.
void dWorldSetContactSurfaceLayer (dWorldID, dReal depth);
dReal dWorldGetContactSurfaceLayer (dWorldID);
모든 기하 구조 오브젝트의 표면 층의 깊이를 설정/리턴한다. 접촉시 정지할 때까지 표면을 주어진 깊이까지 파고 드는 것을 허락한다. 이것을 증가하는 것은 반복적인 접촉에 대해 떨림 문제(jittering problems)를 방지하는 데 도움을 줄 수 있다.

강체 함수

강체의 생성과 소멸

dBodyID dBodyCreate (dWorldID);
주어진 월드의 (0,0,0) 위치에 기본 질량을 가진 강체를 생성하고 ID를 리턴한다.
void dBodyDestroy (dBodyID);
강체를 소멸시킨다. 강체에 부착된 모든 관절들을 림보(limbo)에 넣는다. (즉, 시뮬레이션에 영향을 주지는 않으나 제거또한 되지 않는다.)

위치와 회전

void dBodySetPosition   (dBodyID, dReal x, dReal y, dReal z);
void dBodySetRotation   (dBodyID, const dMatrix3 R);
void dBodySetQuaternion (dBodyID, const dQuaternion q);
void dBodySetLinearVel  (dBodyID, dReal x, dReal y, dReal z);
void dBodySetAngularVel (dBodyID, dReal x, dReal y, dReal z);
const dReal * dBodyGetPosition   (dBodyID);
const dReal * dBodyGetRotation   (dBodyID);
const dReal * dBodyGetQuaternion (dBodyID);
const dReal * dBodyGetLinearVel  (dBodyID);
const dReal * dBodyGetAngularVel (dBodyID);
이 함수들은 강체의 위치, 회전, 선속도와 각속도를 설정하거나 얻는다. 여러 강체들을 설정한 후, 새로운 구성이 현재의 관절/구속과 어울리지 않을 경우 시뮬레이션의 결과는 알 수 없다. dBodyGet-함수를 사용할 때, 리턴 값은 내부 자료 구조에 대한 포인터이다. 그러므로 변경된 값들이 모두 강체 시뮬레이션 구조체에 반영되기까지는 유효하다.
흠. dBodyGetRotation는 4x3 회전 행렬을 리턴한다.

질량과 힘

void dBodySetMass (dBodyID, const dMass *mass);
void dBodyGetMass (dBodyID, dMass *mass);

강체의 질량을 설정/리턴한다(질량 함수들을 보라).

void dBodyAddForce            (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddTorque           (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddRelForce         (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddRelTorque        (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddForceAtPos       (dBodyID, dReal fx, dReal fy, dReal fz,
                                       dReal px, dReal py, dReal pz);
void dBodyAddForceAtRelPos    (dBodyID, dReal fx, dReal fy, dReal fz,
                                       dReal px, dReal py, dReal pz);
void dBodyAddRelForceAtPos    (dBodyID, dReal fx, dReal fy, dReal fz,
                                       dReal px, dReal py, dReal pz);
void dBodyAddRelForceAtRelPos (dBodyID, dReal fx, dReal fy, dReal fz,
                                       dReal px, dReal py, dReal pz);
절대적이거나 상대적인 좌표값들로 강체에 힘을 추가한다. 힘은 각 강체에 누적되고, 매 시간 스텝 후에는 누적치를 0으로 한다.
...RelForce 와 ...RelTorque 함수들은 강체의 기준틀에 관련된 힘 벡터들을 받아들인다.
...ForceAtPos 와 ...ForceAtRelPos 함수들은 각각 전역또는 강체 좌표계에 대해서 추가 위치 벡터를 받아들인다. 위치 벡터는 힘이 적용되는 지점을 명시한다. 다른 모든 함수들은 질량의 중심에 힘을 적용한다.
const dReal * dBodyGetForce  (dBodyID);
const dReal * dBodyGetTorque (dBodyID);
현재 누적된 힘과 돌림힘 벡터를 리턴한다. 3개 dReal로 된 배열에 대한 포인터를 리턴한다. 리턴값은 내부 데이타 구조체에 대한 포인터이다. 벡터값은 강체 시스템에 대한 변화가 반영될 때까지 유효하다.
void dBodySetForce  (dBodyID b, dReal x, dReal y, dReal z);
void dBodySetTorque (dBodyID b, dReal x, dReal y, dReal z);
강체 힘과 돌림힘 누적 벡터를 설정한다. 강체를 비활성화하기 위해 힘과 돌림힘을 0으로 만들 때 주로 유용하다.

유용한 함수

void dBodyGetRelPointPos (dBodyID, dReal px, dReal py, dReal pz,
                         dVector3 result);
void dBodyGetRelPointVel (dBodyID, dReal px, dReal py, dReal pz,
                         dVector3 result);
void dBodyGetPointVel    (dBodyID, dReal px, dReal py, dReal pz,
                         dVector3 result);


강체의 한 점(px,py,pz)를 받아서 그 점의 위치와 속도를 전역 좌표로 result에 리턴한다. dBodyGetRelPointXXX 함수들은 강체 좌표계의 점을 인자로 받는다. dBodyGetPointVel 함수들은 전역 좌표계에서 점을 인자로 받는다.
void dBodyGetPosRelPoint (dBodyID, dReal px, dReal py, dReal pz,
                           dVector3 result);
dBodyGetRelPointPos의 반대이다. 전역 좌표의 (x,y,z)를 받아서 강체 좌표에서의 점의 위치를 result에 리턴한다.
void dBodyVectorToWorld   (dBodyID, dReal px, dReal py, dReal pz,
                          dVector3 result);
void dBodyVectorFromWorld (dBodyID, dReal px, dReal py, dReal pz,
                          dVector3 result);
강체(월드) 좌표계에서 표현된 벡터 (x,y,z)를 받아서, 월드(강체) 좌표로 변환하여 그 결과를 result에 리턴한다.

자동 활성화와 비활성화

모든 강체는 활성화되거나 비활성화될 수 있다. 활성화된 강체들은 시뮬레이션에 참가하고, 반면 비활성화된 강체들은 꺼진 상태라서 시뮬레이션 스텝 동안 갱신되지 않는다. 새로운 강체들은 항상 활성화된 상태로 생성된다. 관절을 통해 활성화된 강체에 연결된 비활성화된 강체는 다음 시뮬레이션 스텝에서 자동적으로 재활성화된다.

비활성화된 강체는 CPU 시간을 소모하지 않는다. 따라서 시뮬레이션 속도를 높이기 위해 강체가 정지된 상태에 이르면 비활성화돼야 한다. 이것이 자동-비활성화 특성으로 자동으로 행해진다.

어떤 강체가 자동-비활성화 플랙을 켜놓았다면, 다음 경우에 자동으로 비활성화될 것이다.

  1. 주어진 시뮬레이션 스텝 수 동안 유휴상태인 경우.
  2. 주어진 시뮬레이션 시간 동안 유휴상태인 경우.

강체의 선속도와 각속도의 양이 모두 주어진 경계 이하에 있을 때 유휴 상태에 있다고 생각할 수 있다.

따라서, 모든 강체는 5개의 자동-비활성화 매개 변수들을 가진다: 활성화된 플랙, 유휴 스텝 카운트, 유휴 시간, 그리고 선/각 속도 경계. 새로 생성된 강체들은 월드로부터 이러한 매개 변수들을 가진다.

다음 함수들은 강체의 활성화/비활성화 매개 변수를 설정/리턴한다.

void dBodyEnable (dBodyID);
void dBodyDisable (dBodyID);
강체를 수동으로 활성화/비활성화 시킨다. 관절을 통해 활성화된 강체에 연결된 비활성화된 강체는 다음 시뮬레이션 스텝에서 자동으로 재활성화될 것이다.
int dBodyIsEnabled (dBodyID);
강체가 현재 활성화돼 있으면 1를 리턴하고, 그렇지 않으면 0을 리턴한다.
void  dBodySetAutoDisableFlag (dBodyID, int do_auto_disable);
int   dBodyGetAutoDisableFlag (dBodyID);
강체의 자동-비활성화 플랙을 설정/리턴한다. do_auto_disable이 0이면, 강체가 오랫동안 유휴상태로 있을 때 자동으로 비활성화된다.
void  dBodySetAutoDisableLinearThreshold (dBodyID, dReal linear_threshold);
dReal dBodyGetAutoDisableLinearThreshold (dBodyID);
자동 비활성화를 위한 강체의 선속도 경계치를 설정/리턴한다. 강체의 선속도 크기가 경계치보다 작아야 유휴 상태에 있다고 생각할 수 있다. 경계치를 dInfinity로 설정하면 선속도를 고려 대상에서 제외한다.
void  dBodySetAutoDisableAngularThreshold (dBodyID, dReal angular_threshold);
dReal dBodyGetAutoDisableAngularThreshold (dBodyID);
자동 비활성화를 위한 강체의 각속도 경계를 설정/리턴한다. 강체의 각속도 크기가 경계치보다 작아야 유휴 상태에 있다고 생각할 수 있다. 경계치를 dInfinity로 설정하면 각속도를 고려 대상에서 제외한다.
void  dBodySetAutoDisableSteps (dBodyID, int steps);
int   dBodyGetAutoDisableSteps (dBodyID);
강체가 유휴 상태에 있어야 할 시뮬레이션 스텝 수를 설정/리턴한다. 이 값을 0으로 설정하면 스텝 수를 고려 대상에서 제외한다.
void  dBodySetAutoDisableTime (dBodyID, dReal time);
dReal dBodyGetAutoDisableTime (dBodyID);
강체가 유휴 상태에 있어야 할 시뮬레이션 시간을 설정/리턴한다. 이 값을 0으로 설정하면 시뮬레이션 시간을 고려 대상에서 제외한다.
void  dBodySetAutoDisableDefaults (dBodyID);
강체의 자동-비활성화 매개 변수들을 기본값으로 설정한다.

기타 강체 함수들

void  dBodySetData (dBodyID, void *data);
void *dBodyGetData (dBodyID);
강체의 사용자 데이타 포인터를 설정/리턴한다.
void dBodySetFiniteRotationMode (dBodyID, int mode);
이 함수는 강체의 회전이 매 시간 스텝마다 갱신되는 방식을 제어한다. mode 인자는 다음과 같다:
  • 0: "무한소(無限小; infinitesimal)"의 회전 갱신을 사용한다. 계산이 빠르지만, 빠른 속도로 회전하는 강체에 대해 가끔 부정확한 결과를 유발할 수 있다. 특히 다른 강체에 연결된 강체의 경우 두드러진다. 새로 생성된 강체에 대해 기본값으로 사용된다.
  • 1: "유한(有限; finite)"한 회전 갱신을 사용한다. 계산하는 데 더 많은 비용이 들지만, 빠른 속도의 회전에 대해 더 정확하다. 그렇지만 빠른 속도의 회전은 시뮬레이션에서 많은 오류를 야기할 수 있다. 그리고 이 모드는 여러 오류들 중 한가지만 고칠 수 있을 것이다.
int dBodyGetFiniteRotationMode (dBodyID);
강체의 현재 유한 회전 모드를 리턴한다. (0 또는 1).
void dBodySetFiniteRotationAxis (dBodyID, dReal x, dReal y, dReal z);
강체의 유한 회전 축을 설정한다. 유한 회전 모드가 설정되었을 때만 의미가 있다 (dBodySetFiniteRotationMode 참고).
축이 (0,0,0)이면, 강체에 완전한 유한 회전을 수행한다.
축이 영이 아니면, 강체는 축방향을 따라 부분적인 유한 회전을 하고나서, 직각 방향을 따라 무한소의 회전을 한다.
이는 빠르게 회전하는 강체에 발생하는 오류들을 완화시키는 데 유용하다. 예를 들어, 빠른 속도로 회전하는 차바퀴가 있다면, 바퀴의 움직임을 개선하기위한 인자로서 바퀴의 경첩 관절축에 이 함수를 호출할 수 있다.
void dBodyGetFiniteRotationAxis (dBodyID, dVector3 result);
강체의 현재 유한 회전축을 리턴한다.
int dBodyGetNumJoints (dBodyID b);
강체에 부착된 관절의 수를 리턴한다.
dJointID dBodyGetJoint (dBodyID, int index);
강체에 부착된 index번째 관절을 리턴한다. 유효한 인덱스는 0에서 n-1사이이다. 여기서 n은 dBodyGetNumJoints에 의해 리턴된 값이다.
void dBodySetGravityMode (dBodyID b, int mode);
int dBodyGetGravityMode (dBodyID b);
강체가 월드의 중력에 의해 영향을 받는지 여부를 설정/리턴한다. mode가 0이 아니면 중력의 영향을 받고, 0이면 영향을 받지 않는다. 새로 생성된 강체는 항상 중력의 영향을 받는다.

관절 타입과 함수

관절의 생성과 소멸

dJointID dJointCreateBall (dWorldID, dJointGroupID);
dJointID dJointCreateHinge (dWorldID, dJointGroupID);
dJointID dJointCreateSlider (dWorldID, dJointGroupID);
dJointID dJointCreateContact (dWorldID, dJointGroupID,
                             const dContact *);
dJointID dJointCreateUniversal (dWorldID, dJointGroupID);
dJointID dJointCreateHinge2 (dWorldID, dJointGroupID);
dJointID dJointCreateFixed (dWorldID, dJointGroupID);
dJointID dJointCreateAMotor (dWorldID, dJointGroupID);
주어진 타입의 새로운 관절을 생성한다. 이 관절은 처음에 강체에 연결된 상태가 아니기 때문에 "림보(limbo)"(시뮬레이션에 아무런 영향을 주지 않는)에 존재한다. 일반적으로 관절 그룹 ID는 0이다. 0이 아니면 특정 관절 그룹에 할당된다. 접촉 관절은 주어진 dContact 구조체를 가지고 초기화된다.

void dJointDestroy (dJointID);

관절을 소멸시키고 강체와 연결을 끊고 월드로부터 제거한다. 그러나, 관절이 그룹의 일원이라면, 이 함수는 아무 효력이 없다. - 그런 관절을 소멸시키려면 그룹을 비우거나 소멸시켜야 한다.

dJointGroupID dJointGroupCreate (int max_size);

관절 그룹을 생성한다. max_size 인자는 현재 사용되지 않으며 0으로 설정해야 한다. 이는 이전 버전과의 호환성을 위해 남겨진 것이다.

void dJointGroupDestroy (dJointGroupID);

관절 그룹을 소멸시킨다. 그룹 안의 모든 관절들이 소멸할 것이다.

void dJointGroupEmpty (dJointGroupID);

관절 그룹을 비운다. 그룹 안의 모든 관절들이 소멸하지만, 그룹 자체는 소멸하지 않을 것이다.

기타 관절 함수

void dJointAttach (dJointID, dBodyID body1, dBodyID body2);

새 강체에 관절을 부착한다. 관절이 이미 부착되어있으면, 예전의 강체로부터 떼어낸다. 관절을 하나의 강체에만 부착하려면, body1 또는 body2를 0으로 설정하면된다. - 0인 강체는 정적 환경을 말한다. 두 강체를 모두 0으로 설정하면 관절이 "림보"에 들어간다. 즉, 시뮬레이션에 영향을 주지 않는다.
경첩 관절-2 같은 몇몇 관절들은 제대로 작동하려면 두 개의 강체에 부착되어야 한다.

void dJointSetData (dJointID, void *data); void *dJointGetData (dJointID);

관절의 사용자 데이타 포인터를 설정/리턴한다.

int dJointGetType (dJointID);

관절의 타입을 얻는다. 다음 상수들 중 하나가 리턴된다:
dJointTypeBall 구상 관절(ball-and-socket joint)
dJointTypeHinge 경첩 관절(hinge joint)
dJointTypeSlider 미닫이 관절(slider joint)
dJointTypeContact 접촉 관절(contact joint)
dJointTypeUniversal 십자축 관절(universal joint)
dJointTypeHinge2 경첩-2 관절(hinge-2 joint)
dJointTypeFixed 고정 관절(fixed joint)
dJointTypeAMotor 각모터 관절(angular motor joint)

dBodyID dJointGetBody (dJointID, int index);

관절이 연결한 강체들을 리턴한다. index가 0이면 "첫번째" 강체를 리턴한다. 이는 dJointAttach의 body1 인자와 일치한다. index가 1이면 "두번째" 강체를 리턴한다. 이는 dJointAttach의 body2 인자와 일치한다.
리턴된 강체 ID 중 하나가 0이면, 관절이 강체를 정적 환경과 연결한 것이다. 두 강체 ID 모두 0이면, 관절은 "림보"에 있는 것이고, 시뮬레이션에 영향을 주지 않는다.

void dJointSetFeedback (dJointID, dJointFeedback *); dJointFeedback *dJointGetFeedback (dJointID);

월드의 시간 스텝 동안, 각각의 관절에 의해 적용되는 힘들이 계산된다. 이 힘들은 연결된 강체에 직접 더해지고, 사용자는 보통 관절이 얼마나 많은 힘을 부여하는 지 알 길이 없다.
이러한 정보가 필요하다면, 사용자는 dJointFeedback 구조체를 할당해서 그 포인터를 dJointSetFeedback() 함수에 넘길 수 있다. 피드백 정보 구조체는 다음과 같이 정의된다:
typedef struct dJointFeedback {
 dVector3 f1;       // force that joint applies to body 1
 dVector3 t1;       // torque that joint applies to body 1
 dVector3 f2;       // force that joint applies to body 2
 dVector3 t2;       // torque that joint applies to body 2
} dJointFeedback;
시간 스텝 중에 관절에 부착된 피드백 구조체는 관절의 힘과 돌림힘 정보로 채워질 것이다. dJointGetFeedback() 함수는 현재 피드백 구조체 포인터를 리턴하거나 아무것도 사용되지 않으면(이게 기본값), 0을 리턴한다. 관절에 대한 피드백을 비활성화시키기위해 dJointSetFeedback()에 0을 인자로 넘길 수 있다.
API 디자인에 잠깐 주목해보자. 사용자가 이러한 구조체들에 할당을 하도록 하는 것이 이상해 보일 수도 있다. 그냥 각 관절에 정적으로 데이타를 저장하면 안되나? 그렇게 하지 않는 이유는 모든 사용자가 피드백 정보를 사용하지 않을 것이고, 심지어 모든 사용자가 사용하더라도 모든 관절이 필요로 하지는 않을 것이기 때문이다. 정적으로 정보를 저장하는 것은 메모리를 낭비하게된다. 특히 앞으로 이 구조체가 추가 정보를 저장하기 위해 크기가 커질 수도 있다.
그럼 사용자가 요청할 때 ODE가 직접 구조체를 할당하면 되지 않나? 그렇게 하지 않는 이유는 (시뮬레이션 단계마다 생성되고 소멸하는) 접촉 관절은 피드백을 요청할 경우 메모리 할당에 많은 시간이 걸릴 것이기 때문이다. 사용자로 하여금 할당하도록 하는 것이 더 나은 정책이다. 즉, 사용자는 단순히 고정 배열로부터 구조체를 할당하면 된다.
이러한 API에 대한 대안은 관절-힘 콜백 함수를 갖는 것이다. 물론 잘 동작하지만 약간의 문제를 가지고 있다. 첫째, 콜백 함수는 API를 더럽히고 때때로 사용자가 데이타를 얻기위해 부자연스러운 왜곡을 거쳐야한다. 둘째, ODE가 시뮬레이션 과정 중에 변경되면 안좋은 결과를 갖게 될 수 있다. 이러한 상황에 대비책을 마련하거나 디버깅 검사가 필요하다 - 이것은 복잡한 문제다.

int dAreConnected (dBodyID, dBodyID);

유용한 함수: 두 강체가 관절에 의해 서로 연결되어 있다면 1을 리턴하고, 그렇지 않으면 0을 리턴한다.

int dAreConnectedExcluding (dBodyID, dBodyID, int joint_type);

유용한 함수: 두 강체가 joint_type이 아닌 관절에 의해 연결되어 있다면 1을 리턴하고, 그렇지 않으면 0을 리턴한다. joint_type은 dJointTypeXXX 상수이다. 이 함수는 두 강체 사이에 접촉 관절을 추가할 지를 결정할 때 유용하다: 만약 두 강체가 이미 접촉 관절이 아닌 다른 관절로 연결되어있다면, 접촉 관절을 추가하는 것은 적절하지 않을 것이다. 그러나 접촉 관절을 가지고 있는 강체 사이에 접촉 관절을 더 추가하는 것은 괜찮다.

관절 매개 변수 설정 함수

구상 관절

그림 4에 구상 관절(ball and socket joint)이 나와 있다.

ball-and-socket.jpg

그림 4: 구상 관절

void dJointSetBallAnchor (dJointID, dReal x, dReal y, dReal z);

관절 고정점을 설정한다. 관절은 두 강체를 연결하기 위해 고정점을 유지한다. 입력값은 월드 좌표계의 좌표이다.

void dJointGetBallAnchor (dJointID, dVector3 result);

월드 좌표계에서 body1의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, body2의 점과 똑같을 것이다.

void dJointGetBallAnchor2 (dJointID, dVector3 result);

월드 좌표계에서 body2의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면 dJointGetBallAnchor dJointGetBallAnchor2의 결과값이 반올림 오차내에서 같다고 볼 수 있다.

경첩 관절

그림 5에 경첩 관절(hinge joint)이 나와 있다.

hinge.jpg

그림 5: 경첩 관절

void dJointSetHingeAnchor (dJointID, dReal x, dReal y, dReal z); void dJointSetHingeAxis (dJointID, dReal x, dReal y, dReal z);

경첩 관절 고정점과 고정축 매개 변수를 설정한다.

void dJointGetHingeAnchor (dJointID, dVector3 result);

월드 좌표계에서 body1의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, body2의 점과 똑같을 것이다.

void dJointGetHingeAnchor2 (dJointID, dVector3 result);

월드 좌표계에서 body2의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, dJointGetHingeAnchor의 리턴값과 똑같을 것이다. 그렇지 않으면, 리턴값이 약간 다를 것이다. 이것은 관절이 얼마나 떨어져있는지를 알아내는 데 사용할 수 있다.

void dJointGetHingeAxis (dJointID, dVector3 result);

경첩 관절축 매개 변수를 얻는다.

dReal dJointGetHingeAngle (dJointID); dReal dJointGetHingeAngleRate (dJointID);

경첩 관절의 각과 시간 변화율을 얻는다. 각도는 두 강체 또는 강체와 정적 환경의 사잇각으로 -pi와 pi 사이의 값이다.
경첩 관절 고정점 또는 축이 설정되었을 때, 부착된 강체의 현재 위치가 검사되고 그 위치는 0도가 될 것이다.

미닫이 관절

그림 6에 미닫이 관절이 나와 있다.

slider.jpg

그림 6: 미닫이 관절

void dJointSetSliderAxis (dJointID, dReal x, dReal y, dReal z);

미닫이 관절 축 매개 변수를 설정한다.

void dJointGetSliderAxis (dJointID, dVector3 result);

미닫이 관절 축 매개 변수를 얻는다.

dReal dJointGetSliderPosition (dJointID); dReal dJointGetSliderPositionRate (dJointID);

미닫이 관절의 선형 위치와 시간 변화율을 얻는다.
축이 설정되면, 부착된 강체의 현재 위치가 검사되고 그 위치는 0이 될것이다.

십자축 관절

그림 7에 십자축 관절이 나와 있다.

universal.jpg

그림 7: 십자축 관절

십자축 관절은 추가적인 회전 자유도를 억압한 볼-소켓 관절과 비슷하다. body1에 axis1을 설정하고, body2에 axis1에 수직인 axis2를 설정하고 그 상태를 유지한다. 바꿔 말하면, 두 축에 수직인 방향에 대한 두 강체의 회전이 같다.

그림에서, 두 강체는 십자가로 연결되어있다. axis1은 body1에 부착되고, axis2는 body2에 부착된다. 십자가는 두 축을 직각으로 유지한다. 그러므로 body1을 잡고 비틀면, body2 또한 비틀어진다.

십자축 관절 축이 서로 수직이고 완충 지점에서 견고하게 연결된 경첩-2 관절과 동일하다.

십자축 관절은 자동차에서 볼 수 있다. 엔진은 자신의 축을 따라 회전하는 굴대와 손잡이에 영향을 준다. 굴대의 방향을 바꾸고 싶을 때가 있을 것이다. 그런데 문제는 굴대를 구부리면, 굽은 곳 이후의 부분은 자신의 축을 따라 회전하지 않을 것이다. 그래서 구부러진 위치를 잘라버리고 그 부분에 십자축 관절을 넣으면, 첫번째 굴대의 각도만큼 두번째 굴대를 회전시키는 구속력을 사용할 수 있게 된다.

팔을 쫙 뻗고 있는 사람을 상상해보라. 팔을 위 아래와 앞 뒤로 움직일 수 있지만 팔의 축에 대해 회전할 수는 없다.

다음은 십자축 관절 함수들이다:

void dJointSetUniversalAnchor (dJointID, dReal x, dReal y, dReal z); void dJointSetUniversalAxis1 (dJointID, dReal x, dReal y, dReal z); void dJointSetUniversalAxis2 (dJointID, dReal x, dReal y, dReal z);

십자축 관절 고정점과 고정축 매개 변수를 설정한다. axis1과 axis2는 서로 수직이어야 한다.

void dJointGetUniversalAnchor (dJointID, dVector3 result);

월드 좌표계에서 body1의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, body2의 고정점과 똑같을 것이다.

void dJointGetUniversalAnchor2 (dJointID, dVector3 result);

월드 좌표계에서 body2의 관절 고정점을 얻는다. 관절이 완벽하게 만족된다면, dJointGetUniversalAnchor의 리턴값이 반올림 오차범위내에서 같다고 볼 수 있다. dJointGetUniversalAnchor2는 관절이 얼마나 떨어져 있는지를 알아보는 데 사용할 수 있다.

void dJointGetUniversalAxis1 (dJointID, dVector3 result); void dJointGetUniversalAxis2 (dJointID, dVector3 result);

십자축 관절 축 매개 변수를 얻는다.

경첩-2

그림 8에 경첩-2 관절이 나와 있다.

hinge2.jpg

그림 8: 경첩-2 관절

경첩-2 관절은 각자 다른 축을 가진 두개의 경첩 관절이 연속으로 연결되어있는 것과 같다. 예를 들면, 위 그림을 자동차 핸들이라고 보면, 하나의 축이 바퀴의 방향을 틀고, 다른 축은 바퀴가 회전하도록 한다.

경첩-2 관절은 한개의 고정점과 두개의 경첩 관절 축을 가진다. axis1은 body1에 상대적으로 특화된 것이고(body1이 차라면 axis1은 핸들이다), axis2는 body2에 상대적으로 특화된 것이다(body2가 바퀴라면 axis2는 바퀴 축이다).

axis1은 관절 한계들과 한 개의 모터를 가질 수 있고, axis2는 한 개의 모터만을 가질 수 있다.

axis1은 완충 장치 축 기능을 할 수 있다. 즉, 구속이 그 축을 따라 압력을 가할 수 있다.

axis1이 axis2에 수직인 경첩-2 관절은 완충 장치가 추가된 십자축 관절과 동일하다.

void dJointSetHinge2Anchor (dJointID, dReal x, dReal y, dReal z); void dJointSetHinge2Axis1 (dJointID, dReal x, dReal y, dReal z); void dJointSetHinge2Axis2 (dJointID, dReal x, dReal y, dReal z);

경첩-2 고정점과 고정축 매개 변수들을 설정한다. axis1과 axis2가 절대로 같은 선상에 있어선 안된다.

void dJointGetHinge2Anchor (dJointID, dVector3 result);

월드 좌표계에서 body1의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, body2의 고정점과 똑같을 것이다.

void dJointGetHinge2Anchor2 (dJointID, dVector3 result);

월드 좌표계에서 body2의 관절 고정점을 얻는다. 관절이 완벽하게 만족되면, dJointGetHinge2Anchor의 리턴값과 똑같을 것이다. 조건을 완벽하게 만족하지 않으면, 값이 약간 다를 것이다. 이것은 관절이 얼마나 떨어져 있는지를 알아내는 데 사용될 수 있다.

void dJointGetHinge2Axis1 (dJointID, dVector3 result); void dJointGetHinge2Axis2 (dJointID, dVector3 result);

경첩-2 축 매개 변수를 얻는다.

dReal dJointGetHinge2Angle1 (dJointID); dReal dJointGetHinge2Angle1Rate (dJointID); dReal dJointGetHinge2Angle2Rate (dJointID);

경첩-2 axis1과 axis2에 대한 각도와 시간 변화율을 얻는다.
고정점과 축이 설정될 때, 부착된 강체의 현재 위치가 검사되고, 그 위치는 0이 될 것이다.

고정 관절

고정 관절은 두 강체 또는 강체와 정적 환경 사이에 고정된 상대적인 위치와 회전을 유지한다. 이 관절을 사용하는 것은 디버깅을 제외하고 실제로 결코 좋은 생각이 아니다. 두 강체를 서로 달라붙게 할 바에는 하나의 강체로 표현하는 것이 더 낫다.

void dJointSetFixed (dJointID);

강체 사이의 현재 상대적인 위치와 회전을 상기하기 위해 이미 부착된 고정 관절에 이 함수를 호출한다.

접촉 관절

그림 9에 접촉 관절이 나와 있다.

contact.jpg

그림 9: 접촉 관절

접촉 관절은 body1과 body2가 접촉점에서 서로 파고드는 것을 방지한다. 이것은 강체가 접촉면의 법선 방향으로 "나가는" 속도만를 가지도록 허용함으로써 가능하다. 접촉 관절은 일반적으로 한 시간 스텝의 수명을 갖고, 충돌 감지에 응답하여 생성되고 소멸된다.

접촉 관절은 법선에 수직인 두 마찰 방향에 특별한 힘을 적용함으로써 접촉면에 마찰을 시뮬레이션할 수 있다.

접촉 관절을 생성할 때, dContact 구조체를 제공해야 한다. 이것의 정의는 다음과 같다:

struct dContact {
 dSurfaceParameters surface;
 dContactGeom geom;
 dVector3 fdir1;
};

geom은 충돌 함수에 의해 설정된다. 충돌 절에서 설명된다.

fdir1는 마찰력이 적용되는 방향을 나타내는 "첫번째 마찰 방향" 벡터이다. 이것은 단위 벡터여야 하며 접촉 법선에 수직이여야 한다(일반적으로 이것은 접촉면의 접선이다). surface.mode에 dContactFDir1플랙이 설정되었을 때 정의되어야 한다. "두번째 마찰 방향"은 접촉 법선과 fdir1에 모두 수직인 벡터이다.

surface는 사용자에 의해 설정되는 구조체이다. 충돌 표면의 속성들을 정의한다. 다음과 같은 멤버를 가진다:

  • int mode - 접촉 플랙. 항상 설정돼 있어야 한다. 다음 플랙들의 하나 이상의 조합으로 설정된다:
dContactMu2 설정되지 않으면, 두 마찰 방향에 대해 mu를 사용한다. 설정되면, 마찰 방향1에 대해 mu를 사용하고, 마찰 방향2에 대해 mu2를 사용한다.
dContactFDir1
설정되면, fdir1를 마찰 방향1로 하고, 그렇지 않으면 자동으로 접촉 법선에 수직인 마찰 방향1를 계산한다 (이 경우 도출된 방향은 예측할 수 없다).
dContactBounce 설정되면, 접촉면은 탄력을 가지게 되어 강체가 서로 되튄다. 정확한 탄력도는 bounce 매개 변수에 의해 제어된다.
dContactSoftERP 설정되면, 접촉 법선의 오류 감쇠 매개 변수는 soft_erp 매개 변수로 설정된다. 이는 표면을 부드럽게 만든다.
dContactSoftCFM 설정되면, 접촉 법선의 구속력 혼합 매개 변수는 soft_cfm 매개 변수로 설정된다. 이는 표면을 부드럽게 만든다.
dContactMotion1 설정되면, 접촉면은 강체들의 움직임과는 독립적으로 움직이는 것처럼 가정한다. 이것은 표면위를 움직이는 일종의 컨베이어 벨트와 같다. motion1은 마찰 방향1에서 표면 속도를 정의한다.
dContactMotion2 마찰 방향2에 대한 것을 빼고, 위와 동일.
dContactSlip1 마찰 방향1에서의 힘-종속-미끄럼(FDS; force-dependent-slip).
dContactSlip2 마찰 방향2에서의 힘-종속-미끄럼(FDS; force-dependent-slip).
dContactApprox1_1 마찰 방향1에 대한 마찰 피라미드 근사(friction pyramid approximation)를 사용한다. 설정되지 않으면 상수-힘-한계 근사(constant-force-limit approximation)가 사용된다 (mu가 힘 한계이다).
dContactApprox1_2 마찰 방향2에 대한 마찰 피라미드 근사(friction pyramid approximation)를 사용한다. 설정되지 않으면 상수-힘-한계 근사(constant-force-limit approximation)가 사용된다 (mu가 힘 한계이다).
dContactApprox1 dContactApprox1_1, dContactApprox1_2와 동일하다.
  • dReal mu : 쿨롱(Coulomb) 마찰 계수. 0에서 dInfinity까지의 범위안에 있어야 한다. 0은 마찰이 없는 접촉을, dInfinity는 절대로 미끄러지지 않는 접촉을 나타낸다. 마찰이 없는 접촉은 마찰이 있는 접촉보다 계산 시간이 덜 걸리고, 무한 마찰 접촉은 유한 마찰 접촉보다 계산 비용이 더 싸다. 값이 항상 설정되어 있어야 한다.
  • dReal mu2 : 마찰 방향2에 대한 선택적인 쿨롱 마찰 계수 (0..dInfinity). mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal bounce : 복원 매개 변수 (0..1). 0은 표면이 전혀 탄력적이지 않다는 것을 의미하고, 1은 최대 탄력도이다. mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal bounce_vel : 되튐에 필요한 최소한의 접촉 속도 (단위 m/s). 접촉 속도가 이보다 이하이면 사실상 되튐 매개 변수가 0이된다. mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal soft_erp : 접촉 법선의 "부드러움(softness)" 매개 변수. mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal soft_cfm : 접촉 법선의 "부드러움(softness)" 매개 변수. mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal motion1,motion2 : 마찰 방향1, 2에서 표면 속도 (단위 m/s). mode에 대응하는 플랙이 설정되어 있을때만 유효하다.
  • dReal slip1,slip2 : 마찰 방향1, 2에 대한 힘-종속-미끄럼(FDS; force-dependent-slip) 계수. mode에 대응하는 플랙이 설정되어 있을때만 유효하다.

FDS는 접촉면들이 표면의 접선 방향으로 적용되는 힘에 비례하는 속도로 서로를 지나가도록 하는 효과이다.

마찰 계수 mu 가 무한대일 때 접촉점을 고려해보자. 통상적으로, 서로를 미끄러져 지나가도록 하기 위해, 힘 f가 두 접촉면들에 적용되면, 접촉면들이 움직이지 않을 것이다. 그러나, FDS 계수가 양수 k값으로 설정되면, k*f 의 등속도에 도달하여 접촉면들이 서로를 지나갈 것이다.

이는 보통의 마찰 효과와는 사뭇 다르다는 점을 주목하라: 힘은 접촉면 서로에 관하여 등가속(constant acceleration)을 일으키지 않는다 - 등속도에 도달하기위해 짧은 가속을 일으킨다.

이것은 몇가지 상황을 모델링하는 데 유용하다, 특히 타이어가 그렇다. 예를 들어 길에 정차된 차를 보자. 차를 앞으로 밀면 움직이기 시작한다 (즉, 타이어가 구르기 시작한다). 수직 방향으로 차를 밀면 꿈쩍도 하지 않는다. 타이어가 그 방향으로는 구르지 않기 때문이다. 그러나 - 만약 차가 v속도로 이동중에, 수직 방향으로 힘 f를 적용하면 타이어가 f*v에 비례하는 속도로 길위를 미끄러진다(그렇다, 실제로 일어난다).

ODE에서 이것을 모델링하기 위해서는 타이어-길 접촉 매개 변수들을 다음과 같이 설정한다: 타이어가 구르는 방향에서 마찰 방향1을 설정하고, 마찰 방향2에서 FDS 미끄럼 계수를 k*v로 설정한다. v는 타이어가 구르는 속도이고, k는 실험에서 얻을 수 있는 타이어 매개 변수이다.

FDS는 쿨롱 마찰의 끈적임/미끄럼 효과와는 완전히 별개다 - 두 모드가 하나의 접촉점에 같이 사용될 수 있다.

각모터 관절

각모터 관절(angular motor)은 제어되는 두 강체의 상대적인 각속도를 허용한다. 각속도는 세개의 축으로 제어될 수 있다, 돌림힘 모터와 멈춤(torque motors and stops)이 축들에 대한 회전을 위해 설정된다 (아래의 "멈춤과 모터 매개 변수들(stops and motor parameters)"항을 보라). 이것은 주로 볼 관절과 결합할 때 유용하다 (볼 관절은 각자유도들을 전혀 구속하지 않는다), 하지만 각제어(angular control)가 필요한 어느 상황에서든 사용될 수 있다. 볼 관절로 각모터를 사용하기 위해서, 볼 관절이 부착된 두 강체에 각모터를 간단히 부착하기만 하면 된다.

각모터는 다른 모드들에서 사용될 수 있다. dAMotorUser 모드에서는, 각모터가 제어하는 축들을 사용자가 직접 설정한다. dAMotorEuler 모드에서는, 각모터가 상대적인 회전에 상응하는 오일러 각들(euler angles)을 계산한다, 돌림힘 모터와 멈춤이 오일러 각으로 설정되는 것을 허용한다. 오일러 각을 가진 각모터 관절은 그림 10에 나와있다.

amotor.jpg

그림 10: 오일러 각들을 가진 각모터 관절.

그림에서, a0, a1, a2는 각모터를 제어하는 세 축이다. 녹색 축은 body1에 고정된다. 파란색 축은 body2에 고정된다. body1 축들로부터 body2 축들을 얻기위해 아래 순서의 회전들을 수행한다:

  • a0축에 대해 theta0만큼 회전한다.
  • a1축에 대해 theta1만큼 회전한다 (a1는 원래 위치로부터 회전되었다).
  • a2축에 대해 theta2만큼 회전한다 (a2는 원래 위치로부터 두번 회전되었다).

오일러 각들을 사용할 때 중요한 제약 조건이 있다: theta1각은 - pi /2 ... pi /2 범위 밖에 있어선 안된다. 만약 범위를 넘으면 각모터 관절이 불안정해질 것이다 (+/- pi /2에 특이점이 있다). 따라서 axis1에 적절한 멈춤을 설정해야 한다.

void dJointSetAMotorMode (dJointID, int mode); int dJointGetAMotorMode (dJointID);

각모터 모드를 설정(리턴)한다. mode 매개 변수는 다음 상수들 중 하나여야 한다:
dAMotorUser 각모터 축과 관절 각 설정이 완전히 사용자에 의해 제어된다. 이것이 기본 모드이다.
dAMotorEuler 오일러 각들이 자동 계산된다. 축 a1 또한 자동 계산된다. 이 모드에서 각모터 축들은 올바르게 설정되야 한다. 초기에 이 모드가 설정되면, 강체의 현재 상대적인 회전들은 0에서 모든 오일러 각들과 일치한다.

void dJointSetAMotorNumAxes (dJointID, int num); int dJointGetAMotorNumAxes (dJointID);

각모터에 의해 제어되는 각 축들의 수를 설정/리턴한다. 인자 num은 0부터 3까지의 범위내에 속한다. 0은 관절을 비활성화한다. dAMotorEuler 모드에서는 자동적으로 3으로 설정된다.
void dJointSetAMotorAxis (dJointID, int anum, int rel,
             dReal x, dReal y, dReal z);
void dJointGetAMotorAxis (dJointID, int anum, dVector3 result);
int dJointGetAMotorAxisRel (dJointID, int anum);
각모터 축들을 설정/리턴한다. anum 인자는 변경할 축(0,1, 또는 2)을 선택한다. 각각의 축은 세 개의 "상대 회전(relative orientation)" 모드들 중 하나를 가질 수 있고, rel에 의해 선택된다:
  • 0: 축이 전역 틀에 고정된다.
  • 1: 축이 첫번째 강체에 고정된다.
  • 2: 축이 두번째 강체에 고정된다.
축 벡터 (x,y,z)는 rel값에 관계없이 항상 전역 좌표계에서 설정된다. 두개의 GetAMotorAxis 함수들이 있다, 하나는 축을 리턴하고 하나는 관련 모드를 리턴한다.
dAMotorEuler 모드에 대해:
  • axis0과 axis2만 설정하면 된다. axis1은 자동으로 매 시간 스텝마다 계산된다.
  • axis0과 2만 반드시 서로 수직이어야 한다.
  • axis0은 첫번째 강체에, axis2는 두번째 강체에 고정되어야 한다.

void dJointSetAMotorAngle (dJointID, int anum, dReal angle);

각모터에게 축 anum에 대한 현재 각도를 알려준다. 이 함수는 dAMotorUser 모드에서만 호출되어야 한다, 왜냐하면 이 모드에서 각모터는 관절 각을 알 수 있는 방법이 없기 때문이다. 각 정보는 축을 따라 멈춤이 설정되었을 때 필요하다. 그러나 각모터에는 필요하지 않다.

dReal dJointGetAMotorAngle (dJointID, int anum);

축 anum에 대한 현재 각도를 리턴한다. dAMotorUser 모드에서 이것은 dJointSetAMotorAngle로 설정한 값이다. dAMotorEuler 모드에서 이것은 오일러 각과 일치한다.

dReal dJointGetAMotorAngleRate (dJointID, int anum);

축 anum에 대한 현재 각 변화율을 리턴한다. dAMotorUser 모드에서 정보가 충분치 않을 때 이것은 항상 0이다. dAMotorEuler 모드에서는 오일러 각 변화율과 일치한다.

일반

관절 기하구조 매개 변수를 설정하는 함수