차량과 LiDAR 포인트 클라우드가 보이는 메인 화면
조 윤범
CARLA Python API - 시뮬레이터 제어asyncio/websockets - 비동기 실시간 통신NumPy - 대용량 센서 데이터 처리Vite - 빠른 개발 환경Three.js - 3D 렌더링 라이브러리Canvas API - 2D 미니맵OpenDRIVE XML - 표준 도로 맵 포맷JSON - 실시간 데이터 전송# Python (carla_client.py)
points = np.frombuffer(lidar_data.raw_data, dtype='f4')
points = np.reshape(points, (int(points.shape[0] / 4), 4))
# 거리 필터링
distances = np.sqrt(points[:, 0]**2 + points[:, 1]**2 + points[:, 2]**2)
valid_mask = (distances > 1.0) & (distances < 50.0)
points = points[valid_mask]
# 샘플링
if len(points) > 5000:
indices = np.random.choice(len(points), 5000, replace=False)
points = points[indices]
x_three = y_carla / 100
y_three = z_carla / 100
z_three = -x_carla / 100
yaw_three = -yaw_carla + 90°ground: 0.000m
roadSurface: 0.002m
roadEdge: 0.004m
centerLine: 0.006m
laneMarking: 0.008m
결과: ✨ 깔끔한 도로 렌더링
// sensors.js - LiDAR 포인트 변환
update(lidarData, vehiclePosition, vehicleRotation) {
// 1. 차량 위치 변환
const vehiclePos = new Vector3(
vehiclePosition.y / 100,
vehiclePosition.z / 100,
-vehiclePosition.x / 100
);
// 2. 차량 회전을 쿼터니언으로
const quaternion = new Quaternion();
quaternion.multiply(qYaw).multiply(qPitch).multiply(qRoll);
// 3. 각 포인트 변환
lidarData.points.forEach((point, i) => {
const pointVector = new Vector3(
point[1] / 100,
point[2] / 100,
-point[0] / 100
);
pointVector.applyQuaternion(quaternion); // 회전 적용
pointVector.add(vehiclePos); // 이동 적용
// 높이 기반 컬러링
const hue = (point[2] + 200) / 400;
const color = new Color().setHSL(hue, 1, 0.5);
});
}
포인트: 회전 먼저, 이동은 나중에 (행렬 연산 순서)
// opendrive_parser.js - 원호 도로 처리
arcToPoints(geometry, numPoints) {
const curvature = geometry.params.curvature;
const radius = 1.0 / curvature;
// 원의 중심 계산
const centerX = geometry.x - radius * sin(geometry.hdg);
const centerY = geometry.y + radius * cos(geometry.hdg);
for (let i = 0; i <= numPoints; i++) {
const ds = (i / numPoints) * geometry.length;
const angle = curvature * ds; // 진행 각도
// 회전 변환 행렬
const dx = radius * sin(angle);
const dy = radius * (1 - cos(angle));
// 초기 방향 고려한 최종 위치
const x = geometry.x +
dx * cos(hdg) - dy * sin(hdg);
const y = geometry.y +
dx * sin(hdg) + dy * cos(hdg);
points.push({ x, y, s: geometry.s + ds });
}
}
포인트: 기하학 공식의 정확한 구현이 부드러운 곡선을 만듦
문제: CARLA와 Three.js 프레임 불일치
해결: CARLA 동기 모드 + tick() 기반 업데이트
문제: 차량 생성/삭제 시 메모리 증가
해결: actors_to_destroy 리스트로 명시적 cleanup
문제: 차량이 반대로 향함
해결: yaw 각도에 -1 곱하고 90도 오프셋
문제: bbox가 제대로 안보임
해결: bbox.extent 사용 + 회전 적용
문제: 복잡한 기하학 파싱 오류
해결: 타입별 분기 처리 + Fallback 맵
작은 문제들의 누적이 큰 기능을 만든다
직접 부딪혀보며 진짜 실력이 늘었습니다
단순한 뷰어를 넘어 실용적인 도구로
https://github.com/kurt01124/carla-web-viewer
├── carla_bridge/
│ ├── carla_client.py # CARLA 데이터 수집
│ └── websocket_server.py # WebSocket 서버
├── frontend/
│ ├── viewer.js # 메인 뷰어
│ ├── vehicle.js # 차량 렌더링
│ ├── sensors.js # 센서 시각화
│ ├── map.js # 맵 렌더링
│ └── opendrive_parser.js # OpenDRIVE 파싱
└── README.md
CARLA Web Viewer 프로젝트 발표를 마치겠습니다
질문이 있으시면 편하게 연락 주세요