mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-28 23:03:21 +01:00
Frame Studio now builds for Windows (x64, x86, ARM64), Linux (x64, x86, ARM64)
and macOS (Apple Silicon, Intel):
- studio/platform.h with Win32 and POSIX implementations (dialogs through
tinyfiledialogs on Linux/macOS), a GLFW window beside the Win32 one, and a
platform-neutral frame pacer
- MoltenVK support in the renderer (portability enumeration) and bundled in
the macOS app
- 32-bit builds: large-address-aware, LZMA packing within a 32-bit address space
- unaligned file reads in the loaders go through loadUnaligned (no UB)
- scripts/build-studio.sh, studio/build_windows.bat [x64|x86|arm64],
cmake/llvm-mingw.cmake, and a GitHub Actions workflow building every target
Frame Home installs without Studio: frame/install-frame-home.{sh,ps1} and
"Install Frame Home.bat" (packaged as FrameHome-installer.zip).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
5.0 KiB
C++
95 lines
5.0 KiB
C++
// Frame Home never puts your eyes inside things or your feet through a floor (Locomotion over a baked walkable area):
|
|
// walking down an open staircase along its side, and walking / leaning into a wall for real.
|
|
#include "locomotion.h"
|
|
#include <cstdio>
|
|
static int fails = 0;
|
|
static void ck(bool ok, const char* w, float a = 0, float b = 0, float c = 0) { std::printf("%s %s (%.2f %.2f %.2f)\n", ok ? "PASS" : "FAIL", w, a, b, c); fails += !ok; }
|
|
static WorldCollider box(float x0, float y0, float z0, float x1, float y1, float z1) {
|
|
WorldCollider c; c.kind = WorldCollider::Box; c.role = WorldCollider::Solid;
|
|
c.half[0] = (x1 - x0) / 2; c.half[1] = (y1 - y0) / 2; c.half[2] = (z1 - z0) / 2;
|
|
c.world[12] = (x0 + x1) / 2; c.world[13] = (y0 + y1) / 2; c.world[14] = (z0 + z1) / 2;
|
|
return c;
|
|
}
|
|
static void bake(HomeWorld& w) {
|
|
std::vector<WalkSource> src;
|
|
std::vector<std::vector<float>> P;
|
|
std::vector<std::vector<uint32_t>> I;
|
|
walkColliderSources(w, src, P, I);
|
|
w.walkGridCache = buildWalkGrid(src, walkSeeds(w), WalkParams{}, nullptr);
|
|
}
|
|
int main() {
|
|
{ // an open staircase: a 1.8 m landing, 8 treads 20 cm down, 30 cm deep, 1 m wide, the ground all round and under
|
|
HomeWorld w;
|
|
w.colliders.push_back(box(-8, -0.1f, -8, 8, 0, 8));
|
|
w.colliders.push_back(box(-4, 0, -1, -2, 1.8f, 1));
|
|
for (int i = 0; i < 8; ++i) w.colliders.push_back(box(-2 + 0.3f * i, 1.8f - 0.2f * (i + 1) - 0.05f, -0.5f, -2 + 0.3f * (i + 1), 1.8f - 0.2f * (i + 1), 0.5f));
|
|
WorldPoint sp; sp.pos[0] = -3; sp.pos[1] = 1.8f; sp.pos[2] = 0.38f; sp.yaw = -1.5708f; // facing +X, down the stairs
|
|
w.spawns.push_back(sp);
|
|
WorldPoint g; g.pos[0] = 4; g.pos[1] = 0; g.pos[2] = 0; w.hotspots.push_back(g);
|
|
bake(w);
|
|
Locomotion loco;
|
|
loco.setWorld(w);
|
|
float head[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1.70f, 0, 1};
|
|
loco.testInput(0, 1, 0, 0);
|
|
float worst = 1e9f, x = -3, y = 0;
|
|
for (int i = 0; i < 900 && x < 0.5f; ++i) {
|
|
loco.update(1.0f / 90, head);
|
|
float R[16];
|
|
loco.rig(R);
|
|
x = R[0] * head[12] + R[4] * head[13] + R[8] * head[14] + R[12];
|
|
y = R[1] * head[12] + R[5] * head[13] + R[9] * head[14] + R[13];
|
|
float tread = 0; // the top under the eyes
|
|
if (x < -2) tread = 1.8f;
|
|
else if (x < 0.4f) tread = std::max(0.0f, 1.8f - 0.2f * (std::floor((x + 2) / 0.3f) + 1));
|
|
worst = std::min(worst, (y - 1.7f) - tread);
|
|
}
|
|
loco.testInput(0, 0, 0, 0);
|
|
for (int i = 0; i < 90; ++i) loco.update(1.0f / 90, head); // the last step eases down
|
|
{ float R[16]; loco.rig(R); y = R[1] * head[12] + R[5] * head[13] + R[9] * head[14] + R[13]; }
|
|
ck(x > 0.3f, "walked down the stairs along their open side", x, y);
|
|
ck(worst > -0.25f, "the feet never sink more than a quarter step under the tread (never through it)", worst);
|
|
ck(std::fabs(y - 1.7f) < 0.05f, "at the bottom, standing on the ground", y);
|
|
}
|
|
{ // a wall 1 m ahead: walk into it for real (no stick): the eyes stop short of it
|
|
HomeWorld w;
|
|
w.colliders.push_back(box(-8, -0.1f, -8, 8, 0, 8));
|
|
w.colliders.push_back(box(0.9f, 0, -2, 1.1f, 2.5f, 2));
|
|
WorldPoint sp; sp.pos[0] = 0; sp.pos[1] = 0; sp.pos[2] = 0; w.spawns.push_back(sp);
|
|
bake(w);
|
|
Locomotion loco;
|
|
loco.setWorld(w);
|
|
float head[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1.70f, 0, 1};
|
|
float most = -1e9f;
|
|
for (int i = 0; i < 300; ++i) {
|
|
head[12] = std::min(1.6f, i * 0.008f); // stepping forward 1.6 m in the room
|
|
head[13] = 1.70f - (i > 200 ? 0.6f : 0.0f); // then bending down
|
|
loco.update(1.0f / 90, head);
|
|
float R[16];
|
|
loco.rig(R);
|
|
most = std::max(most, R[0] * head[12] + R[4] * head[13] + R[8] * head[14] + R[12]);
|
|
}
|
|
ck(most <= 0.9f - 0.14f, "walking into a wall for real, the eyes stay 15 cm off it", most);
|
|
}
|
|
{ // a coffee table (45 cm high) in the way: walking into it for real, you're kept at its edge
|
|
HomeWorld w;
|
|
w.colliders.push_back(box(-8, -0.1f, -8, 8, 0, 8));
|
|
w.colliders.push_back(box(0.8f, 0, -0.5f, 1.6f, 0.45f, 0.5f));
|
|
WorldPoint sp; sp.pos[0] = 0; sp.pos[1] = 0; sp.pos[2] = 0; w.spawns.push_back(sp);
|
|
bake(w);
|
|
Locomotion loco;
|
|
loco.setWorld(w);
|
|
float head[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1.70f, 0, 1};
|
|
float most = -1e9f;
|
|
for (int i = 0; i < 250; ++i) {
|
|
head[12] = std::min(1.2f, i * 0.008f);
|
|
loco.update(1.0f / 90, head);
|
|
float R[16];
|
|
loco.rig(R);
|
|
most = std::max(most, R[0] * head[12] + R[4] * head[13] + R[8] * head[14] + R[12]);
|
|
}
|
|
ck(most <= 0.85f, "walking into a coffee table for real, you stop at its edge", most);
|
|
}
|
|
std::printf("%d failed\n", fails);
|
|
return fails != 0;
|
|
}
|