04. Hallo Boden
Dies ist der erste taktile Effekt. Er erzeugt einen virtuellen horizontalen Boden, der den Cursor zurückdrückt, wenn er gedrückt wird. Die Kraft wird durch eine einfache Straf-Feder (stiffness × penetration_depth), undset_cursor_force wird über die Z-Richtung ausgeübt.
Was Sie in diesem Tutorial lernen werden:
set_cursor_force… verwenden, um die Kraft der Straf-Feder auszuübencursor_positionauslesen und die Kraft in Echtzeit berechnen- (Python)Arbeitsbereichsvoreinstellungen(
arm_front_centered) einstellen, sodass der Ursprung in der Mitte des Arbeitsbereichs liegt - (Python)
keyboardMithilfe des Pakets lassen sich die Bodenhöhe und die Federkonstante interaktiv über die Tastatur anpassen.
Workflow
ws://localhost:10001Dafür wird ein WebSocket geöffnet und auf den ersten Status-Frame gewartet.- Im ersten Frame,Sitzungsprofilregistrieren. Die Python-Version bietet darüber hinaus
configure.preset: arm_front_centeredSenden Sie die Daten und stellen Sie sicher, dass der Ursprung in der Mitte des Arbeitsbereichs liegt. Die C++-Version verwendet die auf dem Gerät bereits aktivierten Einstellungen unverändert. - In jedem Einzelbild,
cursor_position.zlesen undforce_z = max(0, (floor_pos - z) * stiffness)berechnen undset_cursor_forceAls Befehl senden. - Bei den folgenden Ticks werden ausschließlich Befehle zum Einstellen der Kraft gesendet. Das Sitzungsprofil dient nur für einen einmaligen Handshake.
- (Python) Bei jedem Tick werden auch die Pfeiltasten der Tastatur abgefragt, und
floor_posundstiffnesswird in Echtzeit aktualisiert.
Parameter
| Name | Standard | Verwendungszweck |
|---|---|---|
floor_pos | 0.10 m | Z-Koordinate der Ebene des virtuellen Bodens |
stiffness | 1000 k. A. | Federkonstante (1 N bei 1 mm Einfederung) |
PRINT_EVERY_MS | 100–200 | Telemetrie-Abtastintervall |
| Name des Sitzungsprofils | co.haply.inverse.tutorials:hello-floor | Haply Identifizieren Sie diese Simulation im Hub. |
Die Python-Version ist keyboard Verwenden Sie das Paket (unter Linux sind Administratorrechte erforderlich):
↑/↓— Die Liegefläche anheben / absenken←/→— Die Federkonstante verringern / erhöhenR— Auf die Standardeinstellungen zurücksetzen
Bei „Service Tick“werden die Kräfte addiert. Bevor sie an das Gerät gesendet werden, werden die Kräfte aus mehreren Quellen summiert. Ein solches Tutorial kann mit anderen Kraftquellen koexistieren, ohne dass eine der beiden Seiten blockiert wird.
Feld „Lesezustand“
data.inverse3[i].state Aus dem folgenden Text geht hervor:
cursor_position.z—vec3。Wird zur Berechnung der Einschnitttiefe verwendetcurrent_cursor_force— Wird für Telemetriezwecke gemeldet
Senden / Empfangen
Bei jedem Tick wird die Z-Koordinate des Cursors ausgelesen, undforce_z = max(0, (floor_pos - z) * stiffness) berechnen undset_cursor_force wird gesendet. Die erste gesendete Nachricht enthält (in allen Versionen) auch das Sitzungsprofil; in der Python-Version configure.preset: arm_front_centered ... sind ebenfalls enthalten.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Es handelt sich um eine einzelne asynchrone Schleife. Für den Handshake im ersten Frame werden das Profil und configure.preset umfasst,floor_pos = 0.1 sodass sie an den Mittelpunkten des Arbeitsbereichs ausgerichtet sind.
async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)
if first_message:
first_message = False
device_id = data["inverse3"][0]["device_id"]
# Handshake: profile + preset (one-shot)
request_msg = {
"session": {"configure": {"profile": {"name": "co.haply.inverse.tutorials:hello-floor"}}},
"inverse3": [{
"device_id": device_id,
"configure": {"preset": {"preset": "arm_front_centered"}}
}]
}
else:
# Per tick: compute force along Z, send set_cursor_force
z = data["inverse3"][0]["state"]["cursor_position"]["z"]
force_z = 0.0 if z > floor_pos else (floor_pos - z) * stiffness
request_msg = {
"inverse3": [{
"device_id": device_id,
"commands": {"set_cursor_force":
{"vector": {"x": 0.0, "y": 0.0, "z": force_z}}}
}]
}
await websocket.send(json.dumps(request_msg))
Dies ist das Callback-Modell von libhv. „onmessage“ wird auf dem WebSocket-E/A-Thread ausgeführt, während der Hauptthread auf den Druck der ENTER-Taste wartet (blockiert). Die C++-Variante implementiert einen minimalen Handshake (nur Sitzungsprofil, keine Voreinstellungen).
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
// If no Inverse3 yet, ask the service to re-send the full state
if (!data.contains("inverse3") || data["inverse3"].empty()) {
ws.send(R"({"session":{"force_render_full_state":{}}})");
return;
}
json request = {};
if (first_message) {
first_message = false;
request["session"] = {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:hello-floor"}}}}}};
}
request["inverse3"] = json::array();
for (auto &el : data["inverse3"].items()) {
const float z = el.value()["state"]["cursor_position"]["z"].get<float>();
const float force_z = z > floor_pos ? 0.0f : (floor_pos - z) * stiffness;
request["inverse3"].push_back({
{"device_id", el.value()["device_id"]},
{"commands", {{"set_cursor_force",
{{"vector", {{"x", 0.0}, {"y", 0.0}, {"z", force_z}}}}}}},
});
}
ws.send(request.dump());
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Das Callback-Modell von libhv ist identisch. Nur der Hauptteil hat sich geändert. Sowohl für den Status als auch für die Befehle werden typisierte Strukturen verwendet.std::optional<session_cmd> Es wird ein einmaliges Profil gespeichert; ist dieses nicht festgelegt, lässt Glaze es aus dem serialisierten JSON weg.
// Struct models
struct vec3 { float x{}, y{}, z{}; };
struct inverse_state { vec3 cursor_position{}, current_cursor_force{}; };
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message { std::vector<inverse_device> inverse3; };
struct set_cursor_force_cmd { vec3 vector; };
struct commands_message {
std::optional<session_cmd> session;
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
if (data.inverse3.empty()) {
ws.send(R"({"session":{"force_render_full_state":{}}})");
return;
}
commands_message out_cmds{};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = hello-floor */ };
}
for (const auto &dev : data.inverse3) {
const float z = dev.state.cursor_position.z;
const float force_z = z > floor_pos ? 0.0f : (floor_pos - z) * stiffness;
device_commands dc{ .device_id = dev.device_id };
dc.commands.set_cursor_force = set_cursor_force_cmd{{0.0f, 0.0f, force_z}};
out_cmds.inverse3.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Auch Tutorial 04 wurde zusammen mit dem SDK lokal installiert. Im Installationsverzeichnis des Dienstes unter tutorials/04-haply-inverse-hello-floor/ Siehe dazu.
Quelle: Python · C++ · C++ Glaze
Siehe auch: Steuerbefehle (set_cursor_force) · Halterung und Arbeitsbereich (Voreinstellung) · Typ (vec3) · Sitzung · Tutorial 07 (Basis & Halterung)