mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-29 04:02:56 +01:00
Remove wall spots
Wall spots were markers for where SteamVR panels/windows could dock on a home's walls (Meta's WallPlacementComponent). Removed the feature end to end: the WorldWallSpot type and its JSON/binary serialization, the loader that read WallPlacementComponent, auto-generation of spots and the hotspots placed in front of them, the Studio editor's Wall Spot kind (Add menu, properties, overlay draw, picking, selection, report), the debug overlay, the home report counts, and the walktest viz. The PanelDock subsystem (src/panel_dock.h) existed only to dock panels onto wall spots and is deleted with its use in Frame Home's frame loop. Baked scene format is bumped to v14: new bakes omit wall spots, and older baked scenes still load (the wall-spot block is read past). Collision walls (WorldCollider::Wall) are unaffected — only the panel-placement markers are gone. Docs updated. Note: the pre-existing FRAME_HOME_PC Windows build break in walkgrid.h (the `near` macro) is unrelated and left as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0804df866
commit
64ad00fa1d
21 files changed
+63
-423
No files matched your search
+2
-6
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
// The world layer drawn over the home in the headset (the Debug page's "World layer"): the walkable area as Frame
|
||||
// Home walks it, the surfaces and colliders, spawns, seats, hotspots, wall spots and gravity zones, in the colours
|
||||
// Home walks it, the surfaces and colliders, spawns, seats, hotspots and gravity zones, in the colours
|
||||
// Frame Studio uses for them. Built once per home as guide geometry (guide_geom.h) and appended to the guide each
|
||||
// frame. Lines are thin crossed ribbons, so they read from any side without being rebuilt as you move.
|
||||
#include "guide_geom.h"
|
||||
@@ -58,7 +58,7 @@ inline void marker(std::vector<GuideVertex>& o, const WorldPoint& p, float r, fl
|
||||
constexpr C kWalk{0.15f, 0.85f, 0.35f, 0.32f}, kWalkEdge{0.25f, 1.0f, 0.45f, 0.9f}, kBridged{0.95f, 0.75f, 0.2f, 0.25f};
|
||||
constexpr C kSolid{0.95f, 0.95f, 0.95f, 0.55f}, kObject{1.0f, 0.55f, 0.25f, 0.7f}, kTrigger{0.6f, 0.6f, 1.0f, 0.45f}, kWalkable{0.3f, 1.0f, 0.5f, 0.7f};
|
||||
constexpr C kSpawn{0.3f, 0.75f, 1.0f, 0.95f}, kSeat{1.0f, 0.8f, 0.25f, 0.95f}, kHotspot{0.85f, 0.45f, 1.0f, 0.95f};
|
||||
constexpr C kSpot{0.4f, 0.9f, 1.0f, 0.8f}, kGravity{1.0f, 0.35f, 0.6f, 0.6f};
|
||||
constexpr C kGravity{1.0f, 0.35f, 0.6f, 0.6f};
|
||||
|
||||
// Everything, in world space. Mesh surfaces are drawn as their boundary-free wire (every edge) only up to a budget:
|
||||
// a mesh collider can be a whole room's geometry.
|
||||
@@ -165,10 +165,6 @@ inline void build(const HomeWorld& w, const WalkGrid* g, std::vector<GuideVertex
|
||||
const P ex{p.pos[0] + p.exit[0], p.pos[1] + p.exit[1] + 0.02f, p.pos[2] + p.exit[2]};
|
||||
if (p.exit[0] != 0 || p.exit[2] != 0) { line(o, at, ex, guide::alpha(kSeat, 0.5f)); circle(o, ex, 0.12f, guide::alpha(kSeat, 0.7f)); }
|
||||
}
|
||||
for (const WorldWallSpot& s : w.wallSpots) {
|
||||
const float h[3] = {s.width * 0.5f, s.height * 0.5f, 0.01f};
|
||||
box(o, s.world, h, kSpot);
|
||||
}
|
||||
for (const WorldGravity& z : w.gravity) {
|
||||
if (z.kind == WorldGravity::Planet) circle(o, xf(z.world, {0, 0, 0}), z.radius, kGravity, 64);
|
||||
else box(o, z.world, z.half, kGravity);
|
||||
|
||||
+5
-5
@@ -24,7 +24,7 @@ struct HomeReport {
|
||||
size_t seeThrough = 0, seeThroughTris = 0, cutout = 0, skinned = 0, animated = 0, lightmapped = 0, sky = 0, programs = 0;
|
||||
size_t textures = 0; // distinct
|
||||
double textureMB = 0, decodedMB = 0, geometryMB = 0; // compressed (as the GPU gets it when it can), decoded RGBA, vertices + indices
|
||||
size_t spawns = 0, seats = 0, hotspots = 0, colliders = 0, wallSpots = 0, gravity = 0;
|
||||
size_t spawns = 0, seats = 0, hotspots = 0, colliders = 0, gravity = 0;
|
||||
double walkArea = -1; // m², < 0: no walkable area known
|
||||
double loadSec = 0; // set by the caller
|
||||
struct Mesh { size_t tris; int mesh; };
|
||||
@@ -50,8 +50,8 @@ struct HomeReport {
|
||||
o += b;
|
||||
std::snprintf(b, sizeof b, "%zu textures: %.0f MB compressed, %.0f MB decoded; geometry %.0f MB\n", textures, textureMB, decodedMB, geometryMB);
|
||||
o += b;
|
||||
std::snprintf(b, sizeof b, "world: %zu spawns, %zu seats, %zu hotspots, %zu surfaces, %zu wall spots, %zu gravity zones", spawns, seats, hotspots,
|
||||
colliders, wallSpots, gravity);
|
||||
std::snprintf(b, sizeof b, "world: %zu spawns, %zu seats, %zu hotspots, %zu surfaces, %zu gravity zones", spawns, seats, hotspots,
|
||||
colliders, gravity);
|
||||
o += b;
|
||||
o += walkArea >= 0 ? "; walkable " + num(walkArea, 0) + " m²\n" : "\n";
|
||||
if (!biggestMeshes.empty()) o += "most triangles:\n";
|
||||
@@ -72,7 +72,7 @@ struct HomeReport {
|
||||
n("meshes", meshes); n("triangles", triangles); n("vertices", vertices); n("seeThrough", seeThrough); n("seeThroughTris", seeThroughTris);
|
||||
n("cutout", cutout); n("skinned", skinned); n("animated", animated); n("lightmapped", lightmapped); n("programs", programs);
|
||||
n("textures", textures); n("textureMB", std::round(textureMB)); n("decodedMB", std::round(decodedMB)); n("geometryMB", std::round(geometryMB));
|
||||
n("spawns", spawns); n("seats", seats); n("colliders", colliders); n("wallSpots", wallSpots); n("walkArea", std::round(walkArea));
|
||||
n("spawns", spawns); n("seats", seats); n("colliders", colliders); n("walkArea", std::round(walkArea));
|
||||
n("loadSec", std::round(loadSec * 10) / 10);
|
||||
o += ",\"biggestMeshes\":[";
|
||||
for (size_t i = 0; i < biggestMeshes.size(); ++i) o += (i ? ",{\"name\":" : "{\"name\":") + q(meshName(biggestMeshes[i].mesh)) + ",\"tris\":" + num(biggestMeshes[i].tris, 0) + "}";
|
||||
@@ -209,7 +209,7 @@ inline HomeReport makeHomeReport(const HslScene& s, const HomeWorld* walk = null
|
||||
// The world.
|
||||
const HomeWorld& w = walk ? *walk : s.world;
|
||||
r.spawns = w.spawns.size(); r.seats = w.seats.size(); r.hotspots = w.hotspots.size(); r.colliders = w.colliders.size();
|
||||
r.wallSpots = w.wallSpots.size(); r.gravity = w.gravity.size();
|
||||
r.gravity = w.gravity.size();
|
||||
const WalkGrid* g = walk ? walk->walkGridCache.get() : nullptr;
|
||||
float floorY = 0;
|
||||
if (w.spawns.empty() && w.gravity.empty() && (!g || !g->floorAt(0, 0, 0, 0.5f, 1.0f, floorY)))
|
||||
|
||||
+1
-1
@@ -485,7 +485,7 @@ bool loadHslScene(const std::vector<std::string>& apkPaths, HslScene& scene, std
|
||||
HomeWorld& w = loaders[i]->world;
|
||||
auto add = [](auto& to, auto& from) { to.insert(to.end(), std::make_move_iterator(from.begin()), std::make_move_iterator(from.end())); };
|
||||
add(scene.world.spawns, w.spawns); add(scene.world.seats, w.seats); add(scene.world.hotspots, w.hotspots);
|
||||
add(scene.world.colliders, w.colliders); add(scene.world.wallSpots, w.wallSpots);
|
||||
add(scene.world.colliders, w.colliders);
|
||||
if (!scene.world.empty()) scene.world.origin = "meta";
|
||||
}
|
||||
for (auto& md : loaders[i]->meshes) {
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ inline bool loadHslScene(const std::string& apkPath, HslScene& scene, std::strin
|
||||
|
||||
// The world to walk in: the home's own (Meta's converted, or a .fenv's) when it has floors or walls; else floors and
|
||||
// walls taken from the drawn geometry (opaque, not the sky, not distant scenery) — Clay homes and homes like Theater
|
||||
// ship no collision at all. Spawns, seats and wall spots are kept either way.
|
||||
// ship no collision at all. Spawns and seats are kept either way.
|
||||
// The world carries its walkable area (walkGridCache): the one Frame Studio baked (world.json), else one baked here from
|
||||
// the drawn geometry plus the colliders (bakeWalkGrid). Gravity homes also get the geometry's floors and walls as
|
||||
// colliders while nothing blocks (the gravity walker reads colliders).
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "hsl_scene.h"
|
||||
#include "scene_bake.h"
|
||||
#include "locomotion.h"
|
||||
#include "panel_dock.h"
|
||||
#include "panorama.h"
|
||||
#include "core/audio.h"
|
||||
#include "env_export.h"
|
||||
@@ -1087,8 +1086,6 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
loco.setOptions(perf.walk, perf.teleport, perf.turn, perf.snapAngle, perf.moveSpeed);
|
||||
loco.setRingFallback(!r.guideAvailable()); // Frame Home draws the arc itself; the overlay ring only if it can't
|
||||
std::vector<GuideVertex> guideVerts;
|
||||
PanelDock dock; // SteamVR panels docking on the home's wall spots
|
||||
|
||||
// Homes are parsed (and their textures decoded) on a worker thread and uploaded between frames,
|
||||
// so the headset keeps getting frames while a new home loads.
|
||||
const bool follow = apks.empty(); // environment mode: follow ~/frame-home/current
|
||||
@@ -1415,7 +1412,6 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
LOG("report: %zu things to look at (logs/report.txt), first: %s", pendingReport.problems.size(), pendingReport.problems.front().what.c_str());
|
||||
pendingReport = HomeReport();
|
||||
}
|
||||
dock.setWorld(scene->world);
|
||||
LOG("showing %s (%zu spawns, %zu seats, %zu colliders)", shown.front().c_str(), scene->world.spawns.size(),
|
||||
scene->world.seats.size(), scene->world.colliders.size());
|
||||
} else {
|
||||
@@ -1691,7 +1687,6 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
lastFrame = frameNow;
|
||||
Mat4 rig;
|
||||
loco.rig(rig.m);
|
||||
dock.update(rig.m);
|
||||
const Mat4 headWorld = mul(rig, head);
|
||||
if (system && widenFixed <= 0) {
|
||||
// head turn rate (rad/s) from the rotation since the last frame: angle of R_prev^T * R
|
||||
@@ -1890,7 +1885,6 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
if (worker.joinable()) worker.join();
|
||||
if (soundJob.joinable()) soundJob.join();
|
||||
publisher.finish();
|
||||
dock.stop();
|
||||
// SteamVR first: its client holds our Vulkan device for the frames it composited, and cleaning up after a
|
||||
// device we'd already destroyed crashed every exit (game launches, restarts).
|
||||
vr::VR_Shutdown();
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
// SteamVR's panels (overlays) of every app. OpenVR can't list them, but a handle is (slot << 32 | serial) with both
|
||||
// small, so probing that range finds them all; a whole sweep takes about 0.1 s. Used to tell whether a hand points
|
||||
// at a panel (frame-switcher) and to dock free-floating panels on a home's wall spots (Frame Home).
|
||||
// at a panel (frame-switcher).
|
||||
#include <openvr.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
#pragma once
|
||||
// Wall spots at work: a SteamVR panel (a desktop or game window, any free-floating overlay) let go of near one of
|
||||
// the home's wall spots docks onto it — flush with the wall, facing the room, no wider than the spot — and stays
|
||||
// there as you walk and teleport (panels live in tracking space, so a docked one is re-placed from the home's frame
|
||||
// whenever you move). Grab it away and it's free again, back at its own size. Docked panels are re-placed only when
|
||||
// the rig moves: setting one every frame would overwrite a grab before it could be seen.
|
||||
// Meta's homes mark these spots with WallPlacementComponent; Frame Studio edits them (World > Wall Spot).
|
||||
#include "core/world.h"
|
||||
#include "overlays.h"
|
||||
|
||||
#include <openvr.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class PanelDock {
|
||||
public:
|
||||
~PanelDock() { stop(); }
|
||||
// Before VR_Shutdown: the scanner calls into SteamVR's client library, which shutting down unloads.
|
||||
void stop() { stop_ = true; if (scanner_.joinable()) scanner_.join(); }
|
||||
|
||||
void setWorld(const HomeWorld& w) {
|
||||
undockAll();
|
||||
spots_ = w.wallSpots;
|
||||
if (!spots_.empty() && !scanner_.joinable()) scanner_ = std::thread([this] { scanLoop(); });
|
||||
}
|
||||
|
||||
// Once per frame with worldFromTracking.
|
||||
void update(const float rig[16]) {
|
||||
if (spots_.empty()) return;
|
||||
std::vector<OverlayInfo> panels;
|
||||
{ std::lock_guard<std::mutex> l(mutex_); panels = panels_; }
|
||||
float inv[16];
|
||||
invertRigid(rig, inv);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
vr::IVROverlay* ov = vr::VROverlay();
|
||||
// Panels whose app went away: their state (and any spot they held) goes too.
|
||||
for (auto it = state_.begin(); it != state_.end();) {
|
||||
bool present = false;
|
||||
for (const OverlayInfo& p : panels) present |= p.handle == it->first;
|
||||
it = present ? std::next(it) : state_.erase(it);
|
||||
}
|
||||
for (const OverlayInfo& p : panels) {
|
||||
if (!ov->IsOverlayVisible(p.handle)) continue;
|
||||
vr::VROverlayTransformType tt;
|
||||
if (ov->GetOverlayTransformType(p.handle, &tt) != vr::VROverlayError_None || tt != vr::VROverlayTransform_Absolute) continue;
|
||||
vr::ETrackingUniverseOrigin origin;
|
||||
vr::HmdMatrix34_t cur;
|
||||
if (ov->GetOverlayTransformAbsolute(p.handle, &origin, &cur) != vr::VROverlayError_None) continue;
|
||||
if (origin != vr::TrackingUniverseStanding) continue; // the spots are placed in standing space
|
||||
State& s = state_[p.handle];
|
||||
if (s.spot >= 0) {
|
||||
// Docked: still where we put it? Then keep it on the wall as the rig moves; else it was grabbed away.
|
||||
if (differs(cur, s.placed, 0.03f)) { undock(p.handle, s, "moved away"); s.still = now; s.last = cur; continue; }
|
||||
// Re-placed only once the rig has moved a few millimetres since it was placed (per panel, so one
|
||||
// hidden while you teleported is put right when it shows again). Tiny continuous rig changes —
|
||||
// seated eye-height easing, step easing — would otherwise overwrite a grab every frame.
|
||||
float moved = 0;
|
||||
for (int k = 0; k < 16; ++k) moved = std::max(moved, std::fabs(rig[k] - s.rig[k]));
|
||||
if (moved < 0.005f) continue;
|
||||
vr::HmdMatrix34_t m = spotInTracking(s.spot, inv, s.flip);
|
||||
ov->SetOverlayTransformAbsolute(p.handle, origin, &m);
|
||||
s.placed = m;
|
||||
std::memcpy(s.rig, rig, sizeof s.rig);
|
||||
continue;
|
||||
}
|
||||
if (differs(cur, s.last, 0.002f)) { s.last = cur; s.still = now; continue; } // still moving
|
||||
if (now - s.still < std::chrono::milliseconds(500) || s.cooldown > now) continue;
|
||||
// At rest: dock onto the nearest free spot within 0.6 m.
|
||||
float pt[3];
|
||||
for (int k = 0; k < 3; ++k) pt[k] = rig[k] * cur.m[0][3] + rig[4 + k] * cur.m[1][3] + rig[8 + k] * cur.m[2][3] + rig[12 + k];
|
||||
int best = -1;
|
||||
float bestD = 0.6f;
|
||||
for (size_t i = 0; i < spots_.size(); ++i) {
|
||||
if (taken(static_cast<int>(i))) continue;
|
||||
const float* c = &spots_[i].world[12];
|
||||
const float d = std::sqrt((pt[0] - c[0]) * (pt[0] - c[0]) + (pt[1] - c[1]) * (pt[1] - c[1]) + (pt[2] - c[2]) * (pt[2] - c[2]));
|
||||
if (d < bestD) { bestD = d; best = static_cast<int>(i); }
|
||||
}
|
||||
if (best < 0) { s.cooldown = now + std::chrono::milliseconds(500); continue; }
|
||||
// Face the panel the way it was facing (towards the user), whichever way the spot's frame points.
|
||||
const float* z = &spots_[best].world[8];
|
||||
float pz[3];
|
||||
for (int k = 0; k < 3; ++k) pz[k] = rig[k] * cur.m[0][2] + rig[4 + k] * cur.m[1][2] + rig[8 + k] * cur.m[2][2];
|
||||
s.flip = z[0] * pz[0] + z[1] * pz[1] + z[2] * pz[2] < 0;
|
||||
ov->GetOverlayWidthInMeters(p.handle, &s.width);
|
||||
const float fit = std::min(s.width, spots_[best].width);
|
||||
ov->SetOverlayWidthInMeters(p.handle, fit);
|
||||
vr::HmdMatrix34_t m = spotInTracking(best, inv, s.flip);
|
||||
ov->SetOverlayTransformAbsolute(p.handle, origin, &m);
|
||||
s.placed = m;
|
||||
std::memcpy(s.rig, rig, sizeof s.rig);
|
||||
s.spot = best;
|
||||
std::fprintf(stderr, "frame-home: docked panel %s on wall spot %s\n", p.key.c_str(), spots_[best].name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct State {
|
||||
int spot = -1;
|
||||
bool flip = false;
|
||||
float width = 1; // its own width, given back when it leaves the wall
|
||||
vr::HmdMatrix34_t placed = {}, last = {};
|
||||
float rig[16] = {}; // the rig it was last placed for
|
||||
std::chrono::steady_clock::time_point still{}, cooldown{};
|
||||
};
|
||||
std::vector<WorldWallSpot> spots_;
|
||||
std::map<vr::VROverlayHandle_t, State> state_;
|
||||
std::vector<OverlayInfo> panels_;
|
||||
std::mutex mutex_;
|
||||
std::thread scanner_;
|
||||
std::atomic<bool> stop_{false};
|
||||
|
||||
// The panel list, refreshed every 2 s off the frame loop (a sweep takes ~0.1 s).
|
||||
void scanLoop() {
|
||||
while (!stop_) {
|
||||
std::vector<OverlayInfo> found;
|
||||
for (const OverlayInfo& o : listOverlays())
|
||||
if (isUserPanel(o) && o.key.rfind("system.", 0) != 0) found.push_back(o);
|
||||
{ std::lock_guard<std::mutex> l(mutex_); panels_ = std::move(found); }
|
||||
for (int i = 0; i < 20 && !stop_; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
bool taken(int spot) const {
|
||||
for (const auto& kv : state_) if (kv.second.spot == spot) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void undock(vr::VROverlayHandle_t h, State& s, const char* why) {
|
||||
vr::VROverlay()->SetOverlayWidthInMeters(h, s.width);
|
||||
std::fprintf(stderr, "frame-home: panel left wall spot %s (%s)\n", spots_[s.spot].name.c_str(), why);
|
||||
s.spot = -1;
|
||||
s.cooldown = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
}
|
||||
|
||||
void undockAll() {
|
||||
for (auto& kv : state_) if (kv.second.spot >= 0) undock(kv.first, kv.second, "new home");
|
||||
state_.clear();
|
||||
}
|
||||
|
||||
// The spot's frame in tracking space, 1 cm off the wall, optionally turned to face the other way.
|
||||
vr::HmdMatrix34_t spotInTracking(int i, const float inv[16], bool flip) const {
|
||||
float w[16];
|
||||
std::memcpy(w, spots_[i].world, 64);
|
||||
for (int c = 0; c < 3; ++c) { // unit axes (a scaled spot frame would scale the panel)
|
||||
const float L = std::sqrt(w[c * 4] * w[c * 4] + w[c * 4 + 1] * w[c * 4 + 1] + w[c * 4 + 2] * w[c * 4 + 2]);
|
||||
if (L > 1e-6f) for (int r = 0; r < 3; ++r) w[c * 4 + r] /= L;
|
||||
}
|
||||
if (flip) for (int r = 0; r < 3; ++r) { w[r] = -w[r]; w[8 + r] = -w[8 + r]; } // 180 degrees about Y
|
||||
for (int r = 0; r < 3; ++r) w[12 + r] += w[8 + r] * 0.01f;
|
||||
float t[16];
|
||||
for (int c = 0; c < 4; ++c)
|
||||
for (int r = 0; r < 4; ++r) t[c * 4 + r] = inv[r] * w[c * 4] + inv[4 + r] * w[c * 4 + 1] + inv[8 + r] * w[c * 4 + 2] + inv[12 + r] * w[c * 4 + 3];
|
||||
vr::HmdMatrix34_t m;
|
||||
for (int r = 0; r < 3; ++r) for (int c = 0; c < 4; ++c) m.m[r][c] = t[c * 4 + r];
|
||||
return m;
|
||||
}
|
||||
|
||||
static void invertRigid(const float m[16], float o[16]) {
|
||||
for (int r = 0; r < 3; ++r) for (int c = 0; c < 3; ++c) o[c * 4 + r] = m[r * 4 + c];
|
||||
for (int r = 0; r < 3; ++r) o[12 + r] = -(o[r] * m[12] + o[4 + r] * m[13] + o[8 + r] * m[14]);
|
||||
o[3] = o[7] = o[11] = 0; o[15] = 1;
|
||||
}
|
||||
|
||||
static bool differs(const vr::HmdMatrix34_t& a, const vr::HmdMatrix34_t& b, float eps) {
|
||||
for (int r = 0; r < 3; ++r) for (int c = 0; c < 4; ++c) if (std::fabs(a.m[r][c] - b.m[r][c]) > eps) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -123,7 +123,7 @@ struct PerfSettings {
|
||||
// why a home looks or runs wrong, not for keeping: Frame Home clears the file each time it starts.
|
||||
struct DebugSettings {
|
||||
int view = 0; // HslRenderer::DebugView: 0 normal, 1 wireframe, 2 meshes, 3 GPU cost, 4 overdraw
|
||||
int world = 0; // the world layer drawn over the home: walkable area, surfaces, spawns, seats, wall spots
|
||||
int world = 0; // the world layer drawn over the home: walkable area, surfaces, spawns, seats
|
||||
int hideSeeThrough = 0, hideCutout = 0, hideSky = 0; // leave those parts of the home out
|
||||
int shot = 0; // asks for a picture of what's shown (logs/frame-<time>.png): the time it was asked
|
||||
// (Unix seconds); one from before Frame Home started is old
|
||||
|
||||
+2
-4
@@ -177,8 +177,6 @@ void writeWorld(Writer& w, const HomeWorld& wd, const std::vector<MeshData>& mes
|
||||
w.str(c.mesh);
|
||||
}
|
||||
w.vec(wd.walkPositions); w.vec(wd.walkIndices);
|
||||
w.u(static_cast<u32>(wd.wallSpots.size()));
|
||||
for (const WorldWallSpot& ws : wd.wallSpots) { w.str(ws.name); w.fa(ws.world, 16); w.f(ws.width); w.f(ws.height); }
|
||||
w.u(static_cast<u32>(wd.gravity.size()));
|
||||
for (const WorldGravity& g : wd.gravity) { w.str(g.name); w.pod(g.kind); w.fa(g.world, 16); w.fa(g.half, 3); w.f(g.radius); w.pod(g.priority); }
|
||||
w.f(wd.fallLimit);
|
||||
@@ -202,8 +200,8 @@ void readWorld(Reader& r, HomeWorld& wd, u32 version, std::vector<std::pair<size
|
||||
if (version >= 4) c.mesh = r.str();
|
||||
}
|
||||
wd.walkPositions = r.vec<float>(); wd.walkIndices = r.vec<u32>();
|
||||
wd.wallSpots.resize(r.count(76));
|
||||
for (WorldWallSpot& ws : wd.wallSpots) { if (r.bad) break; ws.name = r.str(); r.fa(ws.world, 16); ws.width = r.f(); ws.height = r.f(); }
|
||||
if (version < 14) // wall spots, now removed: read past them
|
||||
for (u32 n = r.count(76); n-- && !r.bad;) { float m[16]; r.str(); r.fa(m, 16); r.f(); r.f(); }
|
||||
if (version >= 6) {
|
||||
wd.gravity.resize(r.count(92));
|
||||
for (WorldGravity& g : wd.gravity) { if (r.bad) break; g.name = r.str(); g.kind = r.pod<int>(); r.fa(g.world, 16); r.fa(g.half, 3); g.radius = r.f(); g.priority = r.pod<int>(); }
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
constexpr u32 kSceneBakeVersion = 13; // 2: world; 3: HDR maps as ASTC, binary world; 4: roles; 5: mesh refs; 6: gravity; 7: mesh paths; 8: ambience name; 9: compressed 8-bit lightmaps; 10: reflection cube as ASTC; 11: walkable area (and simplified colliders); 12: prepared and specialized shaders; 13: Clay / glTF CPU animation
|
||||
constexpr u32 kSceneBakeVersion = 14; // 2: world; 3: HDR maps as ASTC, binary world; 4: roles; 5: mesh refs; 6: gravity; 7: mesh paths; 8: ambience name; 9: compressed 8-bit lightmaps; 10: reflection cube as ASTC; 11: walkable area (and simplified colliders); 12: prepared and specialized shaders; 13: Clay / glTF CPU animation; 14: wall spots removed
|
||||
|
||||
struct VisibilityResult;
|
||||
struct ObjectEdit;
|
||||
|
||||
+1
-1
@@ -902,7 +902,7 @@ struct Panel {
|
||||
"Hold still stops the environment's moving parts; animated environments then cost less.",
|
||||
"With the menu open the environment is only its backdrop. Half rate leaves the GPU to the menu and its apps.",
|
||||
"Wireframe: every triangle edge. Meshes: each in its own colour. GPU cost: green is cheap, red is costly. Overdraw: bright where surfaces pile up.",
|
||||
"Where you can walk (green), surfaces and colliders, spawns, seats, wall spots and gravity zones.",
|
||||
"Where you can walk (green), surfaces and colliders, spawns, seats and gravity zones.",
|
||||
"Glass, water, glow and fog. Leave them out to see whether they're what's slow.",
|
||||
"Leaves, grass and fences. Leave them out to see whether they're what's slow.",
|
||||
"The sky around the environment.",
|
||||
|
||||
+7
-82
@@ -7,9 +7,8 @@
|
||||
// Off unless GenParams asks (guesses, and a wrong one is worse than none):
|
||||
// walls — Wall boxes on flat upright faces that are architecture (named a wall, or 2.4 m+ / up to the ceiling),
|
||||
// and along drops inside a tall structure
|
||||
// wall spots — the middle of a tall, solid stretch of wall with room to stand back from it (SteamVR panels)
|
||||
// seats — a top 35-62 cm over the floor with a backrest behind and floor in front, or a stool
|
||||
// hotspots — in front of wall spots, lookouts, the middles of open spaces
|
||||
// hotspots — lookouts, the middles of open spaces
|
||||
// Everything made is named "Auto …" (Frame Studio replaces them on a re-run).
|
||||
#include "walkgrid.h"
|
||||
|
||||
@@ -25,19 +24,18 @@ struct GenParams {
|
||||
float wallMinHeight = 0.9f; // a wall is at least this tall over the floor in front of it (a railing)
|
||||
float wallMinRun = 0.6f; // shortest wall (m)
|
||||
float seatLo = 0.35f, seatHi = 0.62f; // a seat's top over the floor in front of it (m): low lounge sofas to thick cushions
|
||||
int maxSpots = 5, maxHotspots = 12;
|
||||
int maxHotspots = 12;
|
||||
// Seats and hotspots are guesses about how people use a place, and a wrong one (a seat on a planter's rim) is worse
|
||||
// than none: off unless asked for. Auto-generate makes the floor, the walls, the objects' collision and wall spots.
|
||||
// than none: off unless asked for. Auto-generate makes the floor, the walls and the objects' collision.
|
||||
bool seats = false, hotspots = false;
|
||||
// Walls (boxes on faces judged to be walls) and wall spots are guesses too: off. Every mesh gets its own collision
|
||||
// Walls (boxes on faces judged to be walls) are guesses too: off. Every mesh gets its own collision
|
||||
// instead (GenResult::objects), whatever it is.
|
||||
bool walls = false, spots = false;
|
||||
bool walls = false;
|
||||
};
|
||||
|
||||
struct GenResult {
|
||||
std::vector<WorldCollider> walls, objects;
|
||||
std::vector<WorldPoint> seats, hotspots;
|
||||
std::vector<WorldWallSpot> spots;
|
||||
};
|
||||
|
||||
namespace world_gen_detail {
|
||||
@@ -618,63 +616,6 @@ inline GenResult generateWorldItems(const WalkGrid& g, const HomeWorld& have, co
|
||||
for (WorldCollider& c : encl) out.walls.push_back(std::move(c));
|
||||
}
|
||||
|
||||
// ── Wall spots: the solid middle of a tall wall, with room to stand back from it ──
|
||||
if (P.spots) {
|
||||
const float sx = have.spawns.empty() ? 0.0f : have.spawns[0].pos[0], sz = have.spawns.empty() ? 0.0f : have.spawns[0].pos[2];
|
||||
struct Cand { float score, x, y, z, ux, uz, fx, fz, width, height; };
|
||||
std::vector<Cand> cands;
|
||||
for (const Run& rn : runs) {
|
||||
const Wall& w = *rn.w;
|
||||
const float L = (rn.i1 - rn.i0 + 1) * kCell;
|
||||
if (w.glass || w.hint == kHintBlocker || L < 1.6f || rn.top - rn.floor < 2.2f) continue; // (a blocker wall: Meta's, not one to hang on)
|
||||
const float width = std::min(L - 0.4f, 2.4f), height = std::clamp(rn.top - rn.floor - 1.0f, 0.9f, 1.4f);
|
||||
const float bottom = rn.floor + 0.85f;
|
||||
const int wc = static_cast<int>(std::ceil(width / kCell));
|
||||
const float fx = w.nx * rn.side, fz = w.nz * rn.side, ux = -w.nz, uz = w.nx;
|
||||
// slide along the wall from its middle until the panel sits on solid wall (no window, door or gap)
|
||||
const int mid = (rn.i0 + rn.i1) / 2;
|
||||
bool placed = false;
|
||||
for (int off = 0; off <= (rn.i1 - rn.i0 - wc) / 2 + 1 && !placed; off += 2)
|
||||
for (int sgn : {1, -1}) {
|
||||
const int c0 = mid + sgn * off - wc / 2, c1 = c0 + wc - 1;
|
||||
if (c0 < rn.i0 || c1 > rn.i1 || w.fill(c0, c1, bottom - 0.1f, bottom + height + 0.1f) < 0.97f) continue;
|
||||
const float s = w.s0 + (c0 + c1 + 1) * 0.5f * kCell, px = w.nx * w.d + ux * s, pz = w.nz * w.d + uz * s;
|
||||
// clear air in front at panel height (2.5 m straight out, 1.5 m across all of it), floor 2 m back
|
||||
bool clear = true;
|
||||
for (float t = 0.3f; t <= 2.5f && clear; t += 0.2f)
|
||||
for (float lat : {-width * 0.5f, -width * 0.25f, 0.0f, width * 0.25f, width * 0.5f})
|
||||
if ((lat == 0 || t <= 1.5f) && solidBetween(g, px + fx * t + ux * lat, pz + fz * t + uz * lat, rn.floor + 0.7f, rn.floor + 2.2f)) { clear = false; break; }
|
||||
if (!clear || !areaFloor(g, px + fx * 2.0f, pz + fz * 2.0f, rn.floor, 0.3f)) continue;
|
||||
// a wall round a space, not a slab standing between two paths (floor behind it too)
|
||||
bool divider = false;
|
||||
for (float t : {0.3f, 0.5f, 0.7f}) divider |= areaFloor(g, px - fx * t, pz - fz * t, rn.floor, 0.5f) != nullptr;
|
||||
if (divider) continue;
|
||||
const float toSpawn = std::hypot(sx - px, sz - pz), facing = ((sx - px) * fx + (sz - pz) * fz) / std::max(0.1f, toSpawn);
|
||||
cands.push_back({width + (facing > 0.3f && toSpawn < 9 ? 2.0f : 0.0f), px + fx * 0.02f, bottom + height * 0.5f, pz + fz * 0.02f, ux, uz, fx, fz, width, height});
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::sort(cands.begin(), cands.end(), [](const Cand& a, const Cand& b) { return a.score > b.score; });
|
||||
for (const Cand& c : cands) {
|
||||
if (static_cast<int>(out.spots.size()) >= P.maxSpots) break;
|
||||
bool close = false;
|
||||
for (const WorldWallSpot& w : have.wallSpots) close |= std::hypot(w.world[12] - c.x, w.world[14] - c.z) < 1.5f && std::fabs(w.world[13] - c.y) < 1.5f;
|
||||
for (const WorldWallSpot& w : out.spots) close |= std::hypot(w.world[12] - c.x, w.world[14] - c.z) < 3.0f;
|
||||
if (close) continue;
|
||||
// the frame: X along the wall, Y up, Z (= X x Y) out into the room
|
||||
float ux = c.ux, uz = c.uz;
|
||||
if (-uz * c.fx + ux * c.fz < 0) { ux = -ux; uz = -uz; }
|
||||
WorldWallSpot w;
|
||||
w.name = "Auto wall spot";
|
||||
const float m[16] = {ux, 0, uz, 0, 0, 1, 0, 0, -uz, 0, ux, 0, c.x, c.y, c.z, 1};
|
||||
std::copy_n(m, 16, w.world);
|
||||
w.width = c.width;
|
||||
w.height = c.height;
|
||||
out.spots.push_back(w);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Seats: where on a seat-high top you'd sit — a backrest behind, floor in front — or a free-standing stool ──
|
||||
if (P.seats) {
|
||||
const float cs = g.p.cell;
|
||||
@@ -994,23 +935,7 @@ inline GenResult generateWorldItems(const WalkGrid& g, const HomeWorld& have, co
|
||||
out.hotspots.push_back(p);
|
||||
taken.push_back({x, y, z});
|
||||
};
|
||||
// 1. in front of each wall spot, looking at it
|
||||
std::vector<const WorldWallSpot*> spots;
|
||||
for (const WorldWallSpot& w : have.wallSpots) spots.push_back(&w);
|
||||
for (const WorldWallSpot& w : out.spots) spots.push_back(&w);
|
||||
for (const WorldWallSpot* w : spots)
|
||||
for (float sgn : {1.0f, -1.0f}) {
|
||||
const float fx = w->world[8] * sgn, fz = w->world[10] * sgn, fl = std::hypot(fx, fz);
|
||||
if (fl < 0.5f) continue;
|
||||
float x = w->world[12] + fx / fl * 2.2f, z = w->world[14] + fz / fl * 2.2f;
|
||||
const WalkSpan* f = areaFloor(g, x, z, w->world[13] - 1.5f, 1.2f);
|
||||
if (!f) continue;
|
||||
const float y = f->hi;
|
||||
if (!g.roomAt(x, y, z, 0.5f) || !free(x, y, z, 2.0f)) continue;
|
||||
add(x, y, z, -fx / fl, -fz / fl);
|
||||
break;
|
||||
}
|
||||
// 2. lookouts: where the area's edge is a drop with open air beyond it (a terrace's rail, a balcony)
|
||||
// 1. lookouts: where the area's edge is a drop with open air beyond it (a terrace's rail, a balcony)
|
||||
{
|
||||
struct Edge { float x, y, z, ox, oz; };
|
||||
std::vector<Edge> view;
|
||||
@@ -1041,7 +966,7 @@ inline GenResult generateWorldItems(const WalkGrid& g, const HomeWorld& have, co
|
||||
++lookouts;
|
||||
}
|
||||
}
|
||||
// 3. the middles of open spaces (1.5 m or more from any edge, 6 m apart, five at most), facing the longest view
|
||||
// 2. the middles of open spaces (1.5 m or more from any edge, 6 m apart, five at most), facing the longest view
|
||||
{
|
||||
std::vector<std::array<float, 4>> cand;
|
||||
for (int z = 0; z < g.nz; z += 2)
|
||||
|
||||
Reference in new issue
Block a user