Skip to content

Commit 841a3f3

Browse files
committed
Implement simple object picking.
1 parent 8f7c449 commit 841a3f3

12 files changed

Lines changed: 266 additions & 33 deletions

File tree

src/editor/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ set(PROJECT_SOURCES
1010
src/editor_camera.c
1111
src/editor.h
1212
src/editor_camera.h
13+
src/obj_picking.c
14+
src/obj_picking.h
1315
src/ui/editor_ui.c
1416
src/ui/editor_ui.h
1517
src/ui/theme.h

src/editor/src/editor.c

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <ui/file_dialog.h>
1919
#include <ui/editor_ui.h>
2020
#include <ui/world_inspector.h>
21-
#include <ui/file_dialog.h>
21+
#include <obj_picking.h>
2222

2323
struct te_editor {
2424
// Not NULL if @ref game_world was loaded from a file (relative to the `res` directory).
@@ -364,38 +364,39 @@ editor_on_mouse_button_pressed(
364364
(void)was_handled_by_widget;
365365
te_editor* editor = game_instance;
366366

367-
if (button == TE_MB_RIGHT) {
368-
if (editor->game_world == NULL) {
369-
return;
370-
}
371-
te_camera* game_camera = world_get_active_camera(editor->game_world);
372-
if (game_camera == NULL) {
373-
return;
374-
}
367+
if (editor->game_world == NULL) {
368+
return;
369+
}
370+
te_camera* game_camera = world_get_active_camera(editor->game_world);
371+
if (game_camera == NULL) {
372+
return;
373+
}
375374

376-
vec4 viewport;
377-
camera_get_viewport(game_camera, viewport);
375+
vec4 viewport;
376+
camera_get_viewport(game_camera, viewport);
378377

379-
vec2 cursor_pos;
380-
te_window* window = game_manager_get_window(game_manager);
381-
window_get_cursor_position(window, &cursor_pos[0], &cursor_pos[1]);
378+
vec2 cursor_pos;
379+
te_window* window = game_manager_get_window(game_manager);
380+
window_get_cursor_position(window, &cursor_pos[0], &cursor_pos[1]);
382381

383-
unsigned int window_width;
384-
unsigned int window_height;
385-
window_get_size(window, &window_width, &window_height);
386-
glm_vec2_div(
387-
cursor_pos, (vec2){(float)window_width, (float)window_height}, cursor_pos);
382+
unsigned int window_width;
383+
unsigned int window_height;
384+
window_get_size(window, &window_width, &window_height);
385+
glm_vec2_div(cursor_pos, (vec2){(float)window_width, (float)window_height}, cursor_pos);
388386

389-
if (cursor_pos[0] < viewport[0] || cursor_pos[1] < viewport[1]
390-
|| cursor_pos[0] > viewport[0] + viewport[2]
391-
|| cursor_pos[1] > viewport[1] + viewport[3]) {
392-
// Outside of the viewport.
393-
return;
394-
}
387+
if (cursor_pos[0] < viewport[0] || cursor_pos[1] < viewport[1]
388+
|| cursor_pos[0] > viewport[0] + viewport[2]
389+
|| cursor_pos[1] > viewport[1] + viewport[3]) {
390+
// Outside of the game viewport.
391+
return;
392+
}
395393

394+
if (button == TE_MB_RIGHT) {
396395
window_capture_mouse_cursor(window, true);
397-
398396
editor_camera_enable_input(editor->editor_camera, true);
397+
}else if (button == TE_MB_LEFT) {
398+
void* obj = obj_picking_find_obj_under_cursor(cursor_pos, game_camera, editor->game_world);
399+
world_inspector_select_obj(editor_ui_get_world_inspector(editor->ui), obj);
399400
}
400401
}
401402

@@ -414,7 +415,6 @@ editor_on_mouse_button_released(
414415
}
415416

416417
window_capture_mouse_cursor(window, false);
417-
418418
editor_camera_enable_input(editor->editor_camera, false);
419419
}
420420
}

src/editor/src/obj_picking.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include "obj_picking.h"
2+
3+
#include "world.h"
4+
#include "game/camera.h"
5+
#include "game/model.h"
6+
#include "render/model_renderer.h"
7+
#include "shape/frustum_shape.h"
8+
9+
void* obj_picking_find_obj_under_cursor(vec2 cursor_pos_rel, te_camera* camera, te_world* world) {
10+
te_frustum_shape* frustum = camera_get_frustum(camera);
11+
vec3 camera_world_pos;
12+
camera_get_position(camera, camera_world_pos);
13+
14+
// Convert mouse pos to NDC [-1; 1] space.
15+
vec2 ndc;
16+
glm_vec2_mul(cursor_pos_rel, (vec2){2.0f, 2.0f}, ndc);
17+
ndc[1] = 2.0f - ndc[1]; // flip Y
18+
glm_vec2_sub(ndc, (vec2){1.0f, 1.0f}, ndc);
19+
20+
// Construct a point in clip space.
21+
vec4 camera_ray;
22+
camera_ray[0] = ndc[0];
23+
camera_ray[1] = ndc[1];
24+
camera_ray[2] = -1.0f; // forward axis in clip space
25+
camera_ray[3] = 1.0f;
26+
27+
// Apply inverse view/proj matrix.
28+
mat4* view_proj_mat = camera_get_view_proj_mat(camera);
29+
mat4 inv_view_proj_mat;
30+
glm_mat4_inv(*view_proj_mat, inv_view_proj_mat);
31+
glm_mat4_mulv(inv_view_proj_mat, camera_ray, camera_ray);
32+
glm_vec3_divs(camera_ray, camera_ray[3], camera_ray);
33+
34+
// Get direction from camera pos.
35+
glm_vec3_sub(camera_ray, camera_world_pos, camera_ray);
36+
glm_vec3_normalize(camera_ray);
37+
38+
unsigned int count;
39+
te_model** models = world_get_models(world, &count);
40+
41+
struct closest_model_info {
42+
te_model* model;
43+
te_aabb_shape aabb_world;
44+
float bb_size;
45+
float distance;
46+
};
47+
struct closest_model_info info;
48+
info.model = NULL;
49+
50+
unsigned int handle = 0xFFFFFFFF;
51+
for (unsigned int i = 0; i < count; i++) {
52+
handle = prv_model_get_render_data_handle(models[i]);
53+
if (handle == 0xFFFFFFFF) {
54+
continue;
55+
}
56+
57+
te_model_renderer* renderer = prv_model_get_model_renderer(models[i]);
58+
if (renderer == NULL) {
59+
continue;
60+
}
61+
62+
te_model_render_data* data = model_renderer_get_render_data_tmp(renderer, handle);
63+
if (!frustum_shape_is_aabb_inside(frustum, &data->aabb_world)) {
64+
continue;
65+
}
66+
67+
float distance;
68+
if (!aabb_shape_intersect_ray(
69+
&data->aabb_world, camera_world_pos, camera_ray, &distance)) {
70+
continue;
71+
}
72+
73+
// TODO: for now just do a bunch of simple tests (no ray-triangle intersection
74+
// because we don't store the geometry on the CPU).
75+
const float bb_size = data->aabb_world.extents[0] * 2.0f * data->aabb_world.extents[1]
76+
* 2.0f * data->aabb_world.extents[2] * 2.0f;
77+
if (info.model != NULL) {
78+
if (!aabb_shape_intersect(&data->aabb_world, &info.aabb_world)) {
79+
if (distance >= info.distance) {
80+
continue;
81+
}
82+
} else {
83+
if (bb_size >= info.bb_size) {
84+
continue;
85+
}
86+
}
87+
}
88+
89+
info.model = models[i];
90+
info.aabb_world = data->aabb_world;
91+
info.bb_size = bb_size;
92+
info.distance = distance;
93+
}
94+
95+
free(models);
96+
97+
return info.model;
98+
}

src/editor/src/obj_picking.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
struct te_camera;
4+
struct te_world;
5+
6+
#include "cglm/vec2.h"
7+
8+
// Looks for a 3D world game object that was under the cursor.
9+
// Cursor position must be in range [0.0; 1.0] relative to the window size.
10+
// Returns NULL if nothing found, otherwise pointer to a game object (for example: te_model).
11+
void* obj_picking_find_obj_under_cursor(
12+
vec2 cursor_pos_rel, struct te_camera* camera, struct te_world* world);

src/editor/src/ui/editor_ui.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ void editor_ui_destroy(te_editor_ui* ui);
1111
// Creates and spawns editor's UI widgets.
1212
void editor_ui_spawn(te_editor_ui* ui, struct te_world* editor_world);
1313

14-
// Returns always valid pointer to world inspector.
15-
// Do not delete/free returned pointer.
16-
struct te_world_inspector* editor_ui_get_world_inspector(te_editor_ui* ui);
17-
1814
// Refreshes displayed directory entries in file explorer.
1915
void editor_ui_refresh_filesystem_view(te_editor_ui* ui);
2016

2117
// Clears all information in UI widgets (resets to their initial state).
22-
void editor_ui_reset(te_editor_ui* ui);
18+
void editor_ui_reset(te_editor_ui* ui);
19+
20+
// Returns always valid pointer to world inspector.
21+
// Do not delete/free returned pointer.
22+
struct te_world_inspector* editor_ui_get_world_inspector(te_editor_ui* ui);

src/editor/src/ui/world_inspector.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,50 @@ world_inspector_rebuild_list(te_world_inspector* inspector, te_world* game_world
541541
}
542542
}
543543

544+
void
545+
world_inspector_select_obj(te_world_inspector* inspector, void* obj) {
546+
if (inspector->state != TE_WIS_SHOW_WORLD_OBJECTS) {
547+
return;
548+
}
549+
550+
if (obj == NULL) {
551+
property_inspector_hide(inspector->property_inspector);
552+
return;
553+
}
554+
555+
te_world_item_info* item_list = inspector->item_list;
556+
unsigned int obj_idx = 0xFFFFFFFF;
557+
for (unsigned int i = 0; i < inspector->item_list_count; i++) {
558+
if (item_list[i].obj == obj) {
559+
obj_idx = i;
560+
break;
561+
}
562+
}
563+
if (obj_idx == 0xFFFFFFFF) {
564+
return;
565+
}
566+
567+
te_world_item_info* selected_info = &item_list[obj_idx];
568+
569+
const char* type_id = NULL;
570+
switch (selected_info->type) {
571+
case (TE_WIT_MODEL): {
572+
type_id = model_get_type_id();
573+
break;
574+
}
575+
case (TE_WIT_CAMERA): {
576+
type_id = camera_get_type_id();
577+
break;
578+
}
579+
case (TE_WIT_WIDGET): {
580+
type_id = widget_get_owner_type_id(selected_info->obj);
581+
break;
582+
}
583+
}
584+
585+
property_inspector_show(inspector->property_inspector, selected_info->obj, type_id);
586+
}
587+
544588
void
545589
world_inspector_refresh_names(te_world_inspector* inspector) {
546590
if (inspector->state == TE_WIS_SHOW_WORLD_OBJECTS

src/editor/src/ui/world_inspector.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ void world_inspector_destroy(te_world_inspector* inspector);
1111
void world_inspector_add(te_world_inspector* inspector, struct te_widget* left_panel);
1212
void world_inspector_rebuild_list(te_world_inspector* inspector, struct te_world* game_world);
1313

14+
// Looks for the specified game object (te_model, te_camera, etc.) and selects
15+
// it if it exists in the world inspector.
16+
// Specify NULL to clear selection.
17+
void world_inspector_select_obj(te_world_inspector* inspector, void* obj);
18+
1419
// In case some game object's name was changed call this function to make sure world inspector displays the updated name.
1520
void world_inspector_refresh_names(te_world_inspector* inspector);

src/engine_lib/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ set(PROJECT_SOURCES
1919
src/debug_console.c
2020
src/type_database.c
2121
src/render/renderer.c
22-
src/render/model_renderer.h
2322
src/render/model_renderer.c
2423
src/render/shader_manager.c
2524
src/render/texture_manager.c
@@ -65,6 +64,7 @@ set(PROJECT_SOURCES
6564
include/render/texture_manager.h
6665
include/render/font_manager.h
6766
include/render/debug_drawer.h
67+
include/render/model_renderer.h
6868
include/world.h
6969
include/game/camera.h
7070
include/game/model.h

src/engine_lib/include/game/model.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
typedef struct te_model te_model;
88
struct te_world;
99
struct te_camera;
10+
struct te_model_renderer;
1011

1112
te_model* model_create();
1213
void model_destroy(te_model* model);
@@ -114,3 +115,11 @@ void prv_model_on_despawned(te_model* model);
114115
mat4* prv_model_get_world_mat_tmp(te_model* model);
115116

116117
void prv_model_set_vertex_attributes();
118+
119+
// Returns 0xFFFFFFFF if the model is not being rendered,
120+
// otherwise handle into the model renderer's data array.
121+
unsigned int prv_model_get_render_data_handle(te_model* model);
122+
123+
// Returns NULL if the model is not being rendered,
124+
// otherwise returns model renderer used to render the model.
125+
struct te_model_renderer* prv_model_get_model_renderer(te_model* model);
File renamed without changes.

0 commit comments

Comments
 (0)