# 세계(World) 파일 구조 이해

#### Gazebo World 파일의 역할

Gazebo에서 시뮬레이션 환경을 구성하기 위해서는 크게 모델(Model) 파일과 세계(World) 파일이 필요하다. 이 중 세계(World) 파일은 가상 환경에서 사용할 지형, 조명, 물리 엔진 설정, 시간, 플러그인, 카메라, 센서 등의 전반적 정보를 총괄한다. 즉, 시뮬레이션의 전반적 무대를 정의하고 관리하는 핵심 파일이다. ROS2 Humble 환경에서도 Gazebo를 사용할 때, 특정 월드에서 로봇과 다양한 객체를 어떻게 배치하고 움직일지 제어하기 위해서는 월드 파일에 대한 정확한 이해가 필수적이다.

#### Gazebo World 파일의 특징

* **SDF(Simulation Description Format)를 주로 사용** Gazebo는 기본적으로 SDF(Simulation Description Format) XML 스키마를 활용한다. SDF는 로봇, 환경, 센서, 플러그인 등의 정보를 구조화하여 표현하는 XML 기반의 포맷이다.
* **XML 계층 구조** 일반적인 XML 문서와 마찬가지로, 태그(tag)와 속성(attribute)을 이용해 계층적인 정보를 표현한다. 이를 통해 복잡한 시뮬레이션 환경을 계층적으로 이해하고 관리할 수 있다.
* **환경 전역 설정** 중력, 마찰 계수, 공기 저항 같은 물리 엔진의 설정부터 시간 스텝 크기, 지형 정보, 조명 설정, 플러그인 연결 관계까지 다양한 전역 설정을 포함한다.
* **재사용성과 모듈성** 월드 파일 내 특정 모델이나 장면 요소를 다른 시뮬레이션에서 재사용하기 쉽도록 구조화한다. 특히 SDF 라이브러리 경로나 시스템 플러그인을 활용하여 확장성 있게 설계할 수 있다.

#### World 파일의 대표 태그

* `<world>` SDF 문서에서 실제 가상 세계를 정의하는 루트 태그이다. 시뮬레이션 시 필요한 모든 정보는 `<world>` 내부에 배치된다.
* `<model>` 가상 세계에 배치되는 객체(로봇, 장애물 등)를 정의하는 태그다. 내부에 링크(link), 조인트(joint), 센서(sensor) 등의 하위 요소를 포함한다.
* `<light>` 시뮬레이션 세계에서 광원(조명)을 정의한다. 광원의 위치, 세기, 색상, 그림자 설정 등을 지정하여 사실적인 시뮬레이션 환경을 구성한다.
* `<physics>` 사용되는 물리 엔진(ODE, Bullet, DART, Simbody 등) 및 엔진 파라미터(시간 스텝 크기, 마찰, 관성 등)를 정의한다.
* `<plugin>` 특정 기능(예: 월드와 직접 상호작용하는 기능, 사용자 정의 시뮬레이션 로직 등)을 수행하는 플러그인을 삽입한다. 로봇 자체의 플러그인뿐 아니라 월드 전역에서 동작하는 플러그인도 여기에 명시할 수 있다.

#### World 파일 예시 구조

아래는 매우 간단화된 형태의 SDF 구조 예시다.

```xml
<?xml version="1.0" ?>
<sdf version="1.7">
  <world name="example_world">

    <light name="sun_light" type="directional">
      <diffuse>1.0 1.0 1.0 1.0</diffuse>
      <specular>0.1 0.1 0.1 1.0</specular>
      <direction>-0.5 0.5 -1.0</direction>
    </light>

    <model name="robot1">
      <include>
        <uri>model://my_robot</uri>
      </include>
    </model>

    <plugin name="example_world_plugin" filename="libworld_plugin.so">
      <param>some_value</param>
    </plugin>

  </world>
</sdf>
```

위 예시에서 볼 수 있듯, `<world>` 안에 `<light>`, `<model>`, `<plugin>` 태그 등이 계층적으로 포함되어 있다. 이 파일을 저장한 뒤 Gazebo나 ROS2에서 실행하면, ‘example\_world’라는 이름으로 지정된 가상 세계가 구동된다.

#### 물리 엔진과 World 파일

월드 파일은 단순히 로봇과 환경을 시각적으로 표현하기만 하는 것이 아니라, 물리 엔진과의 상호작용을 주도한다. 중력 벡터를 예로 들면, 월드 파일에서:

```xml
<gravity>0 0 -9.8</gravity>
```

와 같이 설정할 수 있다. 여기서 중력 벡터를 수식으로 표현하면,

$$
\mathbf{g} =  \begin{bmatrix} 0 \ 0 \ -9.8 \end{bmatrix}
$$

와 같으며, 이는 가상의 세계에서 모든 물체에 작용하게 된다. 이처럼 월드 파일 속 물리 설정은 전체 시뮬레이션의 현실감을 결정짓는 핵심 역할을 한다.

#### World 파일에서 환경(지형)과 지표면 설정

시뮬레이션 환경은 단순히 로봇이나 사물을 배치하는 것만으로는 부족하다. 현실적이고 안정된 실험을 위해서는 지표면 또는 지형(terrain) 요소가 필요하다. Gazebo에서 가장 일반적인 예는 다음과 같은 기본 지면 모델이다.

```xml
<include>
  <uri>model://ground_plane</uri>
</include>
```

이를 사용하면 기본적인 평평한 평면을 월드에 삽입할 수 있다. 실제 지형(예: 언덕, 비포장 도로 등)을 구현하려면 사용자 정의 모델 파일이나 SDF 파일을 포함하여 더욱 복잡한 지형 지도를 불러올 수도 있다.

* 지형 모델 삽입 시 `<pose>` 태그를 통해 특정 위치나 자세(orientation)로 배치할 수 있다.
* 실제 지형과 유사한 충돌(collision) 형상을 적용하기 위해서는 SDF 또는 Collada(DAE) 형식의 고도 데이터(Heightmap) 모델을 사용하기도 한다.

#### Include 태그와 외부 모델(URI) 관리

World 파일에서 `<include>` 태그는 외부 모델을 불러오는 핵심 역할을 한다. 예를 들어:

```xml
<model name="some_model">
  <include>
    <uri>model://my_object</uri>
    <static>true</static>
  </include>
</model>
```

* `<uri>`: Gazebo 모델 데이터베이스, 또는 로컬 경로, 혹은 URDF/XACRO 형식 등을 지정할 수 있다.
* `<static>`: 해당 모델이 물리 엔진 상에서 움직이지 않는 정적인 물체인지 여부를 지정한다. 지면, 벽, 건물 등은 일반적으로 `true`로 설정한다.
* `<pose>`: 월드 내 특정 좌표계에서 해당 모델의 위치 및 자세를 지정한다(예: `<pose>1 2 0 0 0 1.57</pose>`).

#### Pose 태그와 좌표계

Gazebo에서 모델 혹은 링크의 위치와 자세를 정의할 때 `<pose>` 태그를 사용한다. 그 내부 값은 보통 $x\ y\ z\ roll\ pitch\ yaw$ 순서로 배치된다. 예를 들어:

```xml
<pose>1 2 0 0 0 1.57</pose>
```

* 위치(position): `(x, y, z) = (1, 2, 0)`
* 회전(orientation): `(roll, pitch, yaw) = (0, 0, 1.57)`

이를 수학적으로 벡터와 회전 행렬로 표현하면,

$$
\mathbf{p} = \begin{bmatrix} 1 \ 2 \ 0 \end{bmatrix},  \quad R(\text{roll}, \text{pitch}, \text{yaw}) = R(0, 0, 1.57)
$$

와 같이 나타낼 수 있다. 최종적인 좌표 변환은 보통

$$
\mathbf{T} = \begin{bmatrix} R & \mathbf{p} \ \mathbf{0}^{T} & 1 \end{bmatrix}
$$

로 표현되는 4×4 동차변환행렬(homogeneous transformation)을 통해 이루어진다.

#### World 파일에서 센서 사용

월드 차원에서 특정 센서를 정의해야 하는 경우가 있다. 예를 들어, 고정된 CCTV나 환경 센서(레이더, LiDAR 등)를 월드에 직접 배치하고 싶다면 다음처럼 `<sensor>` 태그를 활용할 수 있다.

```xml
<sensor name="static_lidar" type="gpu_lidar">
  <pose>2 3 1 0 0 0</pose>
  <topic>/static_lidar/scan</topic>
  ...
</sensor>
```

* 월드에 고정된 센서는 특정 로봇에 속하지 않으므로 `<world>`의 직계 자식으로 구성할 수 있다.
* 혹은 `<model>` 태그 내부에 `<sensor>`를 배치하여 로봇에 센서를 연결할 수도 있다.

#### 플러그인 구조 확장

World 파일에는 월드 전체를 제어하거나, 월드 레벨에서 수행되어야 하는 처리를 위한 플러그인들을 삽입할 수 있다. 예:

```xml
<plugin name="my_world_plugin" filename="libmyworldplugin.so">
  <param1>value1</param1>
  <param2>value2</param2>
</plugin>
```

* 월드 플러그인은 Gazebo가 실행되면서 월드 로딩 시점에 초기화된다.
* 로봇 등 특정 모델에 관여하기 위해서는 모델 플러그인으로 구분하여 사용하기도 한다.
* ROS2와 연동하기 위한 플러그인(예: ros\_ign\_bridge 등)도 월드 혹은 모델 수준에서 설정한다.

#### World 파일에서 조명, 안개, 하늘(Sky) 설정

Gazebo 월드 환경을 사실적으로 구현하려면 광원(light) 외에도 안개(Fog), 하늘(Sky) 등을 설정하여 몰입도 높은 장면(scene)을 구성할 수 있다.

**조명(Light)**: 월드 파일 내에서 `<light>` 태그를 여러 개 사용해 다양한 광원을 배치할 수 있다. 각 광원의 종류(type)는 ‘directional’, ‘point’, ‘spot’ 등이 있으며, 위치, 세기, 색상 등을 달리하여 여러 형태의 조명을 구성한다.

**안개(Fog)**: `<fog>` 태그를 활용하면 먼 곳을 흐리게 표현해 시야 효과를 구현할 수 있다.

```xml
<scene>
  <fog type="linear">
    <color>0.5 0.5 0.5 1</color>
    <start>0.1</start>
    <end>10.0</end>
  </fog>
</scene>
```

* `type`: ‘linear’, ‘exp’, ‘exp2’ 등 안개의 감쇠(attenuation) 방식
* `color`: 안개의 색상(RGBA)
* `start`, `end`: 안개가 시작되고 끝나는 시점(‘linear’ 모드에서 사용)

**하늘(Sky)**: `<sky>` 태그를 통해 하늘 텍스처나 시간(주야 전환) 효과를 줄 수도 있다. 예:

```xml
<sky>
  <clouds>
    <speed>1</speed>
    <direction>0.1 0.1</direction>
    <humidity>0.5</humidity>
  </clouds>
  <time>12.0</time>
</sky>
```

하늘의 시간(time)을 변경하거나 구름(clouds) 파라미터를 조정해 날씨 변화를 표현할 수 있다.

#### World 파일에서 Scene 구성

월드의 전반적 장면(scene)에 대한 설정을 `<scene>` 태그에서 통합적으로 관리한다. 예:

```xml
<scene>
  <ambient>0.4 0.4 0.4 1</ambient>
  <background>0.8 0.8 0.8 1</background>
  <shadows>true</shadows>
  <fog type="linear">
    ...
  </fog>
</scene>
```

* `ambient`: 환경광(ambient light)의 색상
* `background`: 배경색
* `shadows`: 그림자 사용 여부

#### World 파일에서 시간 및 시뮬레이션 주기

Gazebo 시뮬레이션은 실제 시간(real time)과 시뮬레이션 시간(simulation time)을 분리해서 사용할 수 있다. `<physics>` 태그 내 다양한 요소를 통해 이를 제어한다.

```xml
<physics type="ode">
  <real_time_update_rate>1000</real_time_update_rate>
  <max_step_size>0.001</max_step_size>
  <real_time_factor>1.0</real_time_factor>
</physics>
```

* `real_time_update_rate`: 1초 동안 물리 엔진이 업데이트 되는 횟수
* `max_step_size`: 단일 시뮬레이션 스텝 당 경과하는 시간(초)
* `real_time_factor`: 실제 시간 대비 시뮬레이션 시간의 배율 (1.0이면 시뮬레이션 시간과 실제 시간이 동일)

예를 들어, `real_time_update_rate=1000`이면서 `max_step_size=0.001`이면, 한 번의 업데이트마다 0.001초의 시뮬레이션 시간이 흐르므로 실제 시간과 시뮬레이션 시간의 동기화가 이뤄진다.

#### World 파일에서 상태(State) 정보

시뮬레이션이 진행되는 중 특정 시점의 상태를 월드 파일 형식으로 저장할 수도 있는데, 이를 `<state>` 태그로 표현한다. 월드가 로딩될 때 이 태그가 있는 경우, 시뮬레이션은 해당 시점의 상태로부터 시작한다. 예:

```xml
<state world_name="example_world">
  <sim_time>123.45 678</sim_time>
  <model name="robot1">
    <pose>...</pose>
    ...
  </model>
</state>
```

* `sim_time`: 시뮬레이션 시간과 주기
* `<model name="...">`: 각 모델(혹은 링크)의 위치, 속도, 조인트 상태 등을 기록

#### 물리 엔진(Physics) 상세 설정

Gazebo에서는 다양한 물리 엔진(ODE, Bullet, DART, Simbody)을 지원하며, `<physics>` 태그 내부에서 엔진별 설정을 달리 적용할 수 있다. 대표적인 예인 ODE 엔진의 설정은 다음과 같은 파라미터들을 포함한다.

```xml
<physics type="ode">
  <max_step_size>0.001</max_step_size>
  <real_time_factor>1.0</real_time_factor>
  <real_time_update_rate>1000</real_time_update_rate>
  <ode>
    <solver>
      <type>quick</type>
      <iters>50</iters>
      <sor>1.3</sor>
    </solver>
    <constraints>
      <cfm>0.0</cfm>
      <erp>0.2</erp>
      <contact_max_correcting_vel>100.0</contact_max_correcting_vel>
      <contact_surface_layer>0.001</contact_surface_layer>
    </constraints>
  </ode>
</physics>
```

* `<solver>` 내부 속성
  * `<type>`: solver 알고리즘 타입 (‘quick’, ‘world’, 등).
  * `<iters>`: solver 반복 횟수. 값이 클수록 충돌 감지와 물리 계산 정확도가 높아지지만 계산 비용이 늘어난다.
  * `<sor>`: Successive Over Relaxation 계수로서, 해의 수렴 속도에 영향을 준다.
* `<constraints>` 내부 속성
  * `<cfm>` (Constraint Force Mixing): 고정된 조인트나 충돌 시 경직도를 얼마나 부드럽게 처리할지 결정한다.
  * `<erp>` (Error Reduction Parameter): 물체가 충돌 후, 불규칙한 오차를 얼마나 빠르게 수렴시킬지를 결정한다.
  * `<contact_max_correcting_vel>`: 충돌 시 상대 속도의 최대 보정 값.
  * `<contact_surface_layer>`: 충돌 표면 사이의 완충 영역 두께.

물리 엔진 설정을 적절히 조정하면, 특정 상황(예: 빠른 속도로 움직이는 로봇, 무른 재질 간 접촉 등)에 대한 안정성과 현실성을 개선할 수 있다.

#### Bullet, DART, Simbody 엔진 설정

ODE 외에 Bullet, DART, Simbody 등의 엔진도 월드 파일에서 `<physics type="bullet">`, `<physics type="dart">`, `<physics type="simbody">`와 같은 식으로 지정할 수 있다. 각 엔진마다 사용하는 파라미터가 다르므로 Gazebo 문서를 참고해 해당 엔진에서 지원하는 세부 파라미터를 설정해야 한다.

#### 마찰 계수(진동, damping) 제어

* `<surface>` 태그 아래 `<friction>` 요소를 지정해 각 모델(혹은 링크) 간의 마찰 계수를 설정할 수 있다.
* `<damping>` 같은 속성은 링크의 조인트 등에 적용하여 불필요한 진동을 줄이는 데 활용된다.

예를 들어, `<link>` 내부에서 충돌(collision) 요소를 정의할 때:

```xml
<collision name="collision">
  <surface>
    <friction>
      <ode>
        <mu>1.0</mu>
        <mu2>1.0</mu2>
      </ode>
    </friction>
  </surface>
</collision>
```

* `<mu>`와 `<mu2>`는 각각 주 마찰 계수와 이차 마찰 계수(일종의 직교 방향 마찰 계수)를 의미한다.
* 값이 클수록 접촉 시 더 큰 마찰력이 발생하므로 물체가 쉽게 미끄러지지 않는다.

#### 월드 내 여러 물리 엔진의 활용

일반적으로 월드 파일 하나에 지정되는 물리 엔진은 하나지만, ROS2 연동 시 일부 링크나 조인트에 대해 다른 해석이나 외부 동역학(dynamics)을 모사하기 위해 별도의 플러그인을 사용하는 식으로 유연성을 확보할 수 있다. 그러나 Gazebo 기본 구조상 하나의 월드에 하나의 물리 엔진을 사용하는 것이 기본 원칙이다.

#### ROS2와 연동되는 World 구성

ROS2 Humble 환경에서 Gazebo 월드를 구동할 때는 주로 ROS2 Launch 파일을 통해 실행한다. 예:

```xml
<node pkg="gazebo_ros" exec="gzserver" output="screen"
      args="-s libgazebo_ros_init.so -s libgazebo_ros_factory.so /path/to/my_world.sdf" />
```

* `-s libgazebo_ros_init.so`: Gazebo와 ROS2 간에 시간, 트랜스폼, 토픽 등을 연동해준다.
* `-s libgazebo_ros_factory.so`: 동적으로 모델을 삽입(insert)하거나 삭제(delete)할 때 ROS2 메시지/서비스를 사용할 수 있게 해준다.
* `/path/to/my_world.sdf`: 월드 파일의 실제 경로

이처럼 월드 파일은 Gazebo와 ROS2 사이를 이어주는 기반이며, 월드에 대한 이해가 높아야 시뮬레이션 환경을 효율적으로 제어할 수 있다.

#### World 파일의 확장성과 구조 예시 (다이어그램)

Gazebo World 파일은 다음과 같은 계층 구조를 가질 수 있다. 월드 내부에 여러 모델, 조명, 물리 엔진 설정, 플러그인 등을 배치하며, 각 요소 간 계층관계와 속성을 지정한다. 아래는 다이어그램으로 표현한 예시 구조다.

{% @mermaid/diagram content="flowchart TD
A((sdf)) --> B((world))
B --> C\[scene]
B --> D\[physics]
B --> E\[light... n개]
B --> F\[model... n개]
B --> G\[plugin... n개]
B --> H\[sensor... n개]
F --> F1\[link... n개]
F1 --> F1a\[collision]
F1 --> F1b\[visual]
F --> F2\[joint... n개]" %}

* **sdf**: 루트 XML 문서의 뿌리 요소. `version` 속성을 통해 SDF 스키마 버전을 명시한다.
* world

  : 가상 세계 전체를 표현하는 루트 태그.

  * **scene**: 월드의 시각적 장면(배경색, 안개, 그림자 등)을 설정.
  * **physics**: 사용 물리 엔진 및 시뮬레이션 파라미터.
  * **light**: 광원 정의. 하나의 월드 안에 여러 조명을 삽입 가능.
  * **model**: 로봇, 환경 물체 등. 내부에 여러 링크(link)와 조인트(joint)를 갖는다.
  * **plugin**: 사용자 정의 기능이나 로직을 처리하는 플러그인.
  * **sensor**: 월드 수준에서 사용하는 센서(또는 모델/링크 속 센서) 정의.

#### World 파일 내 여러 모델 동시 배치

하나의 월드 안에는 여러 모델을 동시 배치할 수 있다. 각 모델은 `<model>` 태그에 이름(name)과 고유 속성을 가지며, 서로 다른 위치 `<pose>`를 갖는다. 예를 들어 두 대의 로봇을 배치하려면 아래처럼 구성할 수 있다.

```xml
<sdf version="1.7">
  <world name="multi_robot_world">

    <!-- 첫 번째 로봇 -->
    <model name="robot1">
      <include>
        <uri>model://my_robot</uri>
      </include>
      <pose>0 0 0 0 0 0</pose>
    </model>

    <!-- 두 번째 로봇 -->
    <model name="robot2">
      <include>
        <uri>model://my_robot</uri>
      </include>
      <pose>1 1 0 0 0 1.57</pose>
    </model>

  </world>
</sdf>
```

* `<include>`는 외부 모델 파일(SDF, URDF 등)을 가져온다.
* `<pose>`는 각각 월드 내 좌표계를 기준으로 배치 위치와 회전을 지정한다.

#### World 파일과 Coordinate Frame 관리

Gazebo에서 모델 간 충돌 검사나 로봇 조인트 움직임을 정확히 시뮬레이션하기 위해서는 좌표계 관리가 중요하다.

* **월드 좌표계**: 월드 파일에서 모든 객체는 월드 좌표계를 기준으로 배치된다.
* **모델 좌표계**: 모델 내부에서 `<link>`나 `<joint>`는 모델의 루트 링크를 기준으로 상대 좌표계를 사용한다.
* **Pose 변환**: 상위 태그(예: `<model>`의 `<pose>`)가 바뀌면 하위 요소(링크 등)의 좌표도 해당 변환을 받는다.

예컨대, 월드에서 모델을 $(1, 1, 0)$ 위치에 yaw $=\pi/2$로 배치하면, 모델 내부 링크들의 위치와 회전도 일괄 변환되어 월드 좌표계에 투영된다.

#### World 파일에서 Launch 파일 연동

ROS2에서 시뮬레이션을 자동으로 구동하려면 Launch 파일을 통해 Gazebo와 월드 파일을 함께 호출한다. 예시:

```xml
<launch>
  <node pkg="gazebo_ros" exec="gzserver" output="screen"
        args="/path/to/multi_robot_world.sdf 
              -s libgazebo_ros_init.so 
              -s libgazebo_ros_factory.so"/>
  <node pkg="gazebo_ros" exec="gzclient" output="screen"/>
</launch>
```

* `gzserver`가 Gazebo의 백엔드 서버(물리 엔진, 시뮬레이션 핵심)를 담당하고, `gzclient`가 GUI를 담당한다.
* 월드 파일 경로 뒤에 여러 플러그인을 `-s` 옵션으로 추가하여 ROS2와 동기화 가능.

#### World 파일 수정 시 고려 사항

1. **SDF 버전 호환성** Gazebo(또는 Ignition Gazebo) 버전에 따라 지원하는 SDF 버전이 다르므로, 월드 파일을 작성할 때 프로젝트 환경이 어떤 SDF 버전을 사용하는지 확인해야 한다.
2. **물리 엔진 호환성과 최적화** Bullet, ODE, DART, Simbody 중 어느 엔진을 사용할지 결정하고, 필요한 파라미터(solver, friction, damping)를 조정한다.
3. **플러그인 충돌** 여러 플러그인을 동시에 사용할 때, 동일한 기능(예: ROS2 시간 동기화)을 중복 설정하면 충돌하거나 예기치 못한 동작이 발생할 수 있다.
4. **World 파일 크기 관리** 복잡한 지형, 다수의 모델, 높은 해상도의 텍스처 등을 포함하면 월드 파일(또는 참조 모델)의 용량이 크게 증가한다. 시뮬레이션 로딩 시간 및 성능 저하 문제를 염두에 둬야 한다.

#### Heightmap을 통한 지형 구현

실제 지형(언덕, 골짜기 등)을 실감 나게 표현하기 위해서는 Heightmap 기능을 사용할 수 있다.

```xml
<model name="heightmap_demo">
  <static>true</static>
  <pose>0 0 0 0 0 0</pose>
  <link name="link">
    <collision name="collision">
      <geometry>
        <heightmap>
          <uri>file://media/materials/textures/terrain.png</uri>
          <size>100 100 10</size>
          <pos>0 0 0</pos>
        </heightmap>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <heightmap>
          <uri>file://media/materials/textures/terrain.png</uri>
          <texture>
            <diffuse>file://media/materials/textures/diffuse.png</diffuse>
            <normal>file://media/materials/textures/normal.png</normal>
          </texture>
          <size>100 100 10</size>
        </heightmap>
      </geometry>
    </visual>
  </link>
</model>
```

* `<heightmap>` 요소에 `uri`, `size`, `pos` 등을 설정해 실제 지형을 구성한다.
* `<texture>` 태그로 디퓨즈(diffuse)·노멀(normal) 맵을 적용해 시각 효과를 향상시킬 수 있다.
* `static=true`로 지정해 물리 엔진 상에서 움직이지 않는 지형으로 만든다.

#### GUI 활용한 World 파일 편집

Gazebo(또는 Ignition Gazebo)에서 월드를 로드한 뒤, GUI 상에서 오브젝트를 배치하거나 이동·회전·삭제할 수 있다. 이 때 변경된 내용은 임시적으로 시뮬레이션 엔진에만 반영되고, 실제 SDF(또는 `.world`) 파일에는 자동으로 저장되지 않는다. 변경 사항을 `.world` 파일로 반영하려면 다음과 같은 방식으로 수동 저장해야 한다.

* **Gazebo Classic**: 메뉴 “Edit -> Save World As”를 통해 현재 상태를 `.world` 파일로 내보내기
* **Ignition Gazebo**: 메뉴나 명령줄 인터페이스를 통해 `.sdf`로 저장

GUI에서 생성·편집된 요소들(예: 모델, 광원, 플러그인 설정 등)을 저장해두면, 이후 동일 설정을 다시 불러와 재현할 수 있다.

#### World 파일에서 Visual 요소 확장

Gazebo에서 모델의 시각적 요소(Visual)는 충돌 요소(Collision)와 별도로 관리된다. 월드 차원에서도 다음과 같은 형태로 단순한 Visual 객체를 배치할 수 있다.

```xml
<visual name="my_visual_object">
  <pose>1 0 1 0 0 0</pose>
  <geometry>
    <box>
      <size>1 1 1</size>
    </box>
  </geometry>
  <material>
    <ambient>1 0 0 1</ambient>
    <diffuse>1 0 0 1</diffuse>
    <specular>0.5 0.5 0.5 1</specular>
  </material>
</visual>
```

* **Collision이 없는 순수한 Visual**: 실제 물리 계산(충돌, 마찰 등) 없이 화면에만 렌더링하고 싶을 때 사용한다.
* **Material 설정**: `ambient`, `diffuse`, `specular`와 같은 재질 파라미터를 사용해 색감과 반사 효과를 지정한다.

#### 월드 파일에서 모델 구성과 Visual/Collision 분리

로봇이나 물체를 구성할 때, `<link>` 내부에 `<visual>`와 `<collision>`을 분리해 원하는 물리적 형태와 시각적 형태를 다르게 설정할 수 있다. 예를 들어, 실제 계산 비용이 높지 않도록 낮은 폴리곤(Polygon) 형태의 Collision 메시를 사용하고, 더 세부적인 3D 메시 파일(예: STL, Collada)을 Visual에만 적용하여 렌더링 품질을 높일 수 있다.

```xml
<model name="fancy_model">
  <link name="link">
    <collision name="collision">
      <geometry>
        <sphere>
          <radius>0.5</radius>
        </sphere>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <mesh>
          <uri>model://fancy_model/meshes/high_detail.dae</uri>
          <scale>1 1 1</scale>
        </mesh>
      </geometry>
    </visual>
  </link>
</model>
```

#### 월드에서 Model Configuration을 인라인으로 작성

경우에 따라, 외부 모델 파일을 `<include>` 태그로 불러오지 않고, `<model>` 내부에 링크, 조인트 등을 직접 기술하는 인라인 방식을 사용할 수 있다. 예를 들어, 아주 간단한 큐브(box) 모델을 월드에 직접 기술하고 싶다면:

```xml
<model name="box_model">
  <pose>2 0 0 0 0 0</pose>
  <link name="link">
    <collision name="collision">
      <geometry>
        <box>
          <size>1 1 1</size>
        </box>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <box>
          <size>1 1 1</size>
        </box>
      </geometry>
    </visual>
  </link>
</model>
```

* **장점**: 별도의 모델 파일 없이 월드 하나만으로 모든 정보 관리
* **단점**: 복잡한 로봇·지형 구성에는 코드(월드 파일)가 너무 길어져 가독성이 떨어질 수 있음

#### World 파일에서 State 자동 저장 및 복원

월드가 실행되는 동안, Gazebo는 주기적으로 시뮬레이션 상태(예: 모델 위치, 속도, 조인트 각도 등)를 메모리에 유지한다. 이를 자동 저장(Logging)해뒀다가 이후 복원(Playback)할 수 있다.

**Logging**:

```bash
gz log -r     # 기록(record) 시작
gz log -e     # 기록 종료
```

기록된 로그 파일은 `.log` 형태로 저장되며, 시뮬레이션 재생(Playback)에 활용 가능하다.

**Playback**:

```bash
gz log -p saved_state.log
```

과거 시뮬레이션 상태를 다시 볼 수 있으나, 이때 직접 월드 파일 내 `<state>` 태그로 반영되지는 않는다. `<state>` 요소를 활용해 SDF 파일로 내보내고 싶다면 Gazebo GUI 상의 ‘Save World’ 기능 등을 사용하거나, 로깅된 데이터에서 특정 시점의 상태를 추출해 별도 SDF로 생성하는 스크립트를 사용한다.

#### World 플러그인(Plugin)에서 ROS2 서비스·액션 연동

월드 플러그인은 Gazebo와의 콜백(hook)을 통해 월드가 업데이트될 때마다 특정 함수를 실행한다. 이때 ROS2 서비스, 액션 서버 등을 이용해 외부 요청에 따라 월드를 동적으로 변경할 수 있다.

```cpp
// world_plugin.cpp (간략 예시)
#include <gazebo/gazebo.hh>
#include <rclcpp/rclcpp.hpp>

namespace gazebo
{
  class MyWorldPlugin : public WorldPlugin
  {
  public:
    void Load(physics::WorldPtr _world, sdf::ElementPtr _sdf)
    {
      // ROS2 노드 초기화, 서비스 서버 생성 등
    }

    void OnUpdate()
    {
      // 주기적 시뮬레이션 업데이트 처리
    }
  };
  GZ_REGISTER_WORLD_PLUGIN(MyWorldPlugin)
}
```

* 월드가 실행되면 `Load()`가 호출되고, 매 시뮬레이션 사이클마다 `OnUpdate()`를 통해 월드 상태를 확인·조정한다.
* ROS2를 병행 사용할 때는 rclcpp를 통해 노드를 생성하고, 명령을 받아 모델을 추가·삭제하는 식의 동적 제어가 가능하다.

#### 대규모 월드 환경에서의 성능 최적화

복잡한 지형, 다수의 로봇 및 장애물이 동시에 배치된 월드는 시뮬레이션 성능 저하를 일으킬 수 있다. Gazebo 월드의 규모가 커질수록 고려해야 할 성능 관련 요소는 다음과 같다.

* **충돌(Collision) 메시 단순화**
  * 매우 복잡한 3D 모델(높은 폴리곤 수)을 충돌 검사용으로 그대로 쓰면 계산량이 크게 증가한다.
  * LOD 기법(Level of Detail) 또는 저해상도 충돌 메시를 별도로 만들어 사용한다.
* **센서 업데이트율 조정**
  * 카메라, LiDAR 등 고성능 센서가 많으면 매 사이클마다 대량의 데이터가 생성되어 CPU, GPU 모두 부담이 커진다.
  * `<update_rate>` 옵션으로 각 센서의 업데이트 빈도를 조정해보자.
* **물리 엔진 파라미터 튜닝**
  * `<physics>` 태그 내 `real_time_update_rate`, `max_step_size`, `iters`(solver 반복 횟수) 등을 조정해 성능과 정확도 간 균형점을 찾는다.
  * 예: 시뮬레이션 정확도가 다소 떨어져도 괜찮다면 `iters` 값을 낮추고 `real_time_factor`를 1.0 이상으로 높여 빠른 시뮬레이션을 유도할 수도 있다.
* **불필요한 요소 제거 또는 비활성화**
  * 테스트 시점에서 사용하지 않는 모델이나 플러그인, 센서 등을 월드 파일에서 제외하거나 주석 처리해둔다.
  * 로봇이 실제로 사용하지 않는 센서는 모듈화/주석 처리해 시뮬레이션 부하를 줄일 수 있다.

#### URDF와 SDF의 통합 활용

ROS2에서 로봇 모델은 주로 URDF(XACRO) 형식을 사용하지만, Gazebo 환경(월드)은 SDF로 정의한다. 둘 사이를 연결하기 위한 전형적인 방법은 다음과 같다.

**URDF -> SDF 변환**: ROS2에서는 `gz sdf -p my_robot.urdf` 명령을 통해 URDF를 SDF로 변환할 수 있다. 변환된 SDF를 월드 파일에 `<include>` 형태로 삽입 가능하다.

**XACRO를 이용한 매크로 확장**: 복잡한 로봇을 URDF(XACRO)로 관리하면서, 별도의 Gazebo 태그 (예: `<gazebo>` 요소)를 삽입해 센서, 플러그인 등을 지정할 수 있다.

```xml
<gazebo reference="camera_link">
  <sensor type="camera" name="my_camera">
    ...
  </sensor>
</gazebo>
```

**SDF 기반 World + URDF 기반 로봇**: 실제로는 월드 파일은 SDF, 로봇은 URDF(혹은 XACRO)로 구성하는 경우가 많다. 로봇은 `spawn_entity.py` (혹은 `gazebo_ros_factory` 서비스)를 통해 동적으로 월드에 삽입할 수 있다.

#### 다중 월드(Multiple Worlds) 구성

**한 프로젝트 내 여러 월드 파일 관리**: 테스트 목적, 환경 시나리오별로 별도의 월드 파일을 두고, 동일한 로봇 모델을 여러 환경에서 검증할 수 있다.

월드 파일 간 공통 요소 분리: 지형, 빛, 물리 엔진 설정 등은 공통 요소로 별도 SDF 파일로 만들어놓고 `<include>` 로 불러오는 방법을 사용할 수 있다.

```xml
<include>
  <uri>file://common_world_elements.sdf</uri>
</include>
```

**다중 월드 동시 실행**: Gazebo Classic에서는 한 번에 여러 월드(.world) 파일을 로드하기 어렵다. Ignition Gazebo의 경우 다중 시뮬레이터 인스턴스를 띄워 병렬로 구동할 수 있으나, 기본적으로는 월드 하나당 시뮬레이터 하나를 사용한다.

#### 고급 World 플러그인 사례

* **환경 데이터 로그/분석**: 월드 상태(위치, 속도, 충돌 이벤트 등)를 수집해 로그 파일로 저장하거나, ROS2 메시지로 퍼블리시한다.
* **자동 경로 생성/변경**: 특정 주기나 조건(예: 시간, 센서 값)에 따라 도로를 재배치하거나, 장애물을 이동시키는 월드 플러그인을 만들 수 있다.
* **동적 조명 제어**: 시뮬레이션 도중 해가 지거나 밝기가 달라지도록 `<light>`를 실시간으로 변경하여, 야간 주행 시뮬레이션 등을 재현할 수 있다.

#### 오류(에러) 디버깅 및 유효성 검사

* SDF 문법 에러
  * `$ gz sdf -k my_world.sdf` 명령(또는 `gz sdf --check my_world.sdf`)으로 문법적 유효성을 검사할 수 있다.
  * XML 태그 잘못된 중첩, 속성 누락 등을 확인한다.
* Console 출력 검사
  * Gazebo 실행 후 터미널 로그에 경고(WARN)나 에러(ERROR) 메시지가 뜨면 그 위치(플러그인 로딩, 모델 파싱 등)를 확인한다.
* ROS2 Topic/Service 연결 문제
  * `ros2 topic list`나 `ros2 service list` 등을 통해 월드 플러그인, 센서, 로봇 등이 올바른 토픽/서비스를 쓰는지 점검한다.
* World 요소 충돌
  * 같은 위치에 두 개 이상의 충돌(Collision) 메시가 겹쳐 있으면 물체가 격렬히 튕기거나 시뮬레이션이 불안정해진다.
  * Gazebo GUI에서 모델을 선택해 위치를 확인하거나 `<pose>` 태그를 재검토한다.

#### 지속적 통합(CI) 파이프라인에서의 World 테스트

대규모 ROS2 프로젝트에서는 CI(Continuous Integration) 과정에 시뮬레이션 테스트를 포함한다. 이를 위해:

Headless 모드 시뮬레이션: GUI가 없는 환경에서 `gzserver` 만 실행해 월드를 로드하고, 자동화된 테스트 스크립트를 구동한다.

```bash
gzserver --verbose my_world.sdf &
```

자동 스폰(Spawn) 및 동작 검증:

* 로봇 모델을 스폰하고, 미션(액션, 서비스 호출 등)을 실행한 뒤 결과를 체크한다.
* 성능 측정(예: real\_time\_factor, CPU/GPU 사용량)을 로깅해두면 회귀(regression) 검사에 활용 가능하다.

시뮬레이션 종료 후 로그 및 결과 분석:

* `gz log` 기능으로 기록한 데이터를 분석해, CI 파이프라인 상에서 테스트 결과를 통과/실패로 판별한다.
