Skip to content

Commit f2069a7

Browse files
committed
exprimental for fun project
1 parent 2529dd8 commit f2069a7

2 files changed

Lines changed: 393 additions & 0 deletions

File tree

pufferlib/ocean/scape/scape.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* Pure C demo file for Scape. Build it with:
2+
* bash scripts/build_ocean.sh scape local (debug)
3+
* bash scripts/build_ocean.sh scape fast
4+
* We suggest building and debugging your env in pure C first. You
5+
* get faster builds and better error messages
6+
*/
7+
#include "scape.h"
8+
9+
int main() {
10+
int num_agents = 1;
11+
int num_obs = 1;
12+
13+
Scape env = {
14+
.width = 1080,
15+
.height = 720,
16+
.num_agents = 1
17+
};
18+
init(&env);
19+
20+
// Allocate these manually since they aren't being passed from Python
21+
env.observations = calloc(env.num_agents*num_obs, sizeof(float));
22+
env.actions = calloc(env.num_agents, sizeof(double));
23+
env.rewards = calloc(env.num_agents, sizeof(float));
24+
env.terminals = calloc(env.num_agents, sizeof(double));
25+
26+
// Always call reset and render first
27+
c_reset(&env);
28+
c_render(&env);
29+
30+
// while(True) will break web builds
31+
while (!WindowShouldClose()) {
32+
for (int i=0; i<env.num_agents; i++) {
33+
env.actions[i] = rand() % 9;
34+
}
35+
36+
c_step(&env);
37+
for (int frame=0; frame<36; frame++) {
38+
c_render(&env);
39+
}
40+
}
41+
42+
// Try to clean up after yourself
43+
free(env.observations);
44+
free(env.actions);
45+
free(env.rewards);
46+
free(env.terminals);
47+
c_close(&env);
48+
}
49+

pufferlib/ocean/scape/scape.h

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/* Scape: a sample multiagent env about puffers eating stars.
2+
* Use this as a tutorial and template for your own multiagent envs.
3+
* We suggest starting with the Squared env for a simpler intro.
4+
* Star PufferLib on GitHub to support. It really, really helps!
5+
*/
6+
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#include <math.h>
10+
#include <stdio.h>
11+
#include "raylib.h"
12+
#include "rlgl.h"
13+
14+
static float offset_x = -6.5f;
15+
static float offset_z = 12.5f;
16+
17+
static Vector3 scene_pos = (Vector3){-7.5, -2.5, 61.5};
18+
19+
// Required struct. Only use floats!
20+
typedef struct {
21+
float perf; // Recommended 0-1 normalized single real number perf metric
22+
float score; // Recommended unnormalized single real number perf metric
23+
float episode_return; // Recommended metric: sum of agent rewards over episode
24+
float episode_length; // Recommended metric: number of steps of agent episode
25+
// Any extra fields you add here may be exported to Python in binding.c
26+
float n; // Required as the last field
27+
} Log;
28+
29+
typedef struct {
30+
Texture2D puffer;
31+
Camera3D camera;
32+
Model scene;
33+
Model player;
34+
} Client;
35+
36+
typedef struct {
37+
float x;
38+
float y;
39+
float dest_x;
40+
float dest_y;
41+
} Agent;
42+
43+
typedef struct {
44+
float x;
45+
float y;
46+
} Goal;
47+
48+
// Required that you have some struct for your env
49+
// Recommended that you name it the same as the env file
50+
typedef struct {
51+
Log log; // Required field. Env binding code uses this to aggregate logs
52+
Client* client;
53+
Agent* agents;
54+
Goal* goals;
55+
float* observations; // Required. You can use any obs type, but make sure it matches in Python!
56+
double* actions; // Required. double* for discrete/multidiscrete, float* for box
57+
float* rewards; // Required
58+
float* terminals; // Required. We don't yet have truncations as standard yet
59+
int width;
60+
int height;
61+
int num_agents;
62+
} Scape;
63+
64+
Vector3 to_world(Vector2 pos) {
65+
return (Vector3){
66+
.x = pos.x - 6.5f,
67+
.y = -2.5f,
68+
.z = pos.y + 12.5f
69+
};
70+
}
71+
72+
Vector2 from_world(Vector3 pos) {
73+
return (Vector2){
74+
.x = pos.x + 6.5f,
75+
.y = pos.z - 12.5f
76+
};
77+
}
78+
79+
float clampf(float x, float min, float max) {
80+
if (x < min) return min;
81+
if (x > max) return max;
82+
return x;
83+
}
84+
85+
void move_agent(Scape* env) {
86+
float dx = env->agents[0].dest_x - env->agents[0].x;
87+
float dy = env->agents[0].dest_y - env->agents[0].y;
88+
env->agents[0].x += clampf(dx, -2, 2);
89+
env->agents[0].y += clampf(dy, -2, 2);
90+
}
91+
92+
93+
/* Recommended to have an init function of some kind if you allocate
94+
* extra memory. This should be freed by c_close. Don't forget to call
95+
* this in binding.c!
96+
*/
97+
void init(Scape* env) {
98+
env->agents = calloc(env->num_agents, sizeof(Agent));
99+
}
100+
101+
/* Recommended to have an observation function of some kind because
102+
* you need to compute agent observations in both reset and in step.
103+
* If using float obs, try to normalize to roughly -1 to 1 by dividing
104+
* by an appropriate constant.
105+
*/
106+
void compute_observations(Scape* env) {
107+
}
108+
109+
// Required function
110+
void c_reset(Scape* env) {
111+
env->agents[0].x = 28;
112+
env->agents[0].dest_x = 28;
113+
env->agents[0].y = 17;
114+
env->agents[0].dest_y = 17;
115+
compute_observations(env);
116+
}
117+
118+
// Required function
119+
void c_step(Scape* env) {
120+
for (int i=0; i<env->num_agents; i++) {
121+
env->rewards[i] = 0;
122+
Agent* agent = &env->agents[i];
123+
}
124+
move_agent(env);
125+
compute_observations(env);
126+
}
127+
128+
void handle_camera_controls(Client* client) {
129+
static Vector2 prev_mouse_pos = {0};
130+
static bool is_dragging = false;
131+
float camera_move_speed = 0.5f;
132+
133+
// Handle mouse drag for camera movement
134+
if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {
135+
prev_mouse_pos = GetMousePosition();
136+
is_dragging = true;
137+
}
138+
139+
if (IsMouseButtonReleased(MOUSE_BUTTON_MIDDLE)) {
140+
is_dragging = false;
141+
}
142+
143+
if (is_dragging) {
144+
Vector2 current_mouse_pos = GetMousePosition();
145+
Vector2 delta = {
146+
-(current_mouse_pos.x - prev_mouse_pos.x) * camera_move_speed,
147+
(current_mouse_pos.y - prev_mouse_pos.y) * camera_move_speed
148+
};
149+
150+
// Apply 45-degree rotation to the movement
151+
// For a -45 degree rotation (clockwise)
152+
float cos45 = -0.7071f; // cos(-45°)
153+
float sin45 = 0.7071f; // sin(-45°)
154+
Vector2 rotated_delta = {
155+
delta.x * cos45 - delta.y * sin45,
156+
delta.x * sin45 + delta.y * cos45
157+
};
158+
159+
// Update camera position (only X and Y)
160+
client->camera.position.z += rotated_delta.x;
161+
client->camera.position.x += rotated_delta.y;
162+
163+
// Update camera target (only X and Y)
164+
client->camera.target.z += rotated_delta.x;
165+
client->camera.target.x += rotated_delta.y;
166+
167+
prev_mouse_pos = current_mouse_pos;
168+
}
169+
170+
// Handle mouse wheel for zoom
171+
float wheel = GetMouseWheelMove();
172+
if (wheel != 0) {
173+
float zoom_factor = 1.0f - (wheel * 0.1f);
174+
// Calculate the current direction vector from target to position
175+
Vector3 direction = {
176+
client->camera.position.x - client->camera.target.x,
177+
client->camera.position.y - client->camera.target.y,
178+
client->camera.position.z - client->camera.target.z
179+
};
180+
181+
// Scale the direction vector by the zoom factor
182+
direction.x *= zoom_factor;
183+
direction.y *= zoom_factor;
184+
direction.z *= zoom_factor;
185+
186+
// Update the camera position based on the scaled direction
187+
client->camera.position.x = client->camera.target.x + direction.x;
188+
client->camera.position.y = client->camera.target.y + direction.y;
189+
client->camera.position.z = client->camera.target.z + direction.z;
190+
}
191+
}
192+
193+
Vector2 get_hovered_tile(Camera3D* camera) {
194+
Ray ray = GetMouseRay(GetMousePosition(), *camera);
195+
float plane_y = -0.5f;
196+
float t = (plane_y - ray.position.y) / ray.direction.y;
197+
Vector3 floor_pos = {
198+
.x = ray.position.x + ray.direction.x*t,
199+
.y = plane_y,
200+
.z = ray.position.z + ray.direction.z*t
201+
};
202+
return (Vector2){
203+
.x = floorf(floor_pos.x + 0.5f),
204+
.y = floorf(floor_pos.z + 0.5f)
205+
};
206+
}
207+
208+
Vector3 tile_to_mesh_pos(float x, float y) {
209+
return (Vector3){
210+
.x = x,
211+
.y = -0.49f,
212+
.z = y
213+
};
214+
}
215+
216+
void draw_tile(Client* client, Vector2 hovered_tile) {
217+
Vector3 corner = tile_to_mesh_pos(hovered_tile.x, hovered_tile.y);
218+
219+
Vector3 w00 = (Vector3){corner.x - 0.5, corner.y, corner.z + 0.5};
220+
Vector3 w01 = (Vector3){corner.x + 0.5, corner.y, corner.z + 0.5};
221+
Vector3 w10 = (Vector3){corner.x + 0.5, corner.y, corner.z - 0.5};
222+
Vector3 w11 = (Vector3){corner.x - 0.5, corner.y, corner.z - 0.5};
223+
224+
Vector2 s00 = GetWorldToScreen(w00, client->camera);
225+
Vector2 s01 = GetWorldToScreen(w01, client->camera);
226+
Vector2 s10 = GetWorldToScreen(w10, client->camera);
227+
Vector2 s11 = GetWorldToScreen(w11, client->camera);
228+
229+
DrawLineEx(s00, s01, 2.0f, RED);
230+
DrawLineEx(s00, s11, 2.0f, RED);
231+
DrawLineEx(s10, s11, 2.0f, RED);
232+
DrawLineEx(s10, s01, 2.0f, RED);
233+
}
234+
235+
236+
// Required function. Should handle creating the client on first call
237+
void c_render(Scape* env) {
238+
Client* client = env->client;
239+
if (client == NULL) {
240+
InitWindow(env->width, env->height, "PufferLib Scape");
241+
SetTargetFPS(60);
242+
243+
client = (Client*)calloc(1, sizeof(Client));
244+
env->client = client;
245+
246+
// Don't do this before calling InitWindow
247+
client->puffer = LoadTexture("resources/shared/puffers_128.png");
248+
249+
Camera3D camera = { 0 };
250+
camera.position = (Vector3){ 28.0f, 10.0f, 12.0f };
251+
camera.target = (Vector3){ 28.0f, -0.5, 17.0f };
252+
//camera.position = (Vector3){ 25.0f, 20.0f, 0.0f };
253+
//camera.target = (Vector3){ 25.0f, 0.0, 28.0f };
254+
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
255+
camera.fovy = 70.0f;
256+
camera.projection = CAMERA_PERSPECTIVE;
257+
client->camera = camera;
258+
client->scene = LoadModel("resources/scape/inferno_compress.glb");
259+
client->player = LoadModel("resources/scape/player_twisted_bow_decompressed.glb");
260+
}
261+
262+
handle_camera_controls(client);
263+
264+
if (IsKeyDown(KEY_RIGHT)) offset_x += 0.1f;
265+
if (IsKeyDown(KEY_LEFT)) offset_x -= 0.1f;
266+
if (IsKeyDown(KEY_UP)) offset_z -= 0.1f;
267+
if (IsKeyDown(KEY_DOWN)) offset_z += 0.1f;
268+
269+
// Standard across our envs so exiting is always the same
270+
if (IsKeyDown(KEY_ESCAPE)) {
271+
exit(0);
272+
}
273+
274+
BeginDrawing();
275+
ClearBackground((Color){6, 24, 24, 255});
276+
277+
BeginMode3D(client->camera);
278+
Vector2 origin = (Vector2){0.0f, 57.0f};
279+
//DrawModel(env->client->scene, to_world(origin), 1.0f, WHITE);
280+
if (IsKeyPressed(KEY_W)) scene_pos.x += 1.0f;
281+
if (IsKeyPressed(KEY_S)) scene_pos.x -= 1.0f;
282+
if (IsKeyPressed(KEY_A)) scene_pos.z -= 1.0f;
283+
if (IsKeyPressed(KEY_D)) scene_pos.z += 1.0f;
284+
285+
DrawModel(env->client->scene, (Vector3)scene_pos, 1.0f, WHITE);
286+
/*
287+
rlDisableBackfaceCulling();
288+
DrawModelEx(env->client->scene,
289+
(Vector3){ -6.0f, -2.5f, 12.0f },
290+
(Vector3){ 1.0f, 0.0f, 1.0f },
291+
0.0f,
292+
(Vector3){ 1.0f, 1.0f, -1.0f },
293+
WHITE);
294+
rlEnableBackfaceCulling();
295+
*/
296+
//DrawModel(env->client->player, (Vector3){ 32.0f, 10.0f, -26.0f }, 1.0f, WHITE);
297+
298+
299+
//Vector2 spawn = (Vector2){28.0f, 17.0f};
300+
//Vector3 player_pos = to_world(spawn);
301+
Vector3 player_pos = (Vector3){env->agents[0].x, 0, env->agents[0].y};
302+
DrawCube(player_pos, 1.0f, 1.0f, 1.0f, RED);
303+
304+
DrawCube((Vector3){21, 0, 37}, 3, 6, 3, BLUE);
305+
DrawCube((Vector3){11, 0, 23}, 3, 6, 3, GREEN);
306+
DrawCube((Vector3){28, 0, 21}, 3, 6, 3, RED);
307+
308+
// Draw axes
309+
DrawLine3D((Vector3){0, 0, 0}, (Vector3){50, 0, 0}, RED);
310+
DrawLine3D((Vector3){0, 0, 0}, (Vector3){0, 0, 50}, BLUE);
311+
DrawLine3D((Vector3){0, 0, 0}, (Vector3){0, 50, 0}, GREEN);
312+
313+
EndMode3D();
314+
315+
Vector2 hovered_tile = get_hovered_tile(&client->camera);
316+
draw_tile(client, hovered_tile);
317+
318+
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
319+
env->agents[0].dest_x = hovered_tile.x;
320+
env->agents[0].dest_y = hovered_tile.y;
321+
}
322+
323+
DrawText(TextFormat("scene_pos.x: %f scene_pos.z: %f", scene_pos.x, scene_pos.z), 10, 30, 20, RAYWHITE);
324+
DrawText(TextFormat("offset_x: %f offset_z: %f", offset_x, offset_z), 10, 10, 20, RAYWHITE);
325+
326+
for (int i=0; i<env->num_agents; i++) {
327+
Agent* agent = &env->agents[i];
328+
}
329+
330+
EndDrawing();
331+
}
332+
333+
// Required function. Should clean up anything you allocated
334+
// Do not free env->observations, actions, rewards, terminals
335+
void c_close(Scape* env) {
336+
free(env->agents);
337+
free(env->goals);
338+
if (env->client != NULL) {
339+
Client* client = env->client;
340+
UnloadTexture(client->puffer);
341+
CloseWindow();
342+
free(client);
343+
}
344+
}

0 commit comments

Comments
 (0)