10. Corridor World 제작
1) Corridor World의 목적
Corridor World는 좁은 복도에서 모바일로봇이 안정적으로 주행하는지 확인하기 위한 환경입니다.
좁은 통로 주행
벽 근접 주행
ㄱ자 코너 회전
U자 회전
local planner의 진동 현상 확인
로봇 footprint 설정 검증
2) Corridor 설계
소형 모바일로봇 실습에서는 다음 정도의 폭을 사용할 수 있습니다.
소형 모바일로봇 폭 : 약 0.3~0.5m
기본 복도 폭 : 약 1.2~1.8m
어려운 복도 폭 : 약 0.8~1.0m
벽 높이 : 약 1.5~2.0m
이번 예제의 벽 중심 간 거리는 2m이고 벽 두께는 0.2m입니다.
따라서 실제 내부 통로 폭은 약 1.8m입니다.
3) corridor_world.sdf
worlds/corridor_world.sdf 파일을 만듭니다.
touch worlds/corridor_world.sdf
<?xml version="1.0" ?>
<sdf version="1.9">
<world name="corridor_world">
<plugin
filename="ignition-gazebo-physics-system"
name="ignition::gazebo::systems::Physics"/>
<plugin
filename="ignition-gazebo-user-commands-system"
name="ignition::gazebo::systems::UserCommands"/>
<plugin
filename="ignition-gazebo-scene-broadcaster-system"
name="ignition::gazebo::systems::SceneBroadcaster"/>
<plugin
filename="ignition-gazebo-sensors-system"
name="ignition::gazebo::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<direction>-0.5 0.1 -0.9</direction>
</light>
<model name="ground_plane">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>40 40</size>
</plane>
</geometry>
</collision>
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>40 40</size>
</plane>
</geometry>
<material>
<ambient>0.45 0.45 0.45 1</ambient>
<diffuse>0.45 0.45 0.45 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_01">
<static>true</static>
<pose>-1 -4 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_02">
<static>true</static>
<pose>1 -4 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_03">
<static>true</static>
<pose>4.5 1 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>7 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>7 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_04">
<static>true</static>
<pose>4.5 3 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>7 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>7 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_05">
<static>true</static>
<pose>8 -1 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 8 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 8 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="corridor_wall_06">
<static>true</static>
<pose>10 -1 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 8 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 8 2</size></box>
</geometry>
<material>
<diffuse>0.8 0.8 0.8 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="end_wall_start">
<static>true</static>
<pose>0 -9 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>2.2 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>2.2 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.7 0.7 0.7 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="end_wall_goal">
<static>true</static>
<pose>9 -5 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>2.2 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>2.2 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.7 0.7 0.7 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="start_marker">
<static>true</static>
<pose>0 -7.5 0.01 0 0 0</pose>
<link name="link">
<visual name="visual">
<geometry>
<box><size>0.8 0.8 0.02</size></box>
</geometry>
<material>
<diffuse>0.1 0.8 0.1 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="goal_marker">
<static>true</static>
<pose>9 -3.5 0.01 0 0 0</pose>
<link name="link">
<visual name="visual">
<geometry>
<box><size>0.8 0.8 0.02</size></box>
</geometry>
<material>
<diffuse>0.9 0.1 0.1 1</diffuse>
</material>
</visual>
</link>
</model>
</world>
</sdf>

4) 소스설명
Corridor World 전체 구성
제공된 SDF World는 다음 요소로 구성됩니다.
World 이름 : corridor_world
바닥 크기 : 40m × 40m
복도 벽 : 6개
시작 지점 벽 : 1개
목표 지점 벽 : 1개
시작 마커 : 1개
목표 마커 : 1개
벽 높이 : 2m
벽 두께 : 0.2m
기본 통로 폭 : 약 1.8m
로봇은 왼쪽 아래의 시작 지점에서 출발해 다음 경로로 이동합니다.
시작
↑
│
│
└──────────→
│
│
↓
목표
좌표로 표현하면 다음과 같습니다.
시작 지점 : x=0, y=-7.5
첫 번째 코너 부근 : x=0, y=2
두 번째 코너 부근 : x=9, y=2
목표 지점 : x=9, y=-3.5
SDF 파일 기본 구조
파일은 XML 선언과 SDF 루트 요소로 시작합니다.
<?xml version="1.0" ?>
<sdf version="1.9">
<world name="corridor_world">
...
</world>
</sdf>
각 요소의 의미는 다음과 같습니다.
<?xml version="1.0" ?>
XML 문서 형식을 사용한다는 선언
<sdf version="1.9">
SDF 1.9 문법을 사용한다는 의미
<world name="corridor_world">
Gazebo에 생성할 World의 이름
하나의 <world> 안에는 조명, 바닥, 벽, 센서 시스템 및 여러 모델을 배치할 수 있습니다.
Gazebo Fortress 시스템 플러그인
World 상단에는 Gazebo Fortress의 주요 시스템 플러그인이 등록되어 있습니다.
– Physics 시스템
<plugin
filename="ignition-gazebo-physics-system"
name="ignition::gazebo::systems::Physics"/>
Physics 시스템은 다음 기능을 처리합니다.
중력
충돌 판정
질량과 관성
마찰
힘과 토크
관절 운동
로봇 이동
현재 World의 벽과 바닥은 모두 static 모델이므로 움직이지 않습니다.
추후 차동구동 모바일로봇을 추가하면 Physics 시스템이 바퀴와 바닥 사이의 접촉, 마찰 및 로봇의 움직임을 계산합니다.
– UserCommands 시스템
<plugin
filename="ignition-gazebo-user-commands-system"
name="ignition::gazebo::systems::UserCommands"/>
UserCommands 시스템은 실행 중인 World에서 다음 작업을 지원합니다.
모델 생성
모델 삭제
모델 위치 변경
엔티티 제어
Gazebo GUI 명령 처리
ROS 2에서 모바일로봇 모델을 spawn할 때 필요한 시스템입니다.
– SceneBroadcaster 시스템
<plugin
filename="ignition-gazebo-scene-broadcaster-system"
name="ignition::gazebo::systems::SceneBroadcaster"/>
SceneBroadcaster는 Gazebo 서버의 장면 정보를 GUI에 전달합니다.
이 시스템을 통해 Gazebo GUI에서 바닥, 벽, 마커 및 로봇 모델을 확인할 수 있습니다.
– Sensors 시스템
<plugin
filename="ignition-gazebo-sensors-system"
name="ignition::gazebo::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
Sensors 시스템은 렌더링 기반 센서를 처리합니다.
대표적으로 다음 센서가 해당됩니다.
카메라
Depth Camera
RGB-D Camera
GPU LiDAR
Segmentation Camera
현재 World 자체에는 센서가 포함되어 있지 않지만, 이후 LiDAR나 카메라가 포함된 모바일로봇을 추가할 수 있도록 Sensors 시스템을 등록한 것입니다.
<render_engine>ogre2</render_engine>
렌더링 엔진으로 OGRE2를 사용한다는 의미입니다.
조명 설정
World에는 태양광 역할을 하는 방향성 조명이 포함되어 있습니다.
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<direction>-0.5 0.1 -0.9</direction>
</light>
– 방향성 조명
<light type="directional" name="sun">
directional 조명은 태양처럼 일정한 방향으로 빛을 비춥니다.
– 그림자
<cast_shadows>true</cast_shadows>
벽과 로봇이 바닥에 그림자를 만들도록 설정합니다.
– 조명 pose
<pose>0 0 10 0 0 0</pose>
SDF의 pose는 다음 순서입니다.
x y z roll pitch yaw
조명의 z 위치가 10m로 설정되어 있습니다.
– 조명 방향
<direction>-0.5 0.1 -0.9</direction>
z 방향 값이 음수이므로 빛이 위에서 바닥 방향으로 비칩니다.
바닥 모델
바닥 모델 이름은 ground_plane입니다.
<model name="ground_plane">
<static>true</static>
static이 true이므로 바닥은 중력이나 충돌에 의해 움직이지 않습니다.
– 바닥 collision
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>40 40</size>
</plane>
</geometry>
</collision>
바닥 충돌 영역은 40m × 40m입니다.
<normal>0 0 1</normal>
평면의 법선 방향이 z축 양의 방향이라는 의미입니다.
– 바닥 visual
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>40 40</size>
</plane>
</geometry>
</visual>
화면에 표시되는 바닥 크기도 collision과 동일합니다.
– 바닥 색상
<material>
<ambient>0.45 0.45 0.45 1</ambient>
<diffuse>0.45 0.45 0.45 1</diffuse>
</material>
색상 값은 RGBA 형식입니다.
R : Red
G : Green
B : Blue
A : Alpha
R, G, B가 모두 0.45이므로 바닥은 회색으로 표시됩니다.
첫 번째 세로 복도
첫 번째 복도는 corridor_wall_01과 corridor_wall_02로 구성됩니다.
– 왼쪽 벽
<model name="corridor_wall_01">
<static>true</static>
<pose>-1 -4 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>0.2 10 2</size>
</box>
정리하면 다음과 같습니다.
중심 위치 : x=-1, y=-4, z=1
x 방향 크기 : 0.2m
y 방향 크기 : 10m
높이 : 2m
벽이 y 방향으로 길게 배치되어 있으므로 세로 벽이 됩니다.
벽의 y 방향 범위는 대략 다음과 같습니다.
중심 y : -4
길이 : 10m
범위 : -9m부터 1m까지
– 오른쪽 벽
<model name="corridor_wall_02">
<static>true</static>
<pose>1 -4 1 0 0 0</pose>
두 번째 벽은 첫 번째 벽과 크기가 같고 x 좌표만 1m입니다.
두 벽의 중심 간 거리는 2m입니다.
왼쪽 벽 중심 : x=-1
오른쪽 벽 중심: x=1
중심 간 거리 : 2m
각 벽의 두께가 0.2m이므로 실제 내부 복도 폭은 다음과 같습니다.
내부 복도 폭
= 중심 간 거리 - 벽 두께
= 2.0 - 0.2
= 약 1.8m
조금 더 정확히 계산하면 왼쪽 벽의 안쪽 면은 x=-0.9이고, 오른쪽 벽의 안쪽 면은 x=0.9입니다.
0.9 - (-0.9) = 1.8m
가로 복도
첫 번째 세로 복도는 위쪽에서 오른쪽 방향으로 꺾입니다.
가로 복도는 corridor_wall_03과 corridor_wall_04로 구성됩니다.
– 아래쪽 벽
<model name="corridor_wall_03">
<static>true</static>
<pose>4.5 1 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>7 0.2 2</size>
</box>
정리하면 다음과 같습니다.
중심 위치 : x=4.5, y=1, z=1
x 방향 길이 : 7m
두께 : 0.2m
높이 : 2m
x 방향 범위는 다음과 같습니다.
중심 x : 4.5
길이 : 7m
범위 : x=1부터 x=8까지
– 위쪽 벽
<model name="corridor_wall_04">
<static>true</static>
<pose>4.5 3 1 0 0 0</pose>
위쪽 벽은 아래쪽 벽과 크기가 같고 y 좌표만 3m입니다.
두 벽 중심 간 거리는 2m이고 벽 두께는 0.2m이므로 가로 복도의 실제 내부 폭도 약 1.8m입니다.
아래쪽 벽 안쪽 면 : y=1.1
위쪽 벽 안쪽 면 : y=2.9
복도 폭
= 2.9 - 1.1
= 1.8m
두 번째 세로 복도
가로 복도 끝에서는 로봇이 아래쪽으로 회전합니다.
두 번째 세로 복도는 corridor_wall_05와 corridor_wall_06으로 구성됩니다.
– 왼쪽 벽
<model name="corridor_wall_05">
<static>true</static>
<pose>8 -1 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>0.2 8 2</size>
</box>
벽의 y 방향 범위는 다음과 같습니다.
중심 y : -1
길이 : 8m
범위 : y=-5부터 y=3까지
– 오른쪽 벽
<model name="corridor_wall_06">
<static>true</static>
<pose>10 -1 1 0 0 0</pose>
두 번째 벽은 x=10m에 배치되어 있습니다.
두 벽 중심 사이의 거리는 2m이고 실제 내부 통로 폭은 약 1.8m입니다.
왼쪽 벽 안쪽 면 : x=8.1
오른쪽 벽 안쪽 면: x=9.9
복도 폭
= 9.9 - 8.1
= 1.8m
전체 복도 연결 구조
전체 복도는 여섯 개의 벽이 연결되어 하나의 굽은 통로를 만듭니다.
각 벽의 범위를 정리하면 다음과 같습니다.
| 모델 | 중심 위치 | 크기 | 역할 |
|---|---|---|---|
| corridor_wall_01 | (-1, -4) | 0.2 × 10 × 2 | 첫 번째 복도 왼쪽 벽 |
| corridor_wall_02 | (1, -4) | 0.2 × 10 × 2 | 첫 번째 복도 오른쪽 벽 |
| corridor_wall_03 | (4.5, 1) | 7 × 0.2 × 2 | 가로 복도 아래쪽 벽 |
| corridor_wall_04 | (4.5, 3) | 7 × 0.2 × 2 | 가로 복도 위쪽 벽 |
| corridor_wall_05 | (8, -1) | 0.2 × 8 × 2 | 두 번째 복도 왼쪽 벽 |
| corridor_wall_06 | (10, -1) | 0.2 × 8 × 2 | 두 번째 복도 오른쪽 벽 |
위에서 본 복도는 다음과 같이 해석할 수 있습니다.
y=3 +------------------------+
| |
y=1 +----+--------------+ |
| | |
| | |
| | |
| | |
y=-5 | +----+
| |
y=-9 +--- 시작 목표
로봇의 이동 방향은 다음과 같습니다.
1. 시작 지점에서 북쪽으로 이동
2. 첫 번째 코너에서 오른쪽으로 회전
3. 가로 복도를 따라 동쪽으로 이동
4. 두 번째 코너에서 아래쪽으로 회전
5. 목표 지점까지 남쪽으로 이동
시작 지점의 끝 벽
첫 번째 복도 아래쪽은 end_wall_start가 막고 있습니다.
<model name="end_wall_start">
<static>true</static>
<pose>0 -9 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>2.2 0.2 2</size>
</box>
첫 번째 복도의 외부 폭 전체를 막기 위해 x 방향 크기가 2.2m로 설정되어 있습니다.
왼쪽 벽 외부 면 : 약 x=-1.1
오른쪽 벽 외부 면: 약 x=1.1
전체 폭 : 2.2m
이 벽은 시작 지점 뒤쪽을 막아 로봇이 반대 방향으로 빠져나가지 못하도록 합니다.
목표 지점의 끝 벽
두 번째 복도 아래쪽은 end_wall_goal이 막고 있습니다.
<model name="end_wall_goal">
<static>true</static>
<pose>9 -5 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>2.2 0.2 2</size>
</box>
목표 지점 복도의 x 중심은 9m입니다.
왼쪽 벽 : x=8
오른쪽 벽: x=10
복도 중심: x=9
끝 벽이 y=-5m에 배치되어 목표 지점 뒤쪽을 막습니다.
시작 위치 마커
시작 위치는 초록색 바닥 마커로 표시됩니다.
<model name="start_marker">
<static>true</static>
<pose>0 -7.5 0.01 0 0 0</pose>
마커 크기는 다음과 같습니다.
<box>
<size>0.8 0.8 0.02</size>
</box>
색상은 초록색입니다.
<diffuse>0.1 0.8 0.1 1</diffuse>
마커의 중심 위치는 다음과 같습니다.
x : 0
y : -7.5
z : 0.01
마커 높이가 0.02m이기 때문에 z 중심을 0.01m로 설정하면 바닥 위에 정확히 배치됩니다.
z 위치
= 마커 높이 ÷ 2
= 0.02 ÷ 2
= 0.01m
시작 마커에는 <collision>이 없고 <visual>만 있습니다.
따라서 로봇은 마커 위를 자유롭게 통과할 수 있습니다.
목표 위치 마커
목표 위치는 빨간색 바닥 마커로 표시됩니다.
<model name="goal_marker">
<static>true</static>
<pose>9 -3.5 0.01 0 0 0</pose>
마커 크기는 시작 마커와 같습니다.
<box>
<size>0.8 0.8 0.02</size>
</box>
색상은 빨간색입니다.
<diffuse>0.9 0.1 0.1 1</diffuse>
목표 위치는 다음과 같습니다.
x : 9
y : -3.5
z : 0.01
목표 마커에도 collision이 없기 때문에 로봇의 주행을 방해하지 않습니다.
visual과 collision의 차이
복도 벽에는 visual과 collision이 모두 포함되어 있습니다.
<collision name="collision">
<geometry>
<box>
<size>0.2 10 2</size>
</box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box>
<size>0.2 10 2</size>
</box>
</geometry>
</visual>
역할은 다음과 같습니다.
visual
Gazebo 화면에 벽을 표시
collision
로봇과 벽 사이의 실제 충돌을 계산
벽에 visual만 있고 collision이 없으면 화면에는 벽이 보이지만 로봇이 통과할 수 있습니다.
시작 마커와 목표 마커는 위치 표시만 필요하므로 의도적으로 visual만 사용합니다.
벽 높이와 z 좌표
모든 복도 벽의 높이는 2m입니다.
<size>0.2 10 2</size>
벽의 pose에서 z 좌표는 1m입니다.
<pose>-1 -4 1 0 0 0</pose>
box의 pose는 형상의 중심을 기준으로 하기 때문입니다.
z 중심 위치
= 벽 높이 ÷ 2
= 2 ÷ 2
= 1m
벽 높이가 2m인데 z 좌표를 0으로 설정하면 벽의 절반이 바닥 아래로 들어갑니다.
복도 폭과 로봇 크기
이번 World의 실제 복도 폭은 약 1.8m입니다.
로봇의 폭이 0.4m라면 양쪽에 남는 공간은 다음과 같습니다.
전체 여유 공간
= 복도 폭 - 로봇 폭
= 1.8 - 0.4
= 1.4m
로봇이 복도 중앙에 있다면 좌우 여유 공간은 각각 약 0.7m입니다.
좌우 여유 공간
= 1.4 ÷ 2
= 0.7m
하지만 Nav2에서는 실제 로봇 크기만 고려하지 않습니다.
다음 항목이 함께 영향을 줍니다.
로봇 footprint
localization 오차
제어 오차
costmap 해상도
inflation radius
센서 오차
필요한 복도 폭은 다음과 같이 생각할 수 있습니다.
필요 복도 폭
= 로봇 폭
+ 왼쪽 안전거리
+ 오른쪽 안전거리
+ localization 오차
+ 제어 오차
5) 실행
ROS 2 launch로 실행합니다.
colcon build --packages-select mobile_robot_worlds
source install/setup.bash
ros2 launch mobile_robot_worlds gz_world.launch.py world_name:=corridor_world.sdf


직접 실행합니다.
ign gazebo -r -v 4 $(ros2 pkg prefix --share mobile_robot_worlds)/worlds/corridor_world.sdf

6) 검토
복도 폭과 로봇 footprint
로봇이 실제로 지나갈 수 있는지는 로봇의 실제 폭만 보고 판단하면 안 됩니다.
필요 통로 폭
= 로봇 폭
+ 좌측 안전거리
+ 우측 안전거리
+ localization 오차
+ 제어 오차
예를 들어 로봇 폭이 0.4m이고 좌우 안전거리를 각각 0.2m로 설정하면 최소 0.8m가 필요합니다.
여기에 localization 오차와 제어 오차를 고려하면 실습 환경에서는 1.0m 이상의 통로가 안전합니다.
코너에서 local planner가 흔들리는 이유
좁은 ㄱ자 코너에서는 costmap의 inflation 영역이 서로 겹칠 수 있습니다.
이 경우 planner가 이동 가능한 공간을 실제보다 좁게 판단합니다.
다음 파라미터를 변경하면서 경로 변화를 확인합니다.
inflation_radius: 0.3
cost_scaling_factor: 3.0
robot_radius: 0.22
벽 collision이 없으면 어떻게 되는가?
<visual>만 있고 <collision>이 없으면 벽은 화면에 보이지만 로봇이 통과할 수 있습니다.
Gazebo World 제작 과정에서 자주 발생하는 실수입니다.
11. Warehouse World 제작
1) Warehouse World의 목적
Warehouse World는 물류형 모바일로봇, AMR 및 AGV 실습에 적합합니다.
선반 사이 통로 주행
좁은 aisle에서 경로 생성
여러 목표 지점 waypoint 이동
docking 위치 접근
SLAM 맵 생성
Nav2 global planner 테스트
Nav2 local planner 테스트
2) Warehouse 설계
이번 예제는 다음과 같이 구성합니다.
전체 공간 : 약 24m × 18m
선반 수 : 12개
각 선반 크기 : 1.0m × 3~4m × 1.8m
선반 중심 간격 : 약 4m
입구와 출구 : 전면 중앙
도킹 구역 : 왼쪽 하단
적재 구역 : 오른쪽 하단
3) warehouse_world.sdf
worlds/warehouse_world.sdf 파일을 만듭니다.
touch worlds/warehouse_world.sdf

<?xml version="1.0" ?>
<sdf version="1.9">
<world name="warehouse_world">
<plugin
filename="ignition-gazebo-physics-system"
name="ignition::gazebo::systems::Physics"/>
<plugin
filename="ignition-gazebo-user-commands-system"
name="ignition::gazebo::systems::UserCommands"/>
<plugin
filename="ignition-gazebo-scene-broadcaster-system"
name="ignition::gazebo::systems::SceneBroadcaster"/>
<plugin
filename="ignition-gazebo-sensors-system"
name="ignition::gazebo::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 12 0 0 0</pose>
<diffuse>0.9 0.9 0.9 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<direction>-0.5 0.2 -1.0</direction>
</light>
<model name="warehouse_floor">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>50 50</size>
</plane>
</geometry>
</collision>
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>50 50</size>
</plane>
</geometry>
<material>
<ambient>0.5 0.5 0.5 1</ambient>
<diffuse>0.5 0.5 0.5 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="wall_back">
<static>true</static>
<pose>0 9 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>24 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>24 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.75 0.75 0.75 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="wall_left">
<static>true</static>
<pose>-12 0 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 18 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 18 2</size></box>
</geometry>
<material>
<diffuse>0.75 0.75 0.75 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="wall_right">
<static>true</static>
<pose>12 0 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 18 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 18 2</size></box>
</geometry>
<material>
<diffuse>0.75 0.75 0.75 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="wall_front_left">
<static>true</static>
<pose>-7 -9 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>10 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>10 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.75 0.75 0.75 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="wall_front_right">
<static>true</static>
<pose>7 -9 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>10 0.2 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>10 0.2 2</size></box>
</geometry>
<material>
<diffuse>0.75 0.75 0.75 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_01">
<static>true</static>
<pose>-7 5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_02">
<static>true</static>
<pose>-3 5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_03">
<static>true</static>
<pose>1 5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_04">
<static>true</static>
<pose>5 5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_05">
<static>true</static>
<pose>-7 0 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_06">
<static>true</static>
<pose>-3 0 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_07">
<static>true</static>
<pose>1 0 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_08">
<static>true</static>
<pose>5 0 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 4 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_09">
<static>true</static>
<pose>-7 -5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_10">
<static>true</static>
<pose>-3 -5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_11">
<static>true</static>
<pose>1 -5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="shelf_12">
<static>true</static>
<pose>5 -5 0.9 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 3 1.8</size></box>
</geometry>
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="docking_area">
<static>true</static>
<pose>-10 -6 0.02 0 0 0</pose>
<link name="link">
<visual name="visual">
<geometry>
<box><size>2 2 0.03</size></box>
</geometry>
<material>
<diffuse>0.1 0.7 0.9 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="loading_area">
<static>true</static>
<pose>9 -6 0.02 0 0 0</pose>
<link name="link">
<visual name="visual">
<geometry>
<box><size>2.5 2 0.03</size></box>
</geometry>
<material>
<diffuse>0.9 0.7 0.1 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="pallet_box_01">
<static>true</static>
<pose>9 -3.5 0.4 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 1 0.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 1 0.8</size></box>
</geometry>
<material>
<diffuse>0.4 0.25 0.1 1</diffuse>
</material>
</visual>
</link>
</model>
<model name="pallet_box_02">
<static>true</static>
<pose>10.2 -3.5 0.4 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>1 1 0.8</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>1 1 0.8</size></box>
</geometry>
<material>
<diffuse>0.4 0.25 0.1 1</diffuse>
</material>
</visual>
</link>
</model>
</world>
</sdf>

4) 소스 설명
Warehouse World 전체 구성
위의 SDF 소스는 다음 요소로 구성됩니다.
World 이름 : warehouse_world
바닥 크기 : 50m × 50m
창고 내부 : 약 24m × 18m
외벽 : 5개
입구 : 전면 중앙
선반 : 12개
도킹 구역 : 1개
적재 구역 : 1개
팔레트 박스 : 2개
벽 높이 : 2m
선반 높이 : 1.8m
창고를 위에서 보면 대략 다음과 같이 구성됩니다.
뒤쪽 벽
+--------------------------------------+
| |
| [01] [02] [03] [04] |
| |
| [05] [06] [07] [08] |
| |
| [09] [10] [11] [12] |
| |
| Docking Pallet |
| Loading |
+--------------- ---------------------+
입구
창고의 전면 중앙에는 약 4m 폭의 출입구가 있습니다.
SDF 파일의 기본 구조
SDF 파일은 XML 형식으로 작성합니다.
<?xml version="1.0" ?>
<sdf version="1.9">
<world name="warehouse_world">
...
</world>
</sdf>
각 요소의 의미는 다음과 같습니다.
<?xml version="1.0" ?>
XML 형식의 문서임을 선언합니다.
<sdf version="1.9">
SDF 1.9 문법을 사용한다는 의미입니다.
<world name="warehouse_world">
Gazebo에 생성할 World의 이름을 지정합니다.
<world> 내부에는 시스템 플러그인, 조명, 바닥, 벽, 선반 및 여러 고정 모델이 배치됩니다.
창고 바닥
바닥 모델의 이름은 warehouse_floor입니다.
<model name="warehouse_floor">
<static>true</static>
<static>true</static>은 해당 모델이 중력이나 충돌에 의해 움직이지 않는다는 의미입니다.
바닥, 벽, 선반과 같이 고정된 구조물은 일반적으로 static 모델로 설정합니다.
– 바닥 collision
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>50 50</size>
</plane>
</geometry>
</collision>
바닥 충돌 영역의 크기는 50m × 50m입니다.
<normal>0 0 1</normal>
평면의 법선 방향이 z축 양의 방향이라는 의미입니다.
– 바닥 visual
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>50 50</size>
</plane>
</geometry>
</visual>
화면에 표시되는 바닥 크기도 collision과 동일합니다.
– 바닥 색상
<material>
<ambient>0.5 0.5 0.5 1</ambient>
<diffuse>0.5 0.5 0.5 1</diffuse>
</material>
색상은 RGBA 순서로 지정합니다.
R : Red
G : Green
B : Blue
A : Alpha
R, G, B 값이 모두 0.5이므로 바닥은 회색으로 표시됩니다.
창고 외벽 구성
창고 내부 공간은 약 24m × 18m입니다.
외벽은 다음 다섯 개의 모델로 구성됩니다.
wall_back
wall_left
wall_right
wall_front_left
wall_front_right
전면 벽은 왼쪽과 오른쪽 두 부분으로 나뉘어 있으며, 중앙에는 출입구가 있습니다.
– 뒤쪽 벽
뒤쪽 벽은 wall_back 모델입니다.
<model name="wall_back">
<static>true</static>
<pose>0 9 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<box>
<size>24 0.2 2</size>
</box>
정리하면 다음과 같습니다.
중심 위치 : x=0, y=9, z=1
길이 : 24m
두께 : 0.2m
높이 : 2m
x 방향으로 24m 길이이므로 벽의 범위는 약 x=-12부터 x=12까지입니다.
벽 높이가 2m이므로 중심 z 좌표는 1m입니다.
z 위치
= 벽 높이 ÷ 2
= 2 ÷ 2
= 1m
왼쪽과 오른쪽 벽
– 왼쪽 벽
<model name="wall_left">
<static>true</static>
<pose>-12 0 1 0 0 0</pose>
벽 크기는 다음과 같습니다.
<size>0.2 18 2</size>
중심 위치 : x=-12, y=0, z=1
두께 : 0.2m
길이 : 18m
높이 : 2m
y 방향 범위는 약 -9m부터 9m까지입니다.
– 오른쪽 벽
<model name="wall_right">
<static>true</static>
<pose>12 0 1 0 0 0</pose>
오른쪽 벽은 왼쪽 벽과 크기가 같고 x 좌표만 12m입니다.
두 벽 중심 간 거리는 24m입니다.
전면 벽과 출입구
전면 벽은 두 개로 나뉘어 있습니다.
– 전면 왼쪽 벽
<model name="wall_front_left">
<static>true</static>
<pose>-7 -9 1 0 0 0</pose>
크기는 다음과 같습니다.
<size>10 0.2 2</size>
x 방향 범위는 약 -12m부터 -2m까지입니다.
– 전면 오른쪽 벽
<model name="wall_front_right">
<static>true</static>
<pose>7 -9 1 0 0 0</pose>
크기는 왼쪽 벽과 동일합니다.
x 방향 범위는 약 2m부터 12m까지입니다.
– 출입구 폭
왼쪽 전면 벽은 x=-2m에서 끝나고 오른쪽 전면 벽은 x=2m에서 시작합니다.
따라서 중앙 출입구 폭은 약 4m입니다.
출입구 폭
= 2 - (-2)
= 4m
이 정도 폭이면 소형 모바일로봇 여러 대가 이동하거나 넓은 회전 경로를 만드는 실습에 사용할 수 있습니다.
선반 전체 배치
창고 내부에는 총 12개의 선반이 있습니다.
선반은 네 개의 열과 세 개의 행으로 배치되어 있습니다.
열의 x 좌표는 다음과 같습니다.
첫 번째 열 : x=-7
두 번째 열 : x=-3
세 번째 열 : x=1
네 번째 열 : x=5
행의 y 좌표는 다음과 같습니다.
첫 번째 행 : y=5
두 번째 행 : y=0
세 번째 행 : y=-5
배치를 표로 나타내면 다음과 같습니다.
| 행 | x=-7 | x=-3 | x=1 | x=5 |
|---|---|---|---|---|
| y=5 | shelf_01 | shelf_02 | shelf_03 | shelf_04 |
| y=0 | shelf_05 | shelf_06 | shelf_07 | shelf_08 |
| y=-5 | shelf_09 | shelf_10 | shelf_11 | shelf_12 |
첫 번째 행 선반
첫 번째 행에는 shelf_01부터 shelf_04까지 배치되어 있습니다.
예를 들어 shelf_01은 다음과 같습니다.
<model name="shelf_01">
<static>true</static>
<pose>-7 5 0.9 0 0 0</pose>
선반 크기는 다음과 같습니다.
<size>1 4 1.8</size>
정리하면 다음과 같습니다.
x 방향 폭 : 1m
y 방향 길이 : 4m
높이 : 1.8m
선반 높이가 1.8m이므로 중심 z 좌표는 0.9m입니다.
z 위치
= 1.8 ÷ 2
= 0.9m
첫 번째 행의 선반 위치는 다음과 같습니다.
shelf_01 : (-7, 5)
shelf_02 : (-3, 5)
shelf_03 : (1, 5)
shelf_04 : (5, 5)
두 번째 행 선반
두 번째 행에는 shelf_05부터 shelf_08까지 배치되어 있습니다.
shelf_05 : (-7, 0)
shelf_06 : (-3, 0)
shelf_07 : (1, 0)
shelf_08 : (5, 0)
크기는 첫 번째 행과 동일합니다.
<size>1 4 1.8</size>
첫 번째 행과 두 번째 행 사이에는 가로 통로가 만들어집니다.
첫 번째 행 선반은 y=5를 중심으로 길이 4m이므로 y=3부터 y=7까지 차지합니다.
두 번째 행 선반은 y=0을 중심으로 y=-2부터 y=2까지 차지합니다.
따라서 두 행 사이의 실제 빈 공간은 약 1m입니다.
첫 번째 행 아래쪽 면 : y=3
두 번째 행 위쪽 면 : y=2
통로 폭
= 3 - 2
= 1m
로봇 폭과 costmap 설정에 따라 이 통로는 비교적 어려운 구간이 될 수 있습니다.
세 번째 행 선반
세 번째 행에는 shelf_09부터 shelf_12까지 배치되어 있습니다.
shelf_09 : (-7, -5)
shelf_10 : (-3, -5)
shelf_11 : (1, -5)
shelf_12 : (5, -5)
세 번째 행 선반의 크기는 다음과 같습니다.
<size>1 3 1.8</size>
첫 번째와 두 번째 행보다 y 방향 길이가 1m 짧습니다.
첫 번째·두 번째 행 : 1m × 4m × 1.8m
세 번째 행 : 1m × 3m × 1.8m
세 번째 행은 y=-5를 중심으로 y=-6.5부터 y=-3.5까지 차지합니다.
두 번째 행은 y=-2까지 차지하므로 두 번째 행과 세 번째 행 사이의 빈 공간은 약 1.5m입니다.
두 번째 행 아래쪽 면 : y=-2
세 번째 행 위쪽 면 : y=-3.5
통로 폭
= -2 - (-3.5)
= 1.5m
선반 열 사이 통로 폭
선반의 x 방향 폭은 1m이고 중심 간 거리는 4m입니다.
예를 들어 첫 번째 열과 두 번째 열을 살펴보겠습니다.
첫 번째 선반 중심 : x=-7
두 번째 선반 중심 : x=-3
중심 간 거리 : 4m
각 선반의 폭이 1m이므로 실제 통로 폭은 약 3m입니다.
통로 폭
= 중심 간 거리 - 선반 폭
= 4 - 1
= 3m
따라서 세로 방향 주행 통로는 비교적 넓게 구성되어 있습니다.
각 열 사이의 통로 폭은 동일합니다.
shelf_01과 shelf_02 사이 : 약 3m
shelf_02와 shelf_03 사이 : 약 3m
shelf_03과 shelf_04 사이 : 약 3m
선반 배치 요약
전체 선반 정보를 표로 정리하면 다음과 같습니다.
| 선반 | x | y | x 크기 | y 크기 | 높이 |
| shelf_01 | -7 | 5 | 1 | 4 | 1.8 |
| shelf_02 | -3 | 5 | 1 | 4 | 1.8 |
| shelf_03 | 1 | 5 | 1 | 4 | 1.8 |
| shelf_04 | 5 | 5 | 1 | 4 | 1.8 |
| shelf_05 | -7 | 0 | 1 | 4 | 1.8 |
| shelf_06 | -3 | 0 | 1 | 4 | 1.8 |
| shelf_07 | 1 | 0 | 1 | 4 | 1.8 |
| shelf_08 | 5 | 0 | 1 | 4 | 1.8 |
| shelf_09 | -7 | -5 | 1 | 3 | 1.8 |
| shelf_10 | -3 | -5 | 1 | 3 | 1.8 |
| shelf_11 | 1 | -5 | 1 | 3 | 1.8 |
| shelf_12 | 5 | -5 | 1 | 3 | 1.8 |
visual과 collision
모든 선반에는 visual과 collision이 함께 포함되어 있습니다.
<collision name="collision">
<geometry>
<box>
<size>1 4 1.8</size>
</box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box>
<size>1 4 1.8</size>
</box>
</geometry>
</visual>
각 요소의 역할은 다음과 같습니다.
visual
Gazebo 화면에 표시되는 모양
collision
로봇과 실제로 충돌하는 물리 영역
visual만 있고 collision이 없으면 선반은 화면에 보이지만 로봇이 통과할 수 있습니다.
collision만 있고 visual이 없으면 화면에는 보이지 않지만 로봇은 보이지 않는 장애물과 충돌합니다.
선반 색상
선반은 갈색 계열로 설정되어 있습니다.
<material>
<diffuse>0.55 0.35 0.15 1</diffuse>
</material>
RGBA 값은 다음과 같습니다.
R : 0.55
G : 0.35
B : 0.15
A : 1
Red 값이 상대적으로 높고 Blue 값이 낮으므로 나무 또는 갈색 선반과 비슷하게 표시됩니다.
색상은 물리적인 충돌이나 경로계획에는 영향을 주지 않습니다.
도킹 구역
도킹 구역은 창고 왼쪽 아래에 배치되어 있습니다.
<model name="docking_area">
<static>true</static>
<pose>-10 -6 0.02 0 0 0</pose>
크기는 다음과 같습니다.
<size>2 2 0.03</size>
위치는 다음과 같습니다.
x : -10
y : -6
z : 0.02
색상은 청록색 계열입니다.
<diffuse>0.1 0.7 0.9 1</diffuse>
도킹 구역에는 <collision>이 없고 <visual>만 있습니다.
따라서 로봇은 도킹 구역 위를 자유롭게 통과할 수 있습니다.
이 모델은 물리적인 장애물이 아니라 도킹 목표 위치를 표시하기 위한 바닥 마커입니다.
적재 구역
적재 구역은 창고 오른쪽 아래에 배치되어 있습니다.
<model name="loading_area">
<static>true</static>
<pose>9 -6 0.02 0 0 0</pose>
크기는 다음과 같습니다.
<size>2.5 2 0.03</size>
도킹 구역보다 x 방향으로 0.5m 더 큽니다.
도킹 구역 : 2m × 2m
적재 구역 : 2.5m × 2m
색상은 노란색 계열입니다.
<diffuse>0.9 0.7 0.1 1</diffuse>
적재 구역에도 collision이 없으므로 로봇 주행을 방해하지 않습니다.
팔레트 박스
적재 구역 근처에는 두 개의 팔레트 박스가 배치되어 있습니다.
– 첫 번째 팔레트 박스
<model name="pallet_box_01">
<static>true</static>
<pose>9 -3.5 0.4 0 0 0</pose>
크기는 다음과 같습니다.
<size>1 1 0.8</size>
위치 : x=9, y=-3.5
크기 : 1m × 1m × 0.8m
높이가 0.8m이므로 중심 z 좌표는 0.4m입니다.
z 위치
= 0.8 ÷ 2
= 0.4m
– 두 번째 팔레트 박스
<model name="pallet_box_02">
<static>true</static>
<pose>10.2 -3.5 0.4 0 0 0</pose>
두 번째 박스는 첫 번째 박스 오른쪽에 배치되어 있습니다.
두 박스 중심 간 거리는 1.2m이고 각 박스 폭은 1m입니다.
따라서 박스 사이에는 약 0.2m의 간격이 있습니다.
박스 사이 간격
= 중심 간 거리 - 박스 폭
= 1.2 - 1.0
= 0.2m
팔레트 박스에는 visual과 collision이 모두 있으므로 실제 장애물로 동작합니다.
팔레트 박스 색상
팔레트 박스는 어두운 갈색으로 설정되어 있습니다.
<material>
<diffuse>0.4 0.25 0.1 1</diffuse>
</material>
선반보다 RGB 값이 낮기 때문에 더 어두운 갈색으로 표시됩니다.
창고 통로 분석
이번 World에는 크게 두 종류의 통로가 있습니다.
– 선반 열 사이 세로 통로
선반 열 사이의 통로 폭은 약 3m입니다.
열 중심 간 거리 : 4m
선반 폭 : 1m
통로 폭 : 약 3m
일반적인 소형 모바일로봇이 주행하기에 비교적 넓은 공간입니다.
– 선반 행 사이 가로 통로
첫 번째 행과 두 번째 행 사이 통로는 약 1m입니다.
첫 번째 행과 두 번째 행 사이 : 약 1m
두 번째 행과 세 번째 행 사이 통로는 약 1.5m입니다.
두 번째 행과 세 번째 행 사이 : 약 1.5m
가로 통로는 세로 통로보다 좁기 때문에 Nav2의 footprint와 inflation 설정에 더 큰 영향을 받습니다.
– 로봇 크기와 통로 폭
로봇 폭이 0.5m라고 가정하면 폭 1m 통로에서 남는 공간은 다음과 같습니다.
전체 여유 공간
= 통로 폭 - 로봇 폭
= 1.0 - 0.5
= 0.5m
로봇이 통로 중앙에 있으면 좌우 여유 공간은 각각 약 0.25m입니다.
좌우 여유 공간
= 0.5 ÷ 2
= 0.25m
하지만 실제 Nav2에서는 다음 항목을 추가로 고려해야 합니다.
로봇 footprint
inflation_radius
localization 오차
제어 오차
costmap 해상도
센서 오차
물리적으로 통과 가능한 통로라도 inflation 영역이 겹치면 Nav2가 통과 불가능한 공간으로 판단할 수 있습니다.
– pose의 의미
SDF에서 pose는 다음 순서로 작성합니다.
x y z roll pitch yaw
예를 들어 다음 pose를 살펴보겠습니다.
<pose>-7 5 0.9 0 0 0</pose>
의미는 다음과 같습니다.
x : -7m
y : 5m
z : 0.9m
roll : 0rad
pitch : 0rad
yaw : 0rad
현재 모든 선반의 회전값은 0이므로 x축과 y축에 나란하게 배치됩니다.
선반을 회전하려면 yaw 값을 변경할 수 있습니다.
예를 들어 선반을 90도 회전하려면 다음과 같이 설정합니다.
<pose>-7 5 0.9 0 0 1.57</pose>
1.57rad ≒ 90도
5) 실행
ROS 2 launch로 실행합니다.
colcon build --packages-select mobile_robot_worlds
source install/setup.bash
ros2 launch mobile_robot_worlds gz_world.launch.py world_name:=warehouse_world.sdf


직접 실행합니다.
ign gazebo -r -v 4 \
$(ros2 pkg prefix --share mobile_robot_worlds)/worlds/warehouse_world.sdf

12. 빌드 및 실행 전체 명령어 정리
ROS 2 워크스페이스로 이동합니다.
cd ~/gazebo_ws
ROS 2 Humble 환경을 불러옵니다.
source /opt/ros/humble/setup.bash
패키지를 빌드합니다.
colcon build \
--symlink-install \
--packages-select mobile_robot_worlds
워크스페이스 환경을 불러옵니다.
source install/setup.bash
장애물 World를 실행합니다.
ros2 launch mobile_robot_worlds gz_world.launch.py \
world_name:=obstacle_world.sdf
복도 World를 실행합니다.
ros2 launch mobile_robot_worlds gz_world.launch.py \
world_name:=corridor_world.sdf
창고 World를 실행합니다.
ros2 launch mobile_robot_worlds gz_world.launch.py \
world_name:=warehouse_world.sdf
SDF 파일만 빠르게 확인하려면 직접 실행할 수 있습니다.
ign gazebo -r -v 4 \
$(ros2 pkg prefix --share mobile_robot_worlds)/worlds/obstacle_world.sdf
13. 실습 과제
1) 장애물 World 수정
다음 조건을 만족하도록 장애물 World를 수정합니다.
1. 장애물 5개를 추가한다.
2. 장애물 중 2개는 긴 직사각형으로 만든다.
3. 로봇이 통과할 수 있는 좁은 통로를 만든다.
4. 시작 위치와 목표 위치를 바닥 색상으로 표시한다.
평가 기준은 다음과 같습니다.
SDF 문법 오류 없이 실행되는가?
visual과 collision이 모두 존재하는가?
로봇 주행 테스트에 의미 있는 배치인가?
2) Corridor World 난이도 높이기
다음 조건을 만족하도록 복도를 수정합니다.
1. 복도 폭을 약 1.0m로 줄인다.
2. ㄱ자 코너를 하나 더 추가한다.
3. 막다른 길을 하나 만든다.
4. goal_marker를 더 깊은 위치로 옮긴다.
다음 내용을 확인할 수 있습니다.
좁은 복도에서 local planner가 흔들리는 현상
회전 반경 부족 문제
costmap inflation 설정의 중요성
recovery behavior의 필요성
3) Warehouse World 확장
다음 조건을 만족하도록 Warehouse World를 확장합니다.
1. 선반을 12개에서 20개로 늘린다.
2. 선반 사이 aisle 폭을 다르게 만든다.
3. 도킹 구역을 2개 만든다.
4. loading_area 주변에 pallet box를 추가한다.
5. 로봇이 순서대로 방문할 waypoint 좌표를 5개 정한다.
예시 waypoint는 다음과 같습니다.
waypoint_1:
x: -10.0
y: -6.0
waypoint_2:
x: -8.0
y: 5.0
waypoint_3:
x: 0.0
y: 5.0
waypoint_4:
x: 8.0
y: 0.0
waypoint_5:
x: 9.0
y: -6.0