File size: 3,412 Bytes
4c75661
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""src/vkgs/src/sensors.hpp (๋ ˆ์ด์บ์ŠคํŠธ ๋ผ์ด๋‹คยท๋ ˆ์ด๋”) ๋ฅผ g++ ๋กœ ์ปดํŒŒ์ผํ•ด ์‹œํ—˜ํ•œ๋‹ค. ์ฐฝ์„ ๋„์šฐ์ง€ ์•Š๋Š”๋‹ค.
๋ณต์…€ ์›”๋“œ๋Š” nusim.sim.voxels ๋กœ ๋งŒ๋“ ๋‹ค โ€” ํŒŒ์ผ ํ˜•์‹์„ PythonยทC++ ์–‘์ชฝ์ด ๊ฐ™๊ฒŒ ์ฝ๋Š”์ง€๋„ ๊ฐ™์ด ๋ณธ๋‹ค."""

import os
import shutil
import subprocess
from pathlib import Path

import numpy as np
import pytest

from nusim.sim.voxels import read_voxels, write_grid

ROOT = Path(__file__).resolve().parents[1]
CXX = os.environ.get("CXX") or "g++"


def _world(path: Path):
    g = np.mgrid[-30:30:0.1, -30:30:0.1].reshape(2, -1).T
    ground = np.c_[g, np.full(len(g), 0.1)]  # ์ง€๋ฉด ์นธ z 0 ~ 0.2
    yz = np.mgrid[-30:30:0.1, 0:6:0.1].reshape(2, -1).T
    wall = np.c_[np.full(len(yz), 20.1), yz]  # x = 20 ~ 20.2
    pts = np.vstack([ground, ground, wall, wall])  # ์นธ๋งˆ๋‹ค 2์  ์ด์ƒ
    return write_grid(path, pts, np.array([-30.0, -30.0, -2.0]), np.array([30.0, 30.0, 10.0]))


def test_voxels_roundtrip(tmp_path):
    _world(tmp_path / "w.vox")
    occ, lo, res = read_voxels(tmp_path / "w.vox")
    assert not occ[int((5 - lo[0]) / res), int((0 - lo[1]) / res), int((0.1 - lo[2]) / res)]  # ์ง€๋ฉด์€ ๋†’์ด ์ง€๋„๊ฐ€ ๋งก๋Š”๋‹ค (๋ณต์…€ ์•„๋‹˜)
    assert occ[int((20.1 - lo[0]) / res), int((0 - lo[1]) / res), int((3 - lo[2]) / res)]  # ๋ฒฝ
    assert not occ[int((5 - lo[0]) / res), int((0 - lo[1]) / res), int((2 - lo[2]) / res)]  # ๋นˆ ๊ณต๊ฐ„


@pytest.mark.skipif(shutil.which(CXX) is None, reason="g++ ์—†์Œ")
def test_sensors(tmp_path):
    _world(tmp_path / "w.vox")
    exe = tmp_path / "t"
    subprocess.run([CXX, "-std=c++20", "-O2", "-pthread", "-I", str(ROOT / "src/vkgs/src"),
                    str(ROOT / "tests/cpp/test_sensors.cpp"), "-o", str(exe)], check=True)
    r = subprocess.run([str(exe), str(tmp_path / "w.vox")], capture_output=True, text=True)
    assert r.returncode == 0, r.stdout + r.stderr


@pytest.mark.skipif(not (ROOT / "build" / "vkgs_voxcarve").exists(), reason="build/vkgs_voxcarve ์—†์Œ (cmake --build build)")
def test_free_space_carving(tmp_path):
    """๋นˆ ๊ณต๊ฐ„ ๊นŽ๊ธฐ: ํ•œ sweep ์—๋งŒ ์ฐํžŒ '์ง€๋‚˜๊ฐ„ ์ฐจ ์กฐ๊ฐ' ์€ ๋‹ค๋ฅธ sweep ์˜ ๋น”์ด ๊ทธ ์ž๋ฆฌ๋ฅผ ์ง€๋‚˜๊ฐ€๋ฏ€๋กœ ์ง€์›Œ์ง€๊ณ ,
    ๋งค sweep ์ฐํžˆ๋Š” ๋ฒฝ์€ ๋‚จ๋Š”๋‹ค (nusim.sim.voxels.carve, src/vkgs/src/voxcarve.cpp)."""
    rng = np.random.default_rng(0)
    org, ends, sweep = [], [], []
    for s in range(20):  # ์ž์ฐจ๊ฐ€ x 0 โ†’ 19 m ๋กœ ๊ฐ€๋ฉฐ 20 sweep
        o = np.array([float(s), 0.0, 1.8])
        org.append(o)
        # ๋ฒฝ (x = 30, ๋†’์ด 0.5~3 m): ๋งค sweep
        wall = np.c_[np.full(400, 30.1), rng.uniform(-3, 3, 400), rng.uniform(0.5, 3, 400)]
        pts = [wall]
        if s == 3:  # ์ง€๋‚˜๊ฐ„ ์ฐจ ์กฐ๊ฐ (x 15, y 0, ๋†’์ด 0.5 m) โ€” ํ•œ sweep ์—๋งŒ
            pts.append(np.c_[rng.normal(15, 0.03, 50), rng.normal(0, 0.03, 50), rng.normal(0.5, 0.03, 50)])
        p = np.vstack(pts)
        ends.append(p)
        sweep.append(np.full(len(p), s, np.int32))
    pts = np.vstack(ends)
    lo, hi = np.array([-5.0, -10.0, -2.0]), np.array([40.0, 10.0, 8.0])
    write_grid(tmp_path / "w.vox", pts, lo, hi, rays=(np.array(org), np.concatenate(sweep)))
    occ, lo2, res = read_voxels(tmp_path / "w.vox")
    at = lambda x, y, z: occ[int((x - lo2[0]) / res), int((y - lo2[1]) / res), int((z - lo2[2]) / res)]
    assert not at(15.0, 0.0, 0.5)  # ๊นŽ์˜€๋‹ค
    assert at(30.1, 0.0, 1.5)  # ๋ฒฝ์€ ๋‚จ๋Š”๋‹ค