initial commit
This commit is contained in:
commit
e84e0b902c
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
lref is a c99 reference implementation of the physics of line rider beta 2 revision 6.2.
|
||||
|
||||
the goal is to byte-for-byte match beta 6.2's results on any given track, such that it can be a drop in replacement for unit tests of more modern physics engines.
|
||||
it is also the goal to be readable enough to reference for future physics engines.
|
||||
|
||||
it is currently incomplete, but the raylib frontend in this repository can be used to test it against existing .sol track files.
|
||||
the quality of that code and program is significantly lower than lref.h and only exists for testing purposes.
|
||||
|
||||
### references used to create lref
|
||||
- [OpenLR](https://github.com/kevansevans/OpenLR)
|
||||
- line rider beta 2 revision 6.2, decompiled
|
9
build.sh
Executable file
9
build.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# this isnt meant to be guarenteed to work on whatever system
|
||||
# the c compiler invokation just got really big
|
||||
# works for my system, is a reference for you in the worst case
|
||||
|
||||
cd track_wrapper && zig build --release=fast && cd ..
|
||||
|
||||
clang -Wall -Wextra -pedantic -std=c99 -L/usr/local/lib -I/usr/local/include -I. raylib_frontend.c track_wrapper/zig-out/lib/libtrack_wrapper.a -lraylib -lm
|
644
lref.h
Normal file
644
lref.h
Normal file
@ -0,0 +1,644 @@
|
||||
// LREF:
|
||||
//
|
||||
// c99 reference implementation of line rider,
|
||||
// with the goal of exactly matching beta 6.2.
|
||||
//
|
||||
// ~moss7
|
||||
|
||||
// code todos
|
||||
// TODO 6.1 and 6.0 grid registration
|
||||
// TODO camera implementation
|
||||
// TODO cleanup / deallocation logic
|
||||
// TODO consider which functions actually need to be api vs internal
|
||||
|
||||
|
||||
// documentation todos
|
||||
// TODO equivalent flash decomp code in comments for reference
|
||||
// TODO explain all of the physics functions in comments
|
||||
// TODO explain parts that may differ in a less contrived engine
|
||||
// (NaN checks, optimizations not made here for readability, etc)
|
||||
|
||||
// # external includes
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// these only need to be included by the implementation
|
||||
|
||||
#ifdef LREF_H_IMPLEMENTATION
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#endif
|
||||
|
||||
// # rider constants
|
||||
|
||||
const double endurance = 0.057;
|
||||
|
||||
const double gravity = 0.175;
|
||||
|
||||
#define POINT_COUNT 10
|
||||
#define BONE_COUNT 22
|
||||
#define ITERATION_COUNT 6
|
||||
|
||||
// # rider types
|
||||
|
||||
struct point {
|
||||
double x;
|
||||
double y;
|
||||
double previous_x;
|
||||
double previous_y;
|
||||
double momentum_x;
|
||||
double momentum_y;
|
||||
double friction;
|
||||
};
|
||||
|
||||
struct bone {
|
||||
bool breakable;
|
||||
bool repel;
|
||||
uint8_t a;
|
||||
uint8_t b;
|
||||
double restLength;
|
||||
};
|
||||
|
||||
struct rider {
|
||||
struct point points[POINT_COUNT];
|
||||
struct bone bones[BONE_COUNT];
|
||||
bool dead;
|
||||
bool sledbroken;
|
||||
};
|
||||
|
||||
// # line constants
|
||||
|
||||
const double ZONE = 10;
|
||||
const double ACC = 0.1;
|
||||
|
||||
// # line types
|
||||
|
||||
struct line_flags {
|
||||
bool red;
|
||||
bool invert;
|
||||
bool left_extended;
|
||||
bool right_extended;
|
||||
};
|
||||
|
||||
struct line_values {
|
||||
double dx;
|
||||
double dy;
|
||||
double invSqrDst;
|
||||
double nx;
|
||||
double ny;
|
||||
double lim1;
|
||||
double lim2;
|
||||
double accx;
|
||||
double accy;
|
||||
};
|
||||
|
||||
struct line {
|
||||
int32_t id;
|
||||
double x1;
|
||||
double y1;
|
||||
double x2;
|
||||
double y2;
|
||||
struct line_flags flags;
|
||||
struct line_values values;
|
||||
};
|
||||
|
||||
// # grid constants
|
||||
|
||||
const uint8_t GRID_62 = 0;
|
||||
const uint8_t GRID_61 = 1;
|
||||
const uint8_t GRID_60 = 2;
|
||||
|
||||
const double GRIDSIZE = 14;
|
||||
|
||||
#define GRID_ARRAY_BASE_SIZE 65536
|
||||
|
||||
// # grid types
|
||||
|
||||
struct grid_coordinate {
|
||||
int64_t x;
|
||||
int64_t y;
|
||||
};
|
||||
|
||||
struct grid_bucket {
|
||||
struct grid_coordinate key;
|
||||
struct grid_cell_entry* entry;
|
||||
struct grid_bucket* next;
|
||||
};
|
||||
|
||||
struct grid_cell_entry {
|
||||
struct line* line;
|
||||
struct grid_cell_entry* next;
|
||||
};
|
||||
|
||||
struct grid {
|
||||
struct grid_bucket** buckets;
|
||||
size_t capacity;
|
||||
size_t cells_total;
|
||||
};
|
||||
|
||||
// integer grid coordinate plus the fractional remainder from truncation
|
||||
struct grid_pos {
|
||||
struct grid_coordinate coord;
|
||||
double rx;
|
||||
double ry;
|
||||
};
|
||||
|
||||
// # simulation types
|
||||
|
||||
struct simulation {
|
||||
double start_x;
|
||||
double start_y;
|
||||
struct grid* grid;
|
||||
struct line* lines;
|
||||
size_t line_count;
|
||||
struct rider bosh;
|
||||
};
|
||||
|
||||
// # line function declarations
|
||||
|
||||
void line_calculate_values(struct line* line);
|
||||
|
||||
void line_collide(const struct line line, struct point* point);
|
||||
|
||||
// # grid function declarations
|
||||
|
||||
struct grid* grid_new(void);
|
||||
|
||||
void grid_free(struct grid* grid);
|
||||
|
||||
void grid_register_62(struct grid* grid, struct line* line);
|
||||
|
||||
void grid_register_61(struct grid* grid, struct line* line);
|
||||
|
||||
void grid_register_60(struct grid* grid, struct line* line);
|
||||
|
||||
struct grid_coordinate grid_coordinate_at(double x, double y);
|
||||
|
||||
struct grid_pos grid_pos_at(double x, double y);
|
||||
|
||||
// # rider function declarations
|
||||
|
||||
struct rider rider_new(double x, double y);
|
||||
|
||||
// # simulation function declarations
|
||||
|
||||
struct simulation simulation_new(void);
|
||||
|
||||
void simulation_prepare_line_capacity(struct simulation* simulation, size_t capacity);
|
||||
|
||||
void simulation_add_line(struct simulation* simulation, struct line line);
|
||||
|
||||
#ifdef LREF_H_IMPLEMENTATION
|
||||
|
||||
// # line function implementations
|
||||
|
||||
void line_calculate_values(struct line* line) {
|
||||
line->values.dx = line->x2 - line->x1;
|
||||
line->values.dy = line->y2 - line->y1;
|
||||
double sqrDst = (line->values.dx * line->values.dx + line->values.dy * line->values.dy);
|
||||
line->values.invSqrDst = 1.0 / sqrDst;
|
||||
double dst = sqrt(sqrDst);
|
||||
double invDst = 1.0 / dst;
|
||||
line->values.nx = line->values.dy * invDst * (line->flags.invert ? 1 : -1);
|
||||
line->values.ny = line->values.dx * invDst * (line->flags.invert ? -1 : 1);
|
||||
|
||||
double lim = fmin(0.25, ZONE / dst);
|
||||
line->values.lim1 = line->flags.left_extended ? -lim : 0;
|
||||
line->values.lim2 = line->flags.right_extended ? 1 + lim : 1;
|
||||
if (!line->flags.red) return;
|
||||
|
||||
line->values.accx = line->values.ny * ACC * (line->flags.invert ? 1 : -1);
|
||||
line->values.accy = line->values.nx * ACC * (line->flags.invert ? -1 : 1);
|
||||
}
|
||||
|
||||
void line_collide(const struct line line, struct point* point) {
|
||||
if (point->momentum_x * line.values.nx + point->momentum_y * line.values.ny <= 0.0) return;
|
||||
|
||||
double distx = point->x - line.x1;
|
||||
double disty = point->y - line.y1;
|
||||
double distance_perpendicular = line.values.nx * distx + line.values.ny * disty;
|
||||
double distance_along = (distx * line.values.dx + disty * line.values.dy) * line.values.invSqrDst;
|
||||
|
||||
if (distance_perpendicular > 0 && distance_perpendicular < ZONE &&
|
||||
distance_along >= line.values.lim1 && distance_along <= line.values.lim2) {
|
||||
point->x -= distance_perpendicular * line.values.nx;
|
||||
point->y -= distance_perpendicular * line.values.ny;
|
||||
|
||||
point->previous_x += line.values.ny * point->friction * distance_perpendicular
|
||||
* (point->previous_x < point->x ? 1 : -1) + (line.flags.red ? line.values.accx : 0);
|
||||
point->previous_y -= line.values.nx * point->friction * distance_perpendicular
|
||||
* (point->previous_y < point->y ? -1 : 1) + (line.flags.red ? line.values.accy : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// # grid hashtable function implementations
|
||||
|
||||
struct grid* grid_new(void) {
|
||||
struct grid* grid = calloc(1, sizeof(struct grid));
|
||||
grid->buckets = calloc(GRID_ARRAY_BASE_SIZE, sizeof(struct grid_bucket*));
|
||||
grid->capacity = GRID_ARRAY_BASE_SIZE;
|
||||
grid->cells_total = 0;
|
||||
return grid;
|
||||
}
|
||||
|
||||
uint64_t grid_hash(const struct grid_coordinate coords) {
|
||||
uint64_t x = coords.x;
|
||||
uint64_t y = coords.y;
|
||||
x ^= y + 0x9e3779b9 + (x << 6) + (x >> 2);
|
||||
return x;
|
||||
}
|
||||
|
||||
size_t grid_index(const struct grid grid, const struct grid_coordinate coords) {
|
||||
return (size_t)grid_hash(coords) & (grid.capacity - 1);
|
||||
}
|
||||
|
||||
bool grid_keys_eql(const struct grid_coordinate a, const struct grid_coordinate b) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
bool grid_contains_cell(const struct grid grid, const struct grid_coordinate coords) {
|
||||
size_t index = grid_index(grid, coords);
|
||||
struct grid_bucket* current = grid.buckets[index];
|
||||
while (current != NULL) {
|
||||
if (grid_keys_eql(current->key, coords)) return true;
|
||||
current = current->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void grid_put_ptr(struct grid* grid, struct grid_bucket* cell) {
|
||||
size_t index = grid_index(*grid, cell->key);
|
||||
struct grid_bucket* current = grid->buckets[index];
|
||||
if (current == NULL) {
|
||||
grid->buckets[index] = cell;
|
||||
cell->next = NULL;
|
||||
return;
|
||||
}
|
||||
while (current->next != NULL) {
|
||||
// if we are overwriting the old pointer
|
||||
// (i dont think this ever actually happens
|
||||
// in lref but for correctness, whatever)
|
||||
if (grid_keys_eql(current->next->key, cell->key)) {
|
||||
cell->next = current->next->next;
|
||||
current->next = cell;
|
||||
return;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
current->next = cell;
|
||||
cell->next = NULL;
|
||||
grid->cells_total += 1;
|
||||
}
|
||||
|
||||
bool grid_should_double_capacity(const struct grid grid) {
|
||||
return (grid.cells_total * 4 > grid.capacity * 3);
|
||||
}
|
||||
|
||||
void grid_double_capacity(struct grid* grid) {
|
||||
size_t old_capacity = grid->capacity;
|
||||
struct grid_bucket** old_buckets = grid->buckets;
|
||||
grid->capacity *= 2;
|
||||
grid->buckets = calloc(grid->capacity, sizeof(struct grid_bucket*));
|
||||
for (unsigned int i = 0; i < old_capacity; i++) {
|
||||
struct grid_bucket* next_bucket = old_buckets[i];
|
||||
while (next_bucket != NULL) {
|
||||
struct grid_bucket* current_bucket = next_bucket;
|
||||
next_bucket = current_bucket->next;
|
||||
grid_put_ptr(grid, current_bucket);
|
||||
}
|
||||
}
|
||||
free(old_buckets);
|
||||
}
|
||||
|
||||
struct grid_bucket* grid_cell_new(struct grid_coordinate coords) {
|
||||
struct grid_bucket* cell = calloc(1, sizeof(struct grid_bucket));
|
||||
cell->key = coords;
|
||||
cell->next = NULL;
|
||||
cell->entry = NULL;
|
||||
return cell;
|
||||
}
|
||||
|
||||
// get the cell at a given coordinate, creating it if it doesnt exist
|
||||
struct grid_bucket* grid_get_cell(struct grid* grid, const struct grid_coordinate coords) {
|
||||
size_t index = grid_index(*grid, coords);
|
||||
struct grid_bucket* current = grid->buckets[index];
|
||||
if (current == NULL) {
|
||||
grid->buckets[index] = grid_cell_new(coords);
|
||||
return grid->buckets[index];
|
||||
}
|
||||
|
||||
if (grid_keys_eql(current->key, coords)) return current;
|
||||
|
||||
while (current->next != NULL) {
|
||||
if (grid_keys_eql(current->next->key, coords)) return current->next;
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
current->next = grid_cell_new(coords);
|
||||
return current->next;
|
||||
}
|
||||
|
||||
struct grid_coordinate grid_coordinate_at(double x, double y) {
|
||||
struct grid_coordinate coord = { .x = (int64_t)floor(x / GRIDSIZE), .y = (int64_t)floor(y / GRIDSIZE) };
|
||||
return coord;
|
||||
}
|
||||
|
||||
struct grid_pos grid_pos_at(double x, double y) {
|
||||
struct grid_coordinate coord = grid_coordinate_at(x, y);
|
||||
struct grid_pos pos = {
|
||||
.coord = coord,
|
||||
.rx = x - GRIDSIZE * coord.x,
|
||||
.ry = y - GRIDSIZE * coord.y,
|
||||
};
|
||||
return pos;
|
||||
}
|
||||
|
||||
// # grid line registry function implementations
|
||||
|
||||
void grid_cell_add_line(struct grid_bucket* cell, struct line* line) {
|
||||
if (cell->entry == NULL) {
|
||||
struct grid_cell_entry* entry = calloc(1, sizeof(struct grid_cell_entry));
|
||||
entry->line = line;
|
||||
cell->entry = entry;
|
||||
return;
|
||||
}
|
||||
|
||||
struct grid_cell_entry* current = cell->entry;
|
||||
|
||||
if (current->line->id < line->id) {
|
||||
struct grid_cell_entry* entry = calloc(1, sizeof(struct grid_cell_entry));
|
||||
entry->line = line;
|
||||
entry->next = current;
|
||||
cell->entry = entry;
|
||||
return;
|
||||
}
|
||||
|
||||
while (current->next != NULL) {
|
||||
if (current->next->line->id < line->id) {
|
||||
struct grid_cell_entry* entry = calloc(1, sizeof(struct grid_cell_entry));
|
||||
entry->line = line;
|
||||
entry->next = current->next;
|
||||
current->next = entry;
|
||||
return;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
struct grid_cell_entry* entry = calloc(1, sizeof(struct grid_cell_entry));
|
||||
entry->line = line;
|
||||
current->next = entry;
|
||||
}
|
||||
|
||||
void grid_register_62(struct grid* grid, struct line* line) {
|
||||
struct grid_pos start = grid_pos_at(line->x1, line->y1);
|
||||
struct grid_pos end = grid_pos_at(line->x2, line->y2);
|
||||
|
||||
double right = line->values.dx > 0 ? end.coord.x : start.coord.x;
|
||||
double left = line->values.dx > 0 ? start.coord.x : end.coord.x;
|
||||
double bottom = line->values.dy > 0 ? end.coord.y : start.coord.y;
|
||||
double top = line->values.dy > 0 ? start.coord.y : end.coord.y;
|
||||
|
||||
grid_cell_add_line(grid_get_cell(grid, start.coord), line);
|
||||
|
||||
if ((line->values.dx == 0 && line->values.dy == 0) ||
|
||||
(start.coord.x == end.coord.x && start.coord.y == end.coord.y)) return;
|
||||
|
||||
double x = line->x1;
|
||||
double y = line->y1;
|
||||
double invDx = 1.0 / line->values.dx;
|
||||
double invDy = 1.0 / line->values.dy;
|
||||
|
||||
double difX;
|
||||
double difY;
|
||||
|
||||
while (true) {
|
||||
if (start.coord.x < 0) {
|
||||
difX = line->values.dx > 0 ? (GRIDSIZE + start.rx) : (-GRIDSIZE - start.rx);
|
||||
} else {
|
||||
difX = line->values.dx > 0 ? (GRIDSIZE - start.rx) : (-(start.rx + 1));
|
||||
}
|
||||
if (start.coord.y < 0) {
|
||||
difY = line->values.dy > 0 ? (GRIDSIZE + start.ry) : (-GRIDSIZE - start.ry);
|
||||
} else {
|
||||
difY = line->values.dy > 0 ? (GRIDSIZE - start.ry) : (-(start.ry + 1));
|
||||
}
|
||||
if (line->values.dx == 0) {
|
||||
y += difY;
|
||||
} else if (line->values.dy == 0) {
|
||||
x += difX;
|
||||
} else {
|
||||
double step = y + line->values.dy * difX * invDx;
|
||||
if (fabs(step - y) < fabs(difY)) {
|
||||
x += difX;
|
||||
y = step;
|
||||
} else if (fabs(step - y) == fabs(difY)) {
|
||||
x += difX;
|
||||
y += difY;
|
||||
} else {
|
||||
x += line->values.dx * difY * invDy;
|
||||
y += difY;
|
||||
}
|
||||
}
|
||||
start = grid_pos_at(x, y);
|
||||
if (start.coord.x >= left && start.coord.x <= right && start.coord.y >= top && start.coord.y <= bottom) {
|
||||
grid_cell_add_line(grid_get_cell(grid, start.coord), line);
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// # point function implementations
|
||||
|
||||
void point_apply_momentum(struct point* point) {
|
||||
point->momentum_x = point->x - point->previous_x;
|
||||
point->momentum_y = point->y - point->previous_y + gravity;
|
||||
point->previous_x = point->x;
|
||||
point->previous_y = point->y;
|
||||
point->x += point->momentum_x;
|
||||
point->y += point->momentum_y;
|
||||
}
|
||||
|
||||
// # rider function implementations
|
||||
|
||||
void rider_init_point(struct rider* bosh, uint8_t index, double x, double y, double friction) {
|
||||
bosh->points[index].x = x;
|
||||
bosh->points[index].y = y;
|
||||
bosh->points[index].previous_x = x - 0.4;
|
||||
bosh->points[index].previous_y = y;
|
||||
bosh->points[index].friction = friction;
|
||||
}
|
||||
|
||||
void rider_init_bone(struct rider* bosh, uint8_t index, uint8_t a, uint8_t b, bool breakable, bool repel) {
|
||||
bosh->bones[index].a = a;
|
||||
bosh->bones[index].b = b;
|
||||
bosh->bones[index].breakable = breakable;
|
||||
bosh->bones[index].repel = repel;
|
||||
double x = bosh->points[a].x - bosh->points[b].x;
|
||||
double y = bosh->points[a].y - bosh->points[b].y;
|
||||
bosh->bones[index].restLength = sqrt(x * x + y * y) * (repel ? 0.5 : 1.0);
|
||||
}
|
||||
|
||||
void rider_apply_start_offset(struct rider* bosh, double x, double y) {
|
||||
for (int i = 0; i < POINT_COUNT; i++) {
|
||||
struct point* point = &bosh->points[i];
|
||||
|
||||
point->x += x;
|
||||
point->y += y;
|
||||
point->previous_x = point->x - 0.4;
|
||||
point->previous_y = point->y;
|
||||
}
|
||||
}
|
||||
|
||||
struct rider rider_new(double x, double y) {
|
||||
struct rider bosh;
|
||||
memset(&bosh, 0, sizeof(bosh));
|
||||
|
||||
rider_init_point(&bosh, 0, 0.0, 0.0, 0.8);
|
||||
rider_init_point(&bosh, 1, 0.0, 5.0, 0.0);
|
||||
rider_init_point(&bosh, 2, 15.0, 5.0, 0.0);
|
||||
rider_init_point(&bosh, 3, 17.5, 0.0, 0.0);
|
||||
rider_init_point(&bosh, 4, 5.0, 0.0, 0.8);
|
||||
rider_init_point(&bosh, 5, 5.0, -5.5, 0.8);
|
||||
rider_init_point(&bosh, 6, 11.5, -5.0, 0.1);
|
||||
rider_init_point(&bosh, 7, 11.5, -5.0, 0.1);
|
||||
rider_init_point(&bosh, 8, 10.0, 5.0, 0.0);
|
||||
rider_init_point(&bosh, 9, 10.0, 5.0, 0.0);
|
||||
|
||||
rider_init_bone(&bosh, 0, 0, 1, false, false);
|
||||
rider_init_bone(&bosh, 1, 1, 2, false, false);
|
||||
rider_init_bone(&bosh, 2, 2, 3, false, false);
|
||||
rider_init_bone(&bosh, 3, 3, 0, false, false);
|
||||
rider_init_bone(&bosh, 4, 0, 2, false, false);
|
||||
rider_init_bone(&bosh, 5, 3, 1, false, false);
|
||||
rider_init_bone(&bosh, 6, 0, 4, true, false);
|
||||
rider_init_bone(&bosh, 7, 1, 4, true, false);
|
||||
rider_init_bone(&bosh, 8, 2, 4, true, false);
|
||||
rider_init_bone(&bosh, 9, 5, 4, false, false);
|
||||
rider_init_bone(&bosh, 10, 5, 6, false, false);
|
||||
rider_init_bone(&bosh, 11, 5, 7, false, false);
|
||||
rider_init_bone(&bosh, 12, 4, 8, false, false);
|
||||
rider_init_bone(&bosh, 13, 4, 9, false, false);
|
||||
rider_init_bone(&bosh, 14, 5, 7, false, false);
|
||||
rider_init_bone(&bosh, 15, 5, 0, true, false);
|
||||
rider_init_bone(&bosh, 16, 3, 6, true, false);
|
||||
rider_init_bone(&bosh, 17, 3, 7, true, false);
|
||||
rider_init_bone(&bosh, 18, 8, 2, true, false);
|
||||
rider_init_bone(&bosh, 19, 9, 2, true, false);
|
||||
rider_init_bone(&bosh, 20, 5, 8, false, true);
|
||||
rider_init_bone(&bosh, 21, 5, 9, false, true);
|
||||
|
||||
rider_apply_start_offset(&bosh, x, y);
|
||||
|
||||
return bosh;
|
||||
}
|
||||
|
||||
void rider_bone_satisfy_single(struct rider* bosh, uint8_t bone_idx) {
|
||||
struct bone bone = bosh->bones[bone_idx];
|
||||
struct point* a = &bosh->points[bone.a];
|
||||
struct point* b = &bosh->points[bone.b];
|
||||
double x = a->x - b->x;
|
||||
double y = a->y - b->y;
|
||||
double dist = sqrt(x * x + y * y);
|
||||
if (bone.repel && dist >= bone.restLength) return;
|
||||
double effect = (dist - bone.restLength) / dist * 0.5;
|
||||
if (bone.breakable) {
|
||||
if (effect > endurance * bone.restLength * 0.5) {
|
||||
bosh->dead = true;
|
||||
return;
|
||||
}
|
||||
if (bosh->dead) return;
|
||||
}
|
||||
double xEffect = x * effect;
|
||||
double yEffect = y * effect;
|
||||
a->x -= xEffect;
|
||||
a->y -= yEffect;
|
||||
b->x += xEffect;
|
||||
b->y += yEffect;
|
||||
}
|
||||
|
||||
void rider_bone_satisfy_all(struct rider* bosh) {
|
||||
for (uint8_t i = 0; i < BONE_COUNT; i++) {
|
||||
rider_bone_satisfy_single(bosh, i);
|
||||
}
|
||||
}
|
||||
|
||||
void rider_apply_momentum(struct rider* bosh) {
|
||||
for (uint8_t i = 0; i < POINT_COUNT; i++) {
|
||||
point_apply_momentum(&bosh->points[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void rider_fakie_check(struct rider* bosh) {
|
||||
double along_sled_x = bosh->points[3].x - bosh->points[0].x;
|
||||
double along_sled_y = bosh->points[3].y - bosh->points[0].y;
|
||||
if (along_sled_x * (bosh->points[1].y - bosh->points[0].y) - along_sled_y * (bosh->points[1].x - bosh->points[0].x) < 0) {
|
||||
bosh->dead = true;
|
||||
bosh->sledbroken = true;
|
||||
}
|
||||
if (along_sled_x * (bosh->points[5].y - bosh->points[4].y) - along_sled_y * (bosh->points[5].x - bosh->points[4].x) > 0) {
|
||||
bosh->dead = true;
|
||||
}
|
||||
}
|
||||
|
||||
// # simulation function implementations
|
||||
|
||||
struct simulation simulation_new(void) {
|
||||
struct simulation sim = {
|
||||
.grid = grid_new(),
|
||||
.bosh = rider_new(0, 0),
|
||||
};
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
void simulation_collide_single_point_and_grid_cell(struct point* point, struct grid_bucket* cell) {
|
||||
struct grid_cell_entry* current = cell->entry;
|
||||
while (current != NULL) {
|
||||
line_collide(*current->line, point);
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
void simulation_collide_all(struct simulation* simulation) {
|
||||
for (uint8_t i = 0; i < POINT_COUNT; i++) {
|
||||
struct point* point = &simulation->bosh.points[i];
|
||||
struct grid_coordinate base_coords = grid_pos_at(point->x, point->y).coord;
|
||||
for (int64_t x = -1; x < 2; x++) {
|
||||
for (int64_t y = -1; y < 2; y++) {
|
||||
struct grid_coordinate cell_coords = base_coords;
|
||||
cell_coords.x += x;
|
||||
cell_coords.y += y;
|
||||
struct grid_bucket* cell = grid_get_cell(simulation->grid, cell_coords);
|
||||
simulation_collide_single_point_and_grid_cell(point, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void simulation_step_frame(struct simulation* simulation) {
|
||||
rider_apply_momentum(&simulation->bosh);
|
||||
for (uint8_t i = 0; i < ITERATION_COUNT; i++) {
|
||||
rider_bone_satisfy_all(&simulation->bosh);
|
||||
simulation_collide_all(simulation);
|
||||
}
|
||||
rider_fakie_check(&simulation->bosh);
|
||||
}
|
||||
|
||||
void simulation_prepare_line_capacity(struct simulation* simulation, size_t capacity) {
|
||||
simulation->lines = realloc(simulation->lines, sizeof(struct line) * capacity);
|
||||
}
|
||||
|
||||
void simulation_add_line(struct simulation* simulation, struct line line) {
|
||||
line_calculate_values(&line);
|
||||
simulation->lines[simulation->line_count] = line;
|
||||
// TODO 61 & 60
|
||||
grid_register_62(simulation->grid, &simulation->lines[simulation->line_count]);
|
||||
simulation->line_count++;
|
||||
}
|
||||
|
||||
#endif
|
64
raylib_frontend.c
Normal file
64
raylib_frontend.c
Normal file
@ -0,0 +1,64 @@
|
||||
#define LREF_H_IMPLEMENTATION
|
||||
#include "lref.h"
|
||||
#include "track_wrapper/track_wrapper.h"
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) return EXIT_FAILURE;
|
||||
uint32_t index = atoi(argv[1]);
|
||||
struct simulation sim = simulation_new();
|
||||
load_sol_track(&sim, argv[2], index);
|
||||
bool playing = false;
|
||||
|
||||
const int screenWidth = 800;
|
||||
const int screenHeight = 450;
|
||||
|
||||
InitWindow(screenWidth, screenHeight, "lref test frontend");
|
||||
|
||||
Camera2D camera = { 0 };
|
||||
camera.offset = (Vector2){screenWidth/2.0f, screenHeight/2.0f};
|
||||
camera.rotation = 0.0f;
|
||||
camera.zoom = 4.0f;
|
||||
|
||||
SetTargetFPS(40);
|
||||
|
||||
while (!WindowShouldClose()) {
|
||||
Vector2 camera_target = {0};
|
||||
for (int i = 0; i < POINT_COUNT; i++) {
|
||||
camera_target.x += sim.bosh.points[i].x;
|
||||
camera_target.y += sim.bosh.points[i].y;
|
||||
}
|
||||
camera_target.x /= POINT_COUNT;
|
||||
camera_target.y /= POINT_COUNT;
|
||||
camera.target = camera_target;
|
||||
if (IsKeyPressed(KEY_SPACE)) {
|
||||
playing = !playing;
|
||||
}
|
||||
BeginDrawing();
|
||||
ClearBackground(WHITE);
|
||||
BeginMode2D(camera);
|
||||
{// draw bosh
|
||||
for (int i = 0; i < BONE_COUNT; i++) {
|
||||
struct bone bone = sim.bosh.bones[i];
|
||||
struct point A = sim.bosh.points[bone.a];
|
||||
struct point B = sim.bosh.points[bone.b];
|
||||
DrawLineV((Vector2){A.x, A.y}, (Vector2){B.x, B.y}, BLACK);
|
||||
}
|
||||
}
|
||||
{// draw lines
|
||||
for (size_t i = 0; i < sim.line_count; i++) {
|
||||
struct line line = sim.lines[i];
|
||||
DrawLineV((Vector2){line.x1, line.y1}, (Vector2){line.x2, line.y2}, BLACK);
|
||||
}
|
||||
}
|
||||
EndMode2D();
|
||||
EndDrawing();
|
||||
|
||||
if (playing || IsKeyPressed(KEY_RIGHT)) simulation_step_frame(&sim);
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
|
||||
return 0;
|
||||
}
|
59
track_wrapper/build.zig
Normal file
59
track_wrapper/build.zig
Normal file
@ -0,0 +1,59 @@
|
||||
const std = @import("std");
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// This creates a "module", which represents a collection of source files alongside
|
||||
// some compilation options, such as optimization mode and linked system libraries.
|
||||
// Every executable or library we compile will be based on one or more modules.
|
||||
const lib_mod = b.createModule(.{
|
||||
// `root_source_file` is the Zig "entry point" of the module. If a module
|
||||
// only contains e.g. external object files, you can make this `null`.
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.pic = true,
|
||||
});
|
||||
|
||||
// Now, we will create a static library based on the module we created above.
|
||||
// This creates a `std.Build.Step.Compile`, which is the build step responsible
|
||||
// for actually invoking the compiler.
|
||||
const lib = b.addLibrary(.{
|
||||
.linkage = .static,
|
||||
.name = "track_wrapper",
|
||||
.root_module = lib_mod,
|
||||
});
|
||||
|
||||
// This declares intent for the library to be installed into the standard
|
||||
// location when the user invokes the "install" step (the default step when
|
||||
// running `zig build`).
|
||||
b.installArtifact(lib);
|
||||
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_module = lib_mod,
|
||||
});
|
||||
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
}
|
47
track_wrapper/build.zig.zon
Normal file
47
track_wrapper/build.zig.zon
Normal file
@ -0,0 +1,47 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .track_wrapper,
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x16ca1ccf830cc3bc, // Changing this has security and trust implications.
|
||||
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.minimum_zig_version = "0.14.1",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
184
track_wrapper/src/amf0.zig
Normal file
184
track_wrapper/src/amf0.zig
Normal file
@ -0,0 +1,184 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Type = AmfType;
|
||||
pub const AmfType = enum(u8) {
|
||||
Number = 0,
|
||||
Bool = 1,
|
||||
String = 2,
|
||||
Object = 3,
|
||||
// MovieClip = 4,
|
||||
Null = 5,
|
||||
Undefined = 6,
|
||||
// Reference = 7,
|
||||
Array = 8,
|
||||
ObjectEnd = 9,
|
||||
// StrictArray = 10,
|
||||
// Date = 11,
|
||||
// LongString = 12,
|
||||
// Unsupported = 13,
|
||||
// RecordSet = 14,
|
||||
// Xml = 15,
|
||||
// TypedObject = 16,
|
||||
|
||||
pub fn zigTypeOfValue(t: AmfType) type {
|
||||
return switch (t) {
|
||||
.Number => f64,
|
||||
.Bool => bool,
|
||||
.String => []u8,
|
||||
.Object => std.StringHashMap(AmfValue),
|
||||
.Null, .Undefined, .ObjectEnd => void,
|
||||
.Array => std.ArrayList(AmfValue),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Number = AmfType.zigTypeOfValue(.Number);
|
||||
pub const Bool = AmfType.zigTypeOfValue(.Bool);
|
||||
pub const String = AmfType.zigTypeOfValue(.String);
|
||||
pub const Object = AmfType.zigTypeOfValue(.Object);
|
||||
pub const Array = AmfType.zigTypeOfValue(.Array);
|
||||
|
||||
pub const Value = AmfValue;
|
||||
pub const AmfValue = struct {
|
||||
amf_type: AmfType,
|
||||
data: *anyopaque,
|
||||
name: []u8,
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
pub fn expectAs(self: AmfValue, comptime t: AmfType) !AmfType.zigTypeOfValue(t) {
|
||||
if (self.amf_type != t) return error.TypeMismatch;
|
||||
return @as(*AmfType.zigTypeOfValue(t), @ptrCast(@alignCast(self.data))).*;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
switch (self.amf_type) {
|
||||
.Object => {
|
||||
var obj: *Object = @ptrCast(@alignCast(self.data));
|
||||
var iter = obj.valueIterator();
|
||||
while (iter.next()) |field_amf| {
|
||||
deinit(field_amf);
|
||||
}
|
||||
},
|
||||
.Array => {
|
||||
const arr: *Array = @ptrCast(@alignCast(self.data));
|
||||
for (arr.items) |*item_amf| {
|
||||
deinit(item_amf);
|
||||
}
|
||||
},
|
||||
.String => {
|
||||
const contents: *[]u8 = @as(*[]u8, @ptrCast(@alignCast(self.data)));
|
||||
self.alloc.free(contents.*);
|
||||
},
|
||||
.Bool => {
|
||||
self.alloc.destroy(@as(*bool, @ptrCast(self.data)));
|
||||
},
|
||||
.Number => {
|
||||
self.alloc.destroy(@as(*f64, @ptrCast(@alignCast(self.data))));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
self.alloc.free(self.name);
|
||||
}
|
||||
|
||||
//pub fn readObjectFields(alloc: std.mem.Allocator, reader: anytype, object: *AmfValue) !void {
|
||||
// if (object.amf_type != .Object) return error.PassedNonObject;
|
||||
// const fields: *Object = @ptrCast(@alignCast(object.data));
|
||||
// while (true) {
|
||||
// const val = try read(alloc, reader, false);
|
||||
// if (val.amf_type == .ObjectEnd) break;
|
||||
// try fields.put(val.name, val);
|
||||
// }
|
||||
//}
|
||||
|
||||
//pub fn readArrayItems(alloc: std.mem.Allocator, reader: anytype, array: *AmfValue) !void {
|
||||
// if (array.amf_type != .Array) return error.PassedNonArray;
|
||||
// const items: *Array = @ptrCast(array.data);
|
||||
// while (true) {
|
||||
// const val = try read(alloc, reader, false);
|
||||
// if (val.amf_type == .ObjectEnd) break;
|
||||
// try items.append(alloc, val);
|
||||
// }
|
||||
//}
|
||||
|
||||
pub fn read(alloc: std.mem.Allocator, reader: anytype) !AmfValue {
|
||||
const name: []u8 = try alloc.alloc(u8, std.math.cast(usize, try reader.readInt(i16, .big)) orelse return error.CastError);
|
||||
_ = try reader.readAll(name);
|
||||
const amf_t = std.meta.intToEnum(AmfType, reader.readInt(u8, .big) catch return error.DoneReading) catch return error.UnimplementedAmfType;
|
||||
|
||||
var result = AmfValue{ .name = name, .amf_type = amf_t, .alloc = alloc, .data = undefined };
|
||||
switch (amf_t) {
|
||||
.Number => {
|
||||
const ptr = try alloc.create(Number);
|
||||
ptr.* = @bitCast(try reader.readInt(u64, .big));
|
||||
result.data = @ptrCast(ptr);
|
||||
},
|
||||
.Bool => {
|
||||
const ptr = try alloc.create(Bool);
|
||||
ptr.* = try reader.readByte() != 0;
|
||||
result.data = @ptrCast(ptr);
|
||||
},
|
||||
.String => {
|
||||
const len = try reader.readInt(i16, .big);
|
||||
const ptr = try alloc.create(String);
|
||||
ptr.* = try alloc.alloc(u8, std.math.cast(usize, len) orelse return error.CastError);
|
||||
_ = try reader.readAll(ptr.*);
|
||||
result.data = @ptrCast(ptr);
|
||||
},
|
||||
.Object => {
|
||||
const ptr = try alloc.create(Object);
|
||||
ptr.* = Object.init(alloc);
|
||||
result.data = @ptrCast(ptr);
|
||||
//try readObjectFields(alloc, reader, &result);
|
||||
const fields: *Object = ptr;
|
||||
while (true) {
|
||||
const val = try read(alloc, reader);
|
||||
if (val.amf_type == .ObjectEnd) break;
|
||||
try fields.put(val.name, val);
|
||||
}
|
||||
},
|
||||
.Null, .Undefined, .ObjectEnd => {},
|
||||
.Array => {
|
||||
const ptr = try alloc.create(Array);
|
||||
const len = try reader.readInt(i32, .big);
|
||||
ptr.* = try Array.initCapacity(alloc, std.math.cast(usize, len) orelse return error.CastError);
|
||||
result.data = @ptrCast(ptr);
|
||||
//try readArrayItems(alloc, reader, result);
|
||||
const items: *Array = ptr;
|
||||
while (true) {
|
||||
const val = try read(alloc, reader);
|
||||
if (val.amf_type == .ObjectEnd) break;
|
||||
try items.append(val);
|
||||
}
|
||||
},
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getProperty(self: AmfValue, name: []const u8) !AmfValue {
|
||||
if (self.amf_type != .Object) return error.PassedNonObject;
|
||||
const fields: *Object = @ptrCast(@alignCast(self.data));
|
||||
const prop: AmfValue = fields.get(name) orelse return error.NonexistantProperty;
|
||||
return prop;
|
||||
}
|
||||
|
||||
pub fn getPropertyExpectType(self: AmfValue, name: []const u8, comptime expected_type: AmfType) !AmfType.zigTypeOfValue(expected_type) {
|
||||
const prop = try self.getProperty(name);
|
||||
if (prop.amf_type != expected_type) return error.TypeMismatch;
|
||||
const ptr: *AmfType.zigTypeOfValue(expected_type) = @ptrCast(@alignCast(prop.data));
|
||||
return ptr.*;
|
||||
}
|
||||
|
||||
pub fn getItem(self: AmfValue, index: usize) !AmfValue {
|
||||
if (self.amf_type != .Array) return error.PassedNonArray;
|
||||
const arr: *Array = @ptrCast(@alignCast(self.data));
|
||||
if (index >= arr.items.len) return error.OutOfBounds;
|
||||
return arr.items[index];
|
||||
}
|
||||
|
||||
pub fn getItemExpectType(self: AmfValue, index: usize, comptime expected_type: AmfType) !AmfType.zigTypeOfValue(expected_type) {
|
||||
const item = try self.getItem(index);
|
||||
if (item.amf_type != expected_type) return error.TypeMismatch;
|
||||
const ptr: *AmfType.zigTypeOfValue(expected_type) = @ptrCast(@alignCast(item.data));
|
||||
return ptr.*;
|
||||
}
|
||||
};
|
1128
track_wrapper/src/include.zig
Normal file
1128
track_wrapper/src/include.zig
Normal file
File diff suppressed because it is too large
Load Diff
62
track_wrapper/src/root.zig
Normal file
62
track_wrapper/src/root.zig
Normal file
@ -0,0 +1,62 @@
|
||||
const std = @import("std");
|
||||
const c = @import("include.zig");
|
||||
const sol = @import("sol.zig");
|
||||
const amf = @import("amf0.zig");
|
||||
|
||||
pub export fn load_sol_track(sim: [*c]c.struct_simulation, filename: [*c]u8, index: u32) void {
|
||||
const file = std.fs.cwd().openFile(std.mem.sliceTo(filename, 0), .{}) catch return;
|
||||
defer file.close();
|
||||
var sol_f = sol.readSol(std.heap.smp_allocator, file.reader()) catch return;
|
||||
defer sol_f.deinit();
|
||||
const tracks = sol.getTracks(sol_f) catch return;
|
||||
const track = tracks.items[index];
|
||||
const startprop = track.getProperty("startLine") catch return;
|
||||
const lines: amf.Array = track.getPropertyExpectType("data", .Array) catch return;
|
||||
if (startprop.amf_type == .Number) {
|
||||
const ID: u32 = std.math.cast(u32, @as(u32, @intFromFloat(@as(*f64, @ptrCast(@alignCast(startprop.data))).*))) orelse return;
|
||||
|
||||
var maybe_line: ?amf.AmfValue = null;
|
||||
|
||||
for (lines.items) |iter_line| {
|
||||
const iter_ID: u32 = std.math.cast(u32, @as(u32, @intFromFloat(iter_line.getItemExpectType(8, .Number) catch return))) orelse return;
|
||||
if (iter_ID == ID) {
|
||||
maybe_line = iter_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (maybe_line) |amf_line| {
|
||||
const line = sol.Line.fromAmf(amf_line) catch return;
|
||||
sim.*.bosh = c.rider_new(line.x1, line.y1 - 25.0);
|
||||
} else {
|
||||
sim.*.bosh = c.rider_new(100.0, 100.0);
|
||||
}
|
||||
} else {
|
||||
if (startprop.amf_type != .Array) return;
|
||||
const x = startprop.getItemExpectType(0, .Number) catch return;
|
||||
const y = startprop.getItemExpectType(1, .Number) catch return;
|
||||
sim.*.bosh = c.rider_new(x, y);
|
||||
}
|
||||
|
||||
c.simulation_prepare_line_capacity(sim, lines.items.len);
|
||||
|
||||
for (lines.items) |amf_line| {
|
||||
const line = sol.Line.fromAmf(amf_line) catch continue;
|
||||
if (line.linetype == .Scenery) continue;
|
||||
const simline = c.struct_line{
|
||||
.id = std.math.cast(i32, line.ID) orelse continue,
|
||||
.x1 = line.x1,
|
||||
.y1 = line.y1,
|
||||
.x2 = line.x2,
|
||||
.y2 = line.y2,
|
||||
.flags = .{
|
||||
.red = line.linetype == .Acceleration,
|
||||
.invert = line.invert,
|
||||
.left_extended = line.left_extended,
|
||||
.right_extended = line.right_extended,
|
||||
},
|
||||
};
|
||||
c.simulation_add_line(sim, simline);
|
||||
}
|
||||
// no fucking way !!!!!! its hypothetically done
|
||||
}
|
73
track_wrapper/src/sol.zig
Normal file
73
track_wrapper/src/sol.zig
Normal file
@ -0,0 +1,73 @@
|
||||
const std = @import("std");
|
||||
const amf = @import("amf0.zig");
|
||||
|
||||
pub const Track = amf.Value;
|
||||
|
||||
pub fn readSol(alloc: std.mem.Allocator, reader: anytype) !amf.AmfValue {
|
||||
const sol_version = try reader.readInt(i16, .big);
|
||||
const length = try reader.readInt(i32, .big);
|
||||
_ = sol_version;
|
||||
_ = length;
|
||||
if (try reader.readInt(i32, .big) != 0x5443534F) return error.InvalidMagicNumber;
|
||||
try reader.skipBytes(6, .{});
|
||||
const name: []u8 = try alloc.alloc(u8, std.math.cast(usize, try reader.readInt(i16, .big)) orelse return error.CastError);
|
||||
_ = try reader.readAll(name);
|
||||
if (!std.mem.eql(u8, name, "savedLines")) return error.InvalidSolName;
|
||||
if (try reader.readInt(i32, .big) != 0) return error.InvalidAmfVersion;
|
||||
const ptr = try alloc.create(amf.Object);
|
||||
ptr.* = amf.Object.init(alloc);
|
||||
const amf_obj = amf.AmfValue{ .name = name, .alloc = alloc, .amf_type = .Object, .data = @ptrCast(ptr) };
|
||||
const fields: *amf.Object = ptr;
|
||||
while (true) {
|
||||
const val = amf.AmfValue.read(alloc, reader) catch |err| {
|
||||
if (err == error.EndOfStream) break;
|
||||
return err;
|
||||
};
|
||||
if (val.amf_type == .ObjectEnd) break;
|
||||
try fields.put(val.name, val);
|
||||
}
|
||||
return amf_obj;
|
||||
}
|
||||
|
||||
pub fn getTracks(root: amf.AmfValue) !amf.Array {
|
||||
return root.getPropertyExpectType("trackList", .Array);
|
||||
}
|
||||
|
||||
pub const LineType = enum(u2) {
|
||||
Standard = 0,
|
||||
Acceleration = 1,
|
||||
Scenery = 2,
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
x1: f64,
|
||||
y1: f64,
|
||||
x2: f64,
|
||||
y2: f64,
|
||||
invert: bool,
|
||||
ID: u32,
|
||||
linetype: LineType,
|
||||
left_extended: bool,
|
||||
right_extended: bool,
|
||||
|
||||
pub const Extension = packed struct(u2) {
|
||||
left: bool,
|
||||
right: bool,
|
||||
};
|
||||
|
||||
pub fn fromAmf(line: amf.Value) !Line {
|
||||
//std.debug.print("line: {any} 5: {any} 0: {any}", .{ line.amf_type, (try line.getItem(4)).amf_type, (try line.getItem(0)).amf_type });
|
||||
const extensions: Extension = @bitCast(@as(u2, @intFromFloat(line.getItemExpectType(4, .Number) catch 0)));
|
||||
return Line{
|
||||
.x1 = try line.getItemExpectType(0, .Number),
|
||||
.y1 = try line.getItemExpectType(1, .Number),
|
||||
.x2 = try line.getItemExpectType(2, .Number),
|
||||
.y2 = try line.getItemExpectType(3, .Number),
|
||||
.invert = (line.getItemExpectType(5, .Number) catch 0) != 0,
|
||||
.left_extended = extensions.left,
|
||||
.right_extended = extensions.right,
|
||||
.ID = @as(u32, @intFromFloat(try line.getItemExpectType(8, .Number))),
|
||||
.linetype = @enumFromInt(@as(u2, @intFromFloat(try line.getItemExpectType(9, .Number)))),
|
||||
};
|
||||
}
|
||||
};
|
10
track_wrapper/track_wrapper.h
Normal file
10
track_wrapper/track_wrapper.h
Normal file
@ -0,0 +1,10 @@
|
||||
// i already wrote sol parsing in zig so
|
||||
// this is just some stupid glue code to
|
||||
// use that instead of dealing with it
|
||||
// all over again in c
|
||||
|
||||
// this is scuffed but um i uncomment this is i need to generate include.zig
|
||||
// but leave it commented for when ralib_crontend.c includes it
|
||||
// #include "lref.h"
|
||||
|
||||
void load_sol_track(struct simulation* sim, char* filename, uint32_t index);
|
Loading…
x
Reference in New Issue
Block a user