Skip to content

Commit b6cd58d

Browse files
LimHyungTaeclaude
andcommitted
Add interactive React/Vite tutorial site under web/
Hybrid approach: lightweight filters (Voxel, PassThrough) reimplemented in TypeScript for real-time before/after demos, while heavy registration (ICP, GICP) runs once via the new export_precomputed C++ target and is served as static .pcd assets. - web/: Vite + React + TS + Tailwind v4, react-three-fiber viewer, per-chapter pages with sliders and drag&drop input - scripts/export_precomputed.cpp + CMake target: dumps src/tgt/aligned PCDs for ICP/GICP into web/public/precomputed/ - .github/workflows/deploy.yml: auto-build & deploy to GitHub Pages on pushes touching web/ or materials/ - README: Live demo badge, web/ usage, precomputed regen instructions - .gitignore: scope Python lib/ pattern to root so web/src/lib/ stays tracked Site target: https://limhyungtae.github.io/pcl_tutorial/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 738ee41 commit b6cd58d

41 files changed

Lines changed: 4979 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Deploy web to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "web/**"
8+
- "materials/**"
9+
- ".github/workflows/deploy.yml"
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
pages: write
15+
id-token: write
16+
17+
concurrency:
18+
group: "pages"
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
defaults:
25+
run:
26+
working-directory: web
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- uses: actions/setup-node@v4
31+
with:
32+
node-version: "20"
33+
cache: "npm"
34+
cache-dependency-path: web/package-lock.json
35+
36+
- name: Install dependencies
37+
run: npm ci
38+
39+
- name: Build
40+
run: npm run build
41+
42+
- name: Upload Pages artifact
43+
uses: actions/upload-pages-artifact@v3
44+
with:
45+
path: web/dist
46+
47+
deploy:
48+
needs: build
49+
runs-on: ubuntu-latest
50+
environment:
51+
name: github-pages
52+
url: ${{ steps.deployment.outputs.page_url }}
53+
steps:
54+
- id: deployment
55+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ compile_commands.json
7373
*.out
7474
*.app
7575

76+
### Web (web/) ###
77+
node_modules/
78+
web/dist/
79+
*.tsbuildinfo
80+
# Auto-synced from materials/ at build time — don't commit duplicates.
81+
web/public/data/
82+
7683
### Python ###
7784
# Byte-compiled / optimized / DLL files
7885
__pycache__/
@@ -89,8 +96,8 @@ dist/
8996
downloads/
9097
eggs/
9198
.eggs/
92-
lib/
93-
lib64/
99+
/lib/
100+
/lib64/
94101
parts/
95102
sdist/
96103
var/

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,11 @@ target_link_libraries(lec11_icp ${PCL_LIBRARIES})
6868

6969
add_executable(lec12_gicp lec12_gicp.cpp)
7070
target_link_libraries(lec12_gicp ${PCL_LIBRARIES})
71+
72+
# ──────────────────────────────────────────────────────────────────────────────
73+
# Pre-compute pipeline for the React/web tutorial site (web/).
74+
# Run from the build/ directory after `make export_precomputed`:
75+
# ./export_precomputed
76+
# It dumps src/tgt/aligned PCDs into ../web/public/precomputed/.
77+
add_executable(export_precomputed scripts/export_precomputed.cpp)
78+
target_link_libraries(export_precomputed ${PCL_LIBRARIES})

README.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div align="center">
22
<h1>PCL Tutorial (한글.ver)</h1>
33

4+
<a href="https://limhyungtae.github.io/pcl_tutorial/"><img src="https://img.shields.io/badge/Live%20Demo-limhyungtae.github.io%2Fpcl__tutorial-10b981.svg" alt="Live Demo"></a>
45
<img src="https://img.shields.io/badge/Language-C%2B%2B17-blue.svg" alt="C++17">
56
<img src="https://img.shields.io/badge/PCL-%E2%89%A51.8-brightgreen.svg" alt="PCL">
67
<img src="https://img.shields.io/badge/CMake-%E2%89%A53.10-064F8C.svg" alt="CMake">
@@ -23,6 +24,41 @@ ______________________________________________________________________
2324

2425
______________________________________________________________________
2526

27+
## :sparkles: 인터랙티브 데모 (`web/`)
28+
29+
브라우저에서 슬라이더를 만져 가며 챕터별 동작을 직접 확인할 수 있습니다 — **<https://limhyungtae.github.io/pcl_tutorial/>**.
30+
31+
| 트랙 | 챕터 | 어디서 도느냐 |
32+
| ----------------- | ------------------------------------- | ------------------------------------------------------- |
33+
| **인터랙티브** | Voxelization, PassThrough | TypeScript 재구현 → 브라우저에서 실시간 |
34+
| **Pre-computed** | ICP, GICP, Normal Estimation | C++ 바이너리(`export_precomputed`)가 뽑은 PCD를 fetch |
35+
| **코드 설명** | shared_ptr, Ptr, Transformation 등 | 소스코드 + 블로그 글 링크 (개념 위주) |
36+
37+
### 로컬에서 사이트 띄우기
38+
39+
```bash
40+
cd web
41+
npm install
42+
npm run dev # http://localhost:5173/pcl_tutorial/
43+
```
44+
45+
`predev` / `prebuild` 훅이 `materials/`의 데이터(`.bin` / `.pcd` / `.ply`)를 자동으로 `web/public/data/`로 복사합니다.
46+
47+
### Pre-computed 자산 다시 뽑기 (선택)
48+
49+
`lec11_icp.cpp` / `lec12_gicp.cpp`의 출력은 미리 PCD로 저장돼 `web/public/precomputed/`에 들어 있습니다. 알고리즘이나 입력을 바꿨다면 다시 뽑아주세요:
50+
51+
```bash
52+
cd build && make export_precomputed
53+
KMP_DUPLICATE_LIB_OK=TRUE ./export_precomputed # macOS Homebrew PCL은 이 플래그 필요
54+
```
55+
56+
### 배포
57+
58+
`main` 브랜치에 `web/**` 변경이 push되면 GitHub Actions(`.github/workflows/deploy.yml`)가 자동으로 빌드 후 GitHub Pages에 배포합니다. 첫 배포 전에는 GitHub repo settings → Pages → Source를 **GitHub Actions**로 설정해 주세요.
59+
60+
______________________________________________________________________
61+
2662
## :hammer: Prerequisites & Build
2763

2864
### Prerequisites
@@ -81,10 +117,18 @@ ______________________________________________________________________
81117
```
82118
pcl_tutorial/
83119
├── CMakeLists.txt
84-
├── lec*.cpp # 챕터별 예제 코드
85-
├── auxiliary/ # 본 빌드와 무관한 보조 스니펫
86-
├── img/ # README/블로그용 이미지
87-
└── materials/ # 예제용 포인트클라우드 데이터 (.bin / .pcd / .ply)
120+
├── lec*.cpp # 챕터별 예제 코드 (PCL/C++)
121+
├── scripts/
122+
│ └── export_precomputed.cpp # ICP/GICP 결과를 PCD로 저장 (web/이 사용)
123+
├── auxiliary/ # 본 빌드와 무관한 보조 스니펫
124+
├── img/ # README/블로그용 이미지
125+
├── materials/ # 예제용 포인트클라우드 데이터 (.bin / .pcd / .ply)
126+
└── web/ # React + Vite 기반 인터랙티브 사이트
127+
├── src/
128+
│ ├── pages/ # 챕터별 페이지
129+
│ ├── components/ # 뷰어, 슬라이더, 드롭존
130+
│ └── lib/filters/ # voxel, passthrough, … (TS 재구현)
131+
└── public/precomputed/ # C++가 미리 뽑은 .pcd (커밋됨)
88132
```
89133

90134
______________________________________________________________________

scripts/export_precomputed.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// Tutorial Author: shapelim@mit.edu (임형태)
3+
//
4+
// Runs ICP/GICP/Normal pipelines once and writes their inputs/outputs as PCD
5+
// files into web/public/precomputed/ for the React tutorial site to load.
6+
7+
#include <filesystem>
8+
#include <iostream>
9+
#include <pcl/point_types.h>
10+
#include <pcl/io/pcd_io.h>
11+
#include <pcl/common/transforms.h>
12+
#include <pcl/registration/icp.h>
13+
#include <pcl/registration/gicp.h>
14+
15+
namespace fs = std::filesystem;
16+
17+
static pcl::PointCloud<pcl::PointXYZ>::Ptr load_kitti_bin(const std::string &filename) {
18+
FILE *file = fopen(filename.c_str(), "rb");
19+
if (!file) {
20+
std::cerr << "[export] failed to open " << filename << std::endl;
21+
return nullptr;
22+
}
23+
std::vector<float> buffer(1'000'000);
24+
size_t num_points =
25+
fread(reinterpret_cast<char *>(buffer.data()), sizeof(float), buffer.size(), file) / 4;
26+
fclose(file);
27+
28+
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
29+
cloud->resize(num_points);
30+
for (size_t i = 0; i < num_points; ++i) {
31+
cloud->points[i].x = buffer[i * 4];
32+
cloud->points[i].y = buffer[i * 4 + 1];
33+
cloud->points[i].z = buffer[i * 4 + 2];
34+
}
35+
return cloud;
36+
}
37+
38+
template <typename Reg>
39+
static void run_registration(const std::string &name,
40+
Reg &reg,
41+
pcl::PointCloud<pcl::PointXYZ>::Ptr src,
42+
pcl::PointCloud<pcl::PointXYZ>::Ptr tgt,
43+
const fs::path &outDir) {
44+
reg.setMaxCorrespondenceDistance(1.0);
45+
reg.setTransformationEpsilon(0.003);
46+
reg.setMaximumIterations(1000);
47+
reg.setInputSource(src);
48+
reg.setInputTarget(tgt);
49+
50+
pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(new pcl::PointCloud<pcl::PointXYZ>);
51+
reg.align(*aligned);
52+
53+
pcl::io::savePCDFileBinary((outDir / (name + "_src.pcd")).string(), *src);
54+
pcl::io::savePCDFileBinary((outDir / (name + "_tgt.pcd")).string(), *tgt);
55+
pcl::io::savePCDFileBinary((outDir / (name + "_aligned.pcd")).string(), *aligned);
56+
57+
std::cout << "[export] " << name
58+
<< " fitness=" << reg.getFitnessScore()
59+
<< " converged=" << reg.hasConverged() << std::endl;
60+
}
61+
62+
int main(int argc, char **argv) {
63+
fs::path outDir = (argc > 1) ? fs::path(argv[1])
64+
: fs::path("../web/public/precomputed");
65+
fs::create_directories(outDir);
66+
std::cout << "[export] writing to " << outDir << std::endl;
67+
68+
auto src = load_kitti_bin("./auxiliary/kitti00_000000.bin");
69+
if (!src) return 1;
70+
71+
// Synthetic target = src translated +2m along x.
72+
pcl::PointCloud<pcl::PointXYZ>::Ptr tgt(new pcl::PointCloud<pcl::PointXYZ>);
73+
Eigen::Matrix4f tf = Eigen::Matrix4f::Identity();
74+
tf(0, 3) = 2.0f;
75+
pcl::transformPointCloud(*src, *tgt, tf);
76+
77+
{
78+
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
79+
run_registration("icp", icp, src, tgt, outDir);
80+
}
81+
{
82+
pcl::GeneralizedIterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> gicp;
83+
run_registration("gicp", gicp, src, tgt, outDir);
84+
}
85+
86+
std::cout << "[export] done" << std::endl;
87+
return 0;
88+
}

web/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="ko">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='13' font-size='14'>📍</text></svg>" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>PCL Tutorial · 한글</title>
8+
<meta name="description" content="Point Cloud Library를 어떻게 잘 쓰는지에 초점을 맞춘 한글 인터랙티브 튜토리얼." />
9+
</head>
10+
<body class="bg-slate-950 text-slate-100">
11+
<div id="root"></div>
12+
<script type="module" src="/src/main.tsx"></script>
13+
</body>
14+
</html>

0 commit comments

Comments
 (0)