08. 원격 세션 구성 도구
다른 곳 (다른 애플리케이션, Unity 씬, Haply Hub 데모 등)에서 이미 실행 중인 세션을 해당 기기로 REST HTTP 호출을 전송하여 재구성할 수 있습니다. 이 튜토리얼은 웹소켓을 열지 않습니다. 다른 애플리케이션이 햅틱 피드백을 계속 생성하는 동안, 데이터베이스, 작업 공간 사전 설정 또는 편집 변환을 수정하기 위해 GET, POST 및 DELETE 요청만 수행합니다.
사용 사례
- 실행 중인 데모를 실시간으로 조정합니다. Haply Hub Orb 데모를 실행한 다음, 별도의 터미널에서 이 튜토리얼을 실행하여 기본 순서를 변경하거나, 작업 공간 사전 설정을 수정하거나, 조립 변형을 미세하게 조정해 보세요. 데모가 중단되지 않은 상태에서 Orb의 좌표 기준점이 즉시 이동합니다.
- 사용자별 작업 공간 보정. 주 컴퓨터에서 햅틱 장면을 활성화된 상태로 유지하고, 동일한 네트워크에 속한 다른 작업자가 다음 중 하나를 전송하도록 하십시오:
mount가상 작업 공간이 사용자의 바탕화면과 일치하도록 하기 위해. - 기기 선택 메뉴. 동일한 HTTP 유틸리티 함수들이 다음을 조회할 수 있습니다
GET /devices(자세히 보기 튜토리얼 00) 기기를 나열하고 대화형 메뉴를 구성하기 위해 — 기기를 선택한 다음 재구성하기 — 세션의 WebSocket을 건드리지 않고도 가능합니다. 이 튜토리얼에서는 다음과 같이 묻습니다./sessions그리고 고정*inverse/0코드 내에서, 하지만 이 선택을 다음으로 대체하여/devices단순히 국부적인 수정만 필요합니다. - 스크립트를 통한 구성. 세션이 녹화를 시작하기 전에 필요한 사전 단계(데이터베이스 정의, 사전 설정 및 마운트)를 자동화하여, 각 클라이언트에 구성을 일일이 적용할 필요가 없습니다.
선행 조건
튜토리얼 08에서는 이미 실행 중인 세션을 재구성합니다. 이를 위해서는 활성 상태인 햅틱 세션이 하나 필요합니다. 다른 튜토리얼, Unity 씬, 또는 Haply Hub의 데모 등 무엇이든 상관없습니다.
Haply Hub를 열고 Orb 데모를 실행한 다음, 해당 데모를 직접 선택하세요:
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"
Orb 씬은 디바이스의 작업 공간에 구체를 표시합니다. 기본 설정이나 사전 설정을 반복해서 탐색하거나, 튜토리얼 08을 통해 편집 변형을 약간 조정하면 Orb의 좌표 기준점이 실시간으로 시각적으로 이동합니다.
이용률
# Pick a session interactively (lists every session the service knows)
./08-haply-inverse-http-remote-config
python 08-haply-inverse-http-remote-config.py
# Target the Haply Hub Orb demo directly
./08-haply-inverse-http-remote-config --session co.haply.hub::demo-orb
python 08-haply-inverse-http-remote-config.py --session "co.haply.hub::demo-orb"
# Target one directly by selector
./08-haply-inverse-http-remote-config --session :my_profile:0
python 08-haply-inverse-http-remote-config.py --session "#42"
# Or by a wildcard profile pattern (first match) — handy when the exact profile is unknown
./08-haply-inverse-http-remote-config --session "co.haply.hub::*:0"
# Pick a specific device on the session (default is *inverse/0)
./08-haply-inverse-http-remote-config --device 'inverse3/#A12' # by exact id
./08-haply-inverse-http-remote-config --device '*inverse:left/0' # first left-handed inverse
./08-haply-inverse-http-remote-config --device '*inverse:right/0' # first right-handed inverse
./08-haply-inverse-http-remote-config --device '*verse_grip/0' # first grip-family device
시작 시 튜토리얼은 세션의 기본 설정, 사전 설정 및 현재 구성을 표시한 다음 키 입력을 기다립니다. 키를 한 번 누를 때마다 하나의 REST 호출이 전송됩니다.
프로필 이름이 없는 세션은 실행할 때마다 변경되는 숫자 식별자로만 식별할 수 있습니다. 다음을 호출하십시오. session.configure.profile.name 메인 앱에서 첫 번째 메시지가 도착하는 즉시: 그러면 다음과 같은 안정적인 선택기를 재사용할 수 있습니다. --session :my_profile:0 매 실행 시마다. 참조 세션 — 프로필 이름.
키보드 단축키
- 파이썬
- C++
| 그 말이 맞네요 | 액션 |
|---|---|
B | 기본 순열 살펴보기 |
P | 작업 공간 사전 설정 살펴보기 |
W / E / R | 편집 모드 선택 — 위치 (mm) / 회전 (°) / 확대/축소 (%) |
← / → | 현재 모드에서 −X / +X만큼 앞으로 이동 |
↑ / ↓ | 현재 모드에서 +Y / −Y 방향으로 이동 |
Page Up / Page Down | 현재 모드에서 +Z / −Z 방향으로 이동 |
= / - | 세 축 모두에 대해 동시에 균일한 스케일링 ± (항상 사용 가능) |
Delete | DELETE 기본 설정 + 사전 설정 + 구성 — 기기의 기본값으로 복원하기 |
H | 도움말 보기 |
Esc | 포기자 (Ctrl+C (이것도 작동합니다) |
줄마다 — 입력한 후 Enter 키를 누르세요.
| 주문 | 액션 |
|---|---|
b | 기본 순열 살펴보기 |
p | 작업 공간 사전 설정 살펴보기 |
w / e / r | 편집 모드 선택 — 위치 (mm) / 회전 (°) / 확대/축소 (%) |
x+[N] … z-[N] | 현재의 주요 방향을 발전시키기 N 능동태의 자연스러운 단위에서 (x+ seul = 5 (기본값) |
sx+[N] … sz-[N] | 한 축에 대한 비균일 확대/축소 단축키(백분율 단위), 항상 사용 가능 |
u+[N] / u-[N] | 세 축 모두에 대해 ± N %의 균일한 스케일링 |
reset | DELETE 베이스 + 사전 설정 + 편집 |
h | 도움말 보기 |
Ctrl+C (또는 Ctrl+D / EOF)를 눌러 종료하십시오.
HTTP 동사 — GET, POST, DELETE
이 튜토리얼에서는 세 가지 HTTP 동사만을 사용합니다. 각 호출은표준 JSON 쉘 ({"ok": true, "data": {...}} 성공할 경우, {"ok": false, "error": "..."} (실패 시) 및 다음 세 가지 상태 코드 중 하나: 200 성공, 400 구문 오류가 있는 쿼리, 404 선택기에 대한 결과가 없습니다.
| 동사 | 역할 | 사용된 경로 |
|---|---|---|
GET | 현재 상태 확인 — 세션 목록, 특정 세션 검색, 현재 구성 값 | /sessions, /sessions/<selector>, /<device_selector>/config/{basis,preset,mount}?session=... |
POST | 구성 값 교체 — 본문은 JSON 형식입니다 | /<device_selector>/config/{basis,preset,mount}?session=... |
DELETE | 설정 값을 기기의 기본값으로 복원하기 | /<device_selector>/config/{basis,preset,mount}?session=... |
HTTP 유틸리티 함수
세 개의 동사 주위에 얇은 캡슐화 층을 씌워, 튜토리얼의 나머지 부분이 마치 비즈니스 로직처럼 읽히도록 합니다:
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
파이썬 활용 requests.Session() HTTP 키프-얼라이브(Keep-Alive)의 경우(요청당 지연 시간을 약 50ms에서 약 5ms로 단축):
http = requests.Session()
def api_get(path):
r = http.get(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None
def api_post(path, body):
r = http.post(f"{BASE_URL}{path}", json=body, timeout=3)
return r.json() if r.status_code == 200 else None
def api_delete(path):
r = http.delete(f"{BASE_URL}{path}", timeout=3)
return r.json() if r.status_code == 200 else None
def session_url(endpoint):
return f"{endpoint}?session={session_selector}"
libhv 폭로하다 requests::get / requests::post / requests::Delete (avec un D 대문자 — delete (C++의 키워드입니다). POST의 경우, 다음을 정의하기 위해 수동으로 요청을 구성해야 합니다. Content-Type: application/json :
static std::string session_url(const std::string &endpoint) {
return BASE_URL + endpoint + "?session=" + session_selector;
}
static json http_get(const std::string &url) {
auto resp = requests::get(url.c_str());
if (!resp || resp->status_code != 200) return {};
try { return json::parse(resp->body); } catch (...) { return {}; }
}
static bool http_post_json(const std::string &url, const json &body) {
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_POST;
req->url = url;
req->content_type = APPLICATION_JSON;
req->body = body.dump();
auto resp = requests::request(req);
return resp && resp->status_code == 200;
}
static bool http_delete(const std::string &url) {
auto resp = requests::Delete(url.c_str());
return resp && resp->status_code == 200;
}
응답 본문에는 항상 봉투가 포함되어 있습니다 {"ok", "data": T}. 각 타입 지정된 GET 요청은 하나의 템플릿으로 묶이며, 동일한 패턴이 HttpRequest POST 요청에 다음을 추가하여 glz::write_json :
template <typename T> struct envelope { bool ok{}; T data{}; };
template <typename Payload>
static std::optional<Payload> http_get_envelope(const std::string &url) {
auto resp = requests::get(url.c_str());
if (!resp || resp->status_code != 200) return std::nullopt;
envelope<Payload> env{};
if (glz::read<glz_settings>(env, resp->body)) return std::nullopt;
return std::move(env.data);
}
template <typename Body>
static bool http_post_json(const std::string &url, const Body &body) {
std::string buf;
if (glz::write_json(body, buf)) return false;
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_POST;
req->url = url;
req->content_type = APPLICATION_JSON;
req->body = std::move(buf);
auto resp = requests::request(req);
return resp && resp->status_code == 200;
}
static bool http_delete(const std::string &url) {
auto resp = requests::Delete(url.c_str());
return resp && resp->status_code == 200;
}
세션 살펴보기 — GET /sessions
행동은 다음 요인에 따라 달라집니다. --session :
--session SELECTORfourni → 단 하나GET /sessions/<SELECTOR>.200→ 그것을 사용하다;404→ 오류를 알리고 중단합니다.- 선택 사항 없음 →
GET /sessions(목록) → 프로필 이름이 포함된 세션 표시 → 인덱스 요청 → 최종 선택기 생성 (우선순위::profile:0사용 가능한 경우; 그렇지 않으면 다음을 사용하십시오#id).
SELECTOR 에서 정의된 모든 형식을 허용합니다 선택기 — 세션 선택기 : :profile:instance, #id, :-1, :0, 간단한 프로필 이름, 또는 모티프 프로필 이름에 와일드카드 포함 comme co.haply.hub::*:0. 이 튜토리얼은 채널을 있는 그대로 전달하며, 이를 분석하는 것은 서비스의 역할입니다.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def discover_session(session_arg):
global session_selector
if session_arg:
# Direct lookup (e.g. ":my_profile:0", "#42", ":-1")
if api_get(f"/sessions/{session_arg}") is None:
return False
session_selector = session_arg
return True
# Otherwise: list and pick
data = api_get("/sessions")
sessions = data.get("data", {}).get("sessions", [])
for i, s in enumerate(sessions):
name = s.get("config", {}).get("profile", {}).get("name", "default")
print(f" [{i}] session #{s['session_id']} profile={name}")
picked = sessions[int(input("Pick session index: "))]
name = picked.get("config", {}).get("profile", {}).get("name", "")
# Prefer the profile selector — it survives restarts; id doesn't
session_selector = (f":{name}:0" if name and name != "default"
else f"#{picked['session_id']}")
return True
static bool discover_session(const std::string &session_arg) {
if (!session_arg.empty()) {
const auto data = http_get(BASE_URL + "/sessions/" + session_arg);
if (data.is_null()) return false;
session_selector = session_arg;
return true;
}
const auto data = http_get(BASE_URL + "/sessions");
const json &list = data["data"]["sessions"];
for (size_t i = 0; i < list.size(); ++i) {
const int sid = list[i].value("session_id", 0);
std::string prof = "default";
if (list[i].contains("config") && list[i]["config"].contains("profile"))
prof = list[i]["config"]["profile"].value("name", std::string{"default"});
printf(" [%zu] session #%d profile=%s\n", i, sid, prof.c_str());
}
std::string line; std::getline(std::cin, line);
const json &picked = list[std::stoi(line)];
std::string prof;
if (picked.contains("config") && picked["config"].contains("profile"))
prof = picked["config"]["profile"].value("name", std::string{});
session_selector = (!prof.empty() && prof != "default")
? ":" + prof + ":0"
: "#" + std::to_string(picked.value("session_id", 0));
return true;
}
구조체(struct)를 사용하여 응답의 형태를 모델링합니다. Glaze는 리플렉션을 통해 이를 자동으로 생성합니다:
struct profile_info { std::string name; };
struct session_config{ std::optional<profile_info> profile; };
struct session_info { int session_id{}; std::optional<session_config> config; };
struct sessions_list { int session_count{}; std::vector<session_info> sessions; };
static bool discover_session(const std::string &session_arg) {
if (!session_arg.empty()) {
auto resp = requests::get((BASE_URL + "/sessions/" + session_arg).c_str());
if (!resp || resp->status_code != 200) return false;
session_selector = session_arg;
return true;
}
auto list = http_get_envelope<sessions_list>(BASE_URL + "/sessions");
if (!list || list->sessions.empty()) return false;
for (size_t i = 0; i < list->sessions.size(); ++i) {
const auto &s = list->sessions[i];
std::string prof = "default";
if (s.config && s.config->profile) prof = s.config->profile->name;
printf(" [%zu] session #%d profile=%s\n", i, s.session_id, prof.c_str());
}
std::string line; std::getline(std::cin, line);
const auto &picked = list->sessions[std::atoi(line.c_str())];
std::string prof;
if (picked.config && picked.config->profile) prof = picked.config->profile->name;
session_selector = (!prof.empty() && prof != "default")
? ":" + prof + ":0"
: "#" + std::to_string(picked.session_id);
return true;
}
기기 선택기 — *inverse/0
각 구성 호출은 특정 장치를 대상으로 합니다. 이 튜토리얼에서는 제품군 + 인덱스 형식의 와일드카드 선택자를 사용합니다:
/*inverse/0/config/<key>
*inverseInverse 제품군의 모든 기기에 해당합니다 (inverse3,inverse3x,minverse) — 이 튜토리얼은 구체적인 모델에 관계없이 별다른 수정 없이 바로 사용할 수 있습니다.0이 계열에서 0부터 시작하는 인덱스입니다. — 이 튜토리얼에서는 첫 번째 Inverse에 대해서만 다룹니다.
대상을 변경하려면 코드 한 군데만 수정하면 됩니다: 다음으로 변경하세요 --device (그 길은 이미 닦여 있다) :
/verse_grip/0/config/basis?session=... # --device verse_grip/0
/*verse_grip/0/config/basis?session=... # --device '*verse_grip/0' — first grip, any kind
/inverse3/A14/config/mount?session=... # --device 'inverse3/#A14'
/*inverse:left/0/config/basis?session=... # --device '*inverse:left/0' — first left-handed inverse
/*inverse:right/0/config/basis?session=... # --device '*inverse:right/0' — first right-handed inverse
수업 :left / :right 적용됩니다 avant 색인화 — *inverse:left/0 “0차 역수”를 의미한다 고셰병 », “0차 역수, 바라건대 왼손잡이”가 아니라. 참조 선택자 — 클래스 필터는 인덱싱 전에 처리됩니다.
이 튜토리얼은 GET …/config/basis 시작 시. 세션에
해당하는 기기가 하나도 없는 경우 --device (예: --device '*inverse:left/0' (오직 오른손잡이용
구성으로만 제공됨), 이에 대한 응답은 다음과 같습니다. 404 no device matches selector '<sel>' ; 튜토리얼에는 다음과 같이 표시됩니다 Device '<sel>' not found in session '<sel>'.
그리고 다음 상태로 끝납니다 1. 더 이상 사용되지 않는 기본값을 사용하는 키보드 루프는 실행되지 않습니다.
확인하기 선택기 — 기기 선택기 전체 문법을 보려면. 하드코딩 대신 기기 선택 메뉴를 만들려면, 기기를 다음과 같이 나열하십시오. GET /devices?session=<selector> (튜토리얼 00)를 추가하고 다음을 통합하십시오. device_id 구성 경로에서 선택합니다.
구성 POST — 기본, 사전 설정, 마운트
세 가지 구성 키, 동일한 요청 구조, 서로 다른 본문 스키마. 각 POST 요청은 200 그 결과 값을 다음의 data, 또는 404 세션 선택기나 장치 선택기에 해당하는 항목이 없는 경우.
기초
POST /*inverse/0/config/basis?session=:my_profile:0
Content-Type: application/json
{"permutation": "XZY"}
답변: {"ok": true, "data": {"permutation": "XZY"}}
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def post_basis():
perm, _ = BASIS_OPTIONS[basis_index]
api_post(session_url("/inverse3/0/config/basis"), {"permutation": perm})
static void post_basis() {
http_post_json(session_url("/inverse3/0/config/basis"),
{{"permutation", BASIS_OPTIONS[basis_index].first}});
}
struct basis_body { std::string permutation; };
static void post_basis() {
http_post_json(session_url("/inverse3/0/config/basis"),
basis_body{BASIS_OPTIONS[basis_index].first});
}
사전 설정
POST /*inverse/0/config/preset?session=:my_profile:0
Content-Type: application/json
{"preset": "arm_front_centered"}
답변: {"ok": true, "data": {"preset": "arm_front_centered"}}
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def post_preset():
preset = PRESET_OPTIONS[preset_index]
api_post(session_url("/inverse3/0/config/preset"), {"preset": preset})
static void post_preset() {
http_post_json(session_url("/inverse3/0/config/preset"),
{{"preset", PRESET_OPTIONS[preset_index]}});
}
struct preset_body { std::string preset; };
static void post_preset() {
http_post_json(session_url("/inverse3/0/config/preset"),
preset_body{PRESET_OPTIONS[preset_index]});
}
마운트
POST /*inverse/0/config/mount?session=:my_profile:0
Content-Type: application/json
{
"transform": {
"position": {"x": 0.02, "y": 0.0, "z": 0.0},
"rotation": {"w": 0.966, "x": 0.0, "y": 0.259, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
}
}
답변: {"ok": true, "data": {"transform": { ... }}} — 정규화 후의 실제 변환 결과를 반환합니다.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def post_mount():
body = {
"transform": {
"position": {"x": mount_pos[0], "y": mount_pos[1], "z": mount_pos[2]},
"rotation": quat_from_euler_deg(*mount_rot),
"scale": {"x": mount_scale[0], "y": mount_scale[1], "z": mount_scale[2]},
}
}
api_post(session_url("/inverse3/0/config/mount"), body)
static void post_mount() {
http_post_json(session_url("/inverse3/0/config/mount"), {
{"transform", {
{"position", {{"x", mount_pos[0]}, {"y", mount_pos[1]}, {"z", mount_pos[2]}}},
{"rotation", quat_from_euler_deg(mount_rot[0], mount_rot[1], mount_rot[2])},
{"scale", {{"x", mount_scale[0]}, {"y", mount_scale[1]}, {"z", mount_scale[2]}}},
}},
});
}
struct vec3 { float x{}, y{}, z{}; };
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct transform_t { vec3 position{}; quat rotation{}; vec3 scale{1.0f, 1.0f, 1.0f}; };
struct mount_body { transform_t transform; };
static void post_mount() {
http_post_json(session_url("/inverse3/0/config/mount"), mount_body{
transform_t{
.position = vec3{mount_pos[0], mount_pos[1], mount_pos[2]},
.rotation = quat_from_euler_deg(mount_rot[0], mount_rot[1], mount_rot[2]),
.scale = vec3{mount_scale[0], mount_scale[1], mount_scale[2]},
}});
}
mount et preset 서로 배타적이다두 키 중 하나에 대한 POST 요청이 전송되면, 해당 장치에서 다른 키는 삭제됩니다. 이 튜토리얼에서는 이를 명시적으로 다루지 않습니다. 각 POST 요청은 독립적으로 처리되며, 서버 측에서 충돌을 해결합니다. WebSocket 측에서도 동일한 규칙이 적용되는 내용은 튜토리얼 07을 참조하십시오.
DELETE 재설정 — 세 번의 호출
reset 구성 키별로 DELETE를 트리거합니다. 각각은 200 다음에서 기본값이 복원된 data.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def reset_all():
api_delete(session_url("/inverse3/0/config/basis"))
api_delete(session_url("/inverse3/0/config/preset"))
api_delete(session_url("/inverse3/0/config/mount"))
static void reset_all() {
http_delete(session_url("/inverse3/0/config/basis"));
http_delete(session_url("/inverse3/0/config/preset"));
http_delete(session_url("/inverse3/0/config/mount"));
}
static void reset_all() {
http_delete(session_url("/inverse3/0/config/basis"));
http_delete(session_url("/inverse3/0/config/preset"));
http_delete(session_url("/inverse3/0/config/mount"));
}
마운팅 회전 구성
transform.rotation 네트워크를 통해 전송되는 유니터리 쿼터니언입니다. 이 튜토리얼은 회전을 Z-Y-X 내재 오일러 3원수(X축을 중심으로 한 피치, Z축을 중심으로 한 요, Y축을 중심으로 한 롤 — 모두 도 단위) 형태로 저장하고, POST 요청이 있을 때마다 쿼터니언을 재구성합니다.
- 파이썬
- C++ (nlohmann)
- C++ (Glaze)
def quat_from_euler_deg(pitch_x, yaw_z, roll_y):
"""Hamilton quaternion for q = q_z * q_y * q_x (apply X, then Y, then Z)."""
hx, hy, hz = (math.radians(a) * 0.5 for a in (pitch_x, roll_y, yaw_z))
cx, sx = math.cos(hx), math.sin(hx)
cy, sy = math.cos(hy), math.sin(hy)
cz, sz = math.cos(hz), math.sin(hz)
return {
"w": cz*cy*cx + sz*sy*sx,
"x": cz*cy*sx - sz*sy*cx,
"y": cz*sy*cx + sz*cy*sx,
"z": sz*cy*cx - cz*sy*sx,
}
static json quat_from_euler_deg(float pitch_x, float yaw_z, float roll_y) {
constexpr float deg_to_rad = 3.14159265358979323846f / 180.0f;
const float hx = pitch_x * 0.5f * deg_to_rad;
const float hy = roll_y * 0.5f * deg_to_rad;
const float hz = yaw_z * 0.5f * deg_to_rad;
const float cx = std::cos(hx), sx = std::sin(hx);
const float cy = std::cos(hy), sy = std::sin(hy);
const float cz = std::cos(hz), sz = std::sin(hz);
return {
{"w", cz * cy * cx + sz * sy * sx},
{"x", cz * cy * sx - sz * sy * cx},
{"y", cz * sy * cx + sz * cy * sx},
{"z", sz * cy * cx - cz * sy * sx},
};
}
static quat quat_from_euler_deg(float pitch_x, float yaw_z, float roll_y) {
constexpr float deg_to_rad = 3.14159265358979323846f / 180.0f;
const float hx = pitch_x * 0.5f * deg_to_rad;
const float hy = roll_y * 0.5f * deg_to_rad;
const float hz = yaw_z * 0.5f * deg_to_rad;
const float cx = std::cos(hx), sx = std::sin(hx);
const float cy = std::cos(hy), sy = std::sin(hy);
const float cz = std::cos(hz), sz = std::sin(hz);
return quat{
.w = cz * cy * cx + sz * sy * sx,
.x = cz * cy * sx - sz * sy * cx,
.y = cz * sy * cx + sz * cy * sx,
.z = sz * cy * cx - cz * sy * sx,
};
}
오른손 좌표계를 사용하는, 스칼라 성분이 먼저 오는 유니터리 해밀턴 쿼터니언 (w) — 나머지 서비스와 동일한 약관이 적용되며, 자세한 내용은 다음을 참조하십시오. quaternion. 구성 순서는 다음과 같습니다. Z-Y-X 내재적 (q = q_z * q_y * q_x) : 먼저 X축을 중심으로 피치 각도를 적용하고, 그 다음 Y축을 중심으로 롤 각도를, 마지막으로 Z축을 중심으로 요 각도를 적용합니다.
이 튜토리얼에서는 각 상태 행에 오일러 3원수 옆에 파생된 쿼터니언을 표시하므로, 장치가 회전하기 전에 합성 연산을 확인할 수 있습니다. 로컬 오일러 상태는 (0, 0, 0), 세션에 이미 어떤 상태가 있든 상관없이 — 첫 번째 mount POST 기존 값을 대체합니다.
입력 모델 (간략)
HTTP 통합이 핵심이며, 키보드 사용 편의성은 부차적인 문제입니다. 의도적으로 선택한 두 가지 단축키:
- 파이썬 패키지 활용하기
keyboard— 다양한 플랫폼에서 작동하며, 길게 누른 키의 반복 입력을 기본적으로 지원합니다. 방향키,Page Up/Page Downet=/-고정되어 있는 동안 장착 축을 이동시키고;BetP상승 에지 시 basis 및 preset 값을 순차적으로 훑어봅니다. - C++ 활용하다
std::getline(std::cin, ...)그리고 간결한 토큰 문법 (x+20,sx-5,u+10) — 연속적인 조절에는 인체공학적으로 다소 떨어지지만, 별도의 도구를 사용하지 않고도 휴대할 수 있다.#ifdef콘솔용 API의 경우 각 플랫폼에 맞는 것들입니다.
출처
튜토리얼 08도 SDK와 함께 로컬에 설치되어 있습니다 — 다음을 참조하세요 tutorials/08-haply-inverse-http-remote-config/ 서비스 설치 디렉터리에서.
참조: 세션 — 원격 제어 · 선택기 · 장치 구성 · 베이스 순서 변경 · 마운트 및 작업 공간 · JSON 규칙 · 튜토리얼 00 — 장치 목록 · 튜토리얼 07 — Basis 및 마운트 (WebSocket 버전)