From 9dfe50170ea3d90bb00639a856a484373a324ce3 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 9 Sep 2022 02:20:15 +0200 Subject: [PATCH] Demos: Add Tubes :^) --- Base/res/apps/Tubes.af | 4 + Base/res/icons/16x16/app-tubes.png | Bin 0 -> 171 bytes Base/res/icons/32x32/app-tubes.png | Bin 0 -> 218 bytes Userland/Demos/CMakeLists.txt | 1 + Userland/Demos/Tubes/CMakeLists.txt | 13 ++ Userland/Demos/Tubes/Shapes.cpp | 82 +++++++ Userland/Demos/Tubes/Shapes.h | 12 + Userland/Demos/Tubes/Tubes.cpp | 326 ++++++++++++++++++++++++++++ Userland/Demos/Tubes/Tubes.h | 64 ++++++ Userland/Demos/Tubes/main.cpp | 55 +++++ 10 files changed, 557 insertions(+) create mode 100644 Base/res/apps/Tubes.af create mode 100644 Base/res/icons/16x16/app-tubes.png create mode 100644 Base/res/icons/32x32/app-tubes.png create mode 100644 Userland/Demos/Tubes/CMakeLists.txt create mode 100644 Userland/Demos/Tubes/Shapes.cpp create mode 100644 Userland/Demos/Tubes/Shapes.h create mode 100644 Userland/Demos/Tubes/Tubes.cpp create mode 100644 Userland/Demos/Tubes/Tubes.h create mode 100644 Userland/Demos/Tubes/main.cpp diff --git a/Base/res/apps/Tubes.af b/Base/res/apps/Tubes.af new file mode 100644 index 00000000000..547a0fc01a1 --- /dev/null +++ b/Base/res/apps/Tubes.af @@ -0,0 +1,4 @@ +[App] +Name=Tubes +Executable=/bin/Tubes +Category=Demos diff --git a/Base/res/icons/16x16/app-tubes.png b/Base/res/icons/16x16/app-tubes.png new file mode 100644 index 0000000000000000000000000000000000000000..e4854dfacc812d768a7d7d6267b85684e2ea14e3 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`MV>B>Ar_~T6BdYU*|O!2I=hRj z>tFV)|NLeQXA}gJB@QST$>>!&L`iA0oy_#zteV4M=Cg%Sf%V0V6y^!J%hof#keENc zz)jhQv1-Agm Q0v*fX>FVdQ&MBb@0Ofs2H2?qr literal 0 HcmV?d00001 diff --git a/Userland/Demos/CMakeLists.txt b/Userland/Demos/CMakeLists.txt index cb9b1e2f00f..a00daa57410 100644 --- a/Userland/Demos/CMakeLists.txt +++ b/Userland/Demos/CMakeLists.txt @@ -9,5 +9,6 @@ add_subdirectory(ModelGallery) add_subdirectory(Mouse) add_subdirectory(Screensaver) add_subdirectory(Starfield) +add_subdirectory(Tubes) add_subdirectory(VirGLDemo) add_subdirectory(WidgetGallery) diff --git a/Userland/Demos/Tubes/CMakeLists.txt b/Userland/Demos/Tubes/CMakeLists.txt new file mode 100644 index 00000000000..32e3ba2f8a3 --- /dev/null +++ b/Userland/Demos/Tubes/CMakeLists.txt @@ -0,0 +1,13 @@ +serenity_component( + Tubes + TARGETS Tubes +) + +set(SOURCES + Shapes.cpp + Tubes.cpp + main.cpp +) + +serenity_app(Tubes ICON app-tubes) +target_link_libraries(Tubes LibGUI LibCore LibGfx LibGL LibMain) diff --git a/Userland/Demos/Tubes/Shapes.cpp b/Userland/Demos/Tubes/Shapes.cpp new file mode 100644 index 00000000000..67b7e342127 --- /dev/null +++ b/Userland/Demos/Tubes/Shapes.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +constexpr u8 sphere_number_of_segments = 4; +constexpr u8 tube_number_of_segments = 12; + +void draw_sphere() +{ + // Draw a sphere by drawing a cube with many segments with normalized coordinates + glBegin(GL_QUADS); + auto draw_segment = [](Array corners, Optional flip_a, Optional flip_b, Optional swap_a, Optional swap_b) { + for (DoubleVector3& corner : corners) { + if (flip_a.has_value()) + corner[flip_a.value()] *= -1; + if (flip_b.has_value()) + corner[flip_b.value()] *= -1; + if (swap_a.has_value() && swap_b.has_value()) + swap(corner[swap_a.value()], corner[swap_b.value()]); + + glNormal3d(corner.x(), corner.y(), corner.z()); + glVertex3d(corner.x(), corner.y(), corner.z()); + } + }; + double const segment_size = 2. / sphere_number_of_segments; + for (int y = 0; y < sphere_number_of_segments; y++) { + for (int x = 0; x < sphere_number_of_segments; x++) { + DoubleVector3 bottomleft = { -1. + x * segment_size, -1. + y * segment_size, 1. }; + DoubleVector3 bottomright = { -1. + (x + 1) * segment_size, -1. + y * segment_size, 1. }; + DoubleVector3 topright = { -1. + (x + 1) * segment_size, -1. + (y + 1) * segment_size, 1. }; + DoubleVector3 topleft = { -1. + x * segment_size, -1. + (y + 1) * segment_size, 1. }; + + Array normalized_corners = { + bottomleft.normalized(), + bottomright.normalized(), + topright.normalized(), + topleft.normalized(), + }; + + draw_segment(normalized_corners, {}, {}, {}, {}); // front face + draw_segment(normalized_corners, 0, 2, {}, {}); // back face + draw_segment(normalized_corners, 2, {}, 0, 2); // left face + draw_segment(normalized_corners, 0, {}, 0, 2); // right face + draw_segment(normalized_corners, 1, {}, 1, 2); // top face + draw_segment(normalized_corners, 2, {}, 1, 2); // bottom face + } + } + glEnd(); +} + +void draw_tube() +{ + glBegin(GL_QUADS); + double const segment_angle = 2 * M_PI / tube_number_of_segments; + double last_x = 0.; + double last_y = 1.; + for (int i = 1; i <= tube_number_of_segments; ++i) { + double angle = i * segment_angle; + double segment_x = sin(angle); + double segment_y = cos(angle); + + glNormal3d(last_x, last_y, 0.); + glVertex3d(last_x, last_y, 0.); + glNormal3d(segment_x, segment_y, 0.); + glVertex3d(segment_x, segment_y, 0.); + glVertex3d(segment_x, segment_y, -2.); + glNormal3d(last_x, last_y, 0.); + glVertex3d(last_x, last_y, -2.); + + last_x = segment_x; + last_y = segment_y; + } + glEnd(); +} diff --git a/Userland/Demos/Tubes/Shapes.h b/Userland/Demos/Tubes/Shapes.h new file mode 100644 index 00000000000..aa6141bc6f3 --- /dev/null +++ b/Userland/Demos/Tubes/Shapes.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +void draw_sphere(); +void draw_tube(); diff --git a/Userland/Demos/Tubes/Tubes.cpp b/Userland/Demos/Tubes/Tubes.cpp new file mode 100644 index 00000000000..17713e4b7a3 --- /dev/null +++ b/Userland/Demos/Tubes/Tubes.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr size_t grid_resolution = 15; +constexpr int mouse_max_distance_move = 10; +constexpr int reset_every_ticks = 900; +constexpr double rotation_range = 35.; +constexpr u8 tube_maximum_count = 12; +constexpr u8 tube_minimum_count = 3; +constexpr double tube_movement_per_tick = .25; +constexpr double tube_relative_thickness = .6; +constexpr int tube_travel_max_stretch = 6; + +static double random_double() +{ + return get_random() / static_cast(NumericLimits::max()); +} + +static int random_int(int min, int max) +{ + return min + round_to(random_double() * (max - min)); +} + +static IntVector4 tube_rotation_for_direction(Direction direction) +{ + switch (direction) { + case Direction::XPositive: + return { 0, 1, 0, -90 }; + case Direction::XNegative: + return { 0, 1, 0, 90 }; + case Direction::YPositive: + return { 1, 0, 0, 90 }; + case Direction::YNegative: + return { 1, 0, 0, -90 }; + case Direction::ZPositive: + return { 0, 1, 0, 180 }; + case Direction::ZNegative: + return { 0, 0, 0, 0 }; + default: + VERIFY_NOT_REACHED(); + } +} + +static IntVector3 vector_for_direction(Direction direction) +{ + switch (direction) { + case Direction::XPositive: + return { 1, 0, 0 }; + case Direction::XNegative: + return { -1, 0, 0 }; + case Direction::YPositive: + return { 0, 1, 0 }; + case Direction::YNegative: + return { 0, -1, 0 }; + case Direction::ZPositive: + return { 0, 0, 1 }; + case Direction::ZNegative: + return { 0, 0, -1 }; + default: + VERIFY_NOT_REACHED(); + } +} + +Tubes::Tubes(int interval) + : m_grid(MUST(FixedArray::try_create(grid_resolution * grid_resolution * grid_resolution))) +{ + start_timer(interval); +} + +void Tubes::choose_new_direction_for_tube(Tube& tube) +{ + // Find all possible directions + Vector possible_directions; + for (int i = 1; i <= 6; ++i) { + auto direction = static_cast(i); + auto direction_vector = vector_for_direction(direction); + auto check_position = tube.position + direction_vector; + if (is_valid_grid_position(check_position) && get_grid(check_position) == 0) + possible_directions.append(direction); + } + + // If tube is stuck, kill it :^( + if (possible_directions.is_empty()) { + tube.direction = Direction::None; + tube.active = false; + return; + } + + // Remove our old direction if we have other options available + Direction const old_direction = tube.direction; + if (possible_directions.size() >= 2 && possible_directions.contains_slow(old_direction)) + possible_directions.remove_all_matching([&old_direction](Direction const& item) { return item == old_direction; }); + + // Select a random new direction + tube.direction = possible_directions[random_int(0, static_cast(possible_directions.size()) - 1)]; + + // Determine how far we can go in this direction + auto direction_vector = vector_for_direction(tube.direction); + int max_stretch = random_int(1, tube_travel_max_stretch); + IntVector3 new_target = tube.position; + while (max_stretch-- > 0) { + new_target += direction_vector; + if (!is_valid_grid_position(new_target) || get_grid(new_target) != 0) + break; + set_grid(new_target, 1); + tube.target_position = new_target; + } + tube.progress_to_target = 0.; +} + +ErrorOr Tubes::create_buffer(Gfx::IntSize size) +{ + m_bitmap = TRY(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, size)); + m_gl_context = GL::create_context(*m_bitmap); + return {}; +} + +u8 Tubes::get_grid(IntVector3 position) +{ + return m_grid[position.z() * grid_resolution * grid_resolution + position.y() * grid_resolution + position.x()]; +} + +bool Tubes::is_valid_grid_position(Gfx::IntVector3 position) +{ + return position.x() >= 0 + && position.x() < static_cast(grid_resolution) + && position.y() >= 0 + && position.y() < static_cast(grid_resolution) + && position.z() >= 0 + && position.z() < static_cast(grid_resolution); +} + +void Tubes::set_grid(IntVector3 position, u8 value) +{ + m_grid[position.z() * grid_resolution * grid_resolution + position.y() * grid_resolution + position.x()] = value; +} + +void Tubes::mousemove_event(GUI::MouseEvent& event) +{ + if (m_mouse_origin.is_null()) { + m_mouse_origin = event.position(); + } else if (event.position().distance_from(m_mouse_origin) > mouse_max_distance_move) { + GUI::Application::the()->quit(); + } +} + +void Tubes::mousedown_event(GUI::MouseEvent&) +{ + GUI::Application::the()->quit(); +} + +void Tubes::keydown_event(GUI::KeyEvent&) +{ + GUI::Application::the()->quit(); +} + +void Tubes::paint_event(GUI::PaintEvent& event) +{ + GUI::Painter painter(*this); + painter.add_clip_rect(event.rect()); + painter.blit(rect().location(), *m_bitmap, m_bitmap->rect()); +} + +void Tubes::reset_tubes() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Random rotation + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPushMatrix(); + glRotated((random_double() - .5) * 2 * rotation_range, 0., 1., 0.); + glMatrixMode(GL_MODELVIEW); + + // Clear grid + m_grid.fill_with(0); + + // Create new set of tubes + auto free_grid_position = [&]() { + for (;;) { + IntVector3 position = { + random_int(0, grid_resolution - 1), + random_int(0, grid_resolution - 1), + random_int(0, grid_resolution - 1), + }; + if (get_grid(position) != 0) + continue; + return position; + } + }; + m_tubes.clear_with_capacity(); + int tube_count = random_int(tube_minimum_count, tube_maximum_count); + while (tube_count-- > 0) { + Tube new_tube = { + .color = { + random_double(), + random_double(), + random_double(), + }, + .position = free_grid_position(), + }; + choose_new_direction_for_tube(new_tube); + m_tubes.append(new_tube); + set_grid(new_tube.position, 1); + } +} + +void Tubes::setup_view() +{ + glClearColor(0.f, 0.f, 0.f, 1.f); + + glMatrixMode(GL_PROJECTION); + double const zoom = .25; + auto const half_aspect_ratio = static_cast(m_bitmap->width()) / m_bitmap->height() * zoom; + glFrustum(-half_aspect_ratio, half_aspect_ratio, -zoom, zoom, .5, 10.); + glTranslated(0., 0., -2.); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + + // Set up lighting + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + GLfloat light_ambient[] { .0f, .0f, .0f, 1.f }; + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + GLfloat light_diffuse[] { 1.f, 1.f, 1.f, 1.f }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + GLfloat light_specular[] { 1.f, 1.f, 1.f, 1.f }; + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + GLfloat light_position[] { .5f, 1.f, .5f, 0.f }; + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + + GLfloat mat_specular[] { 1.f, 1.f, 1.f, 1.f }; + glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); + glMaterialf(GL_FRONT, GL_SHININESS, 8.f); + + // Adapt the vertex color as ambient and diffuse colors + glEnable(GL_COLOR_MATERIAL); + glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_NORMALIZE); + glShadeModel(GL_SMOOTH); +} + +void Tubes::timer_event(Core::TimerEvent&) +{ + update_tubes(); + m_gl_context->present(); + repaint(); +} + +void Tubes::update_tubes() +{ + if (++m_ticks % reset_every_ticks == 0) + reset_tubes(); + + double const primitive_size = 2.; // our tubes and spheres are 1 in diameter, so object size is 2 + double const grid_width = 2.; + double const grid_scale = 1. / grid_resolution; + double const primitive_scale = 1. / primitive_size; + double const tube_length_scale = tube_movement_per_tick * primitive_size; + double const tube_thickness_scale = tube_relative_thickness * primitive_scale; + for (auto& tube : m_tubes) { + if (!tube.active) + continue; + + glColor3d(tube.color.x(), tube.color.y(), tube.color.z()); + glPushMatrix(); + + auto pos = tube.position; + glTranslated( + pos.x() * grid_scale * grid_width - (grid_width / 2.), + pos.y() * grid_scale * grid_width - (grid_width / 2.), + pos.z() * grid_scale * grid_width - (grid_width / 2.)); + glScaled(grid_scale, grid_scale, grid_scale); + + // Draw sphere if we're at the start or a corner + if (tube.progress_to_target == 0.) { + glPushMatrix(); + glScaled(tube_thickness_scale, tube_thickness_scale, tube_thickness_scale); + draw_sphere(); + glPopMatrix(); + } + + // Draw tube at the current position + glPushMatrix(); + auto direction_vector = vector_for_direction(tube.direction); + auto distance_to_target = (tube.target_position - tube.position).length(); + auto movement_magnitude = tube.progress_to_target * (distance_to_target - tube_movement_per_tick) / distance_to_target * grid_width; + glTranslated( + direction_vector.x() * movement_magnitude, + direction_vector.y() * movement_magnitude, + direction_vector.z() * movement_magnitude); + auto tube_rotation = tube_rotation_for_direction(tube.direction); + glRotated(tube_rotation.w(), tube_rotation.x(), tube_rotation.y(), tube_rotation.z()); + glScaled(tube_thickness_scale, tube_thickness_scale, primitive_scale * tube_length_scale); + + draw_tube(); + glPopMatrix(); + + // Move towards target + if (tube.progress_to_target >= distance_to_target) { + tube.position = tube.target_position; + choose_new_direction_for_tube(tube); + } else { + tube.progress_to_target = min(tube.progress_to_target + tube_movement_per_tick, distance_to_target); + } + + glPopMatrix(); + } +} diff --git a/Userland/Demos/Tubes/Tubes.h b/Userland/Demos/Tubes/Tubes.h new file mode 100644 index 00000000000..f51682c1e05 --- /dev/null +++ b/Userland/Demos/Tubes/Tubes.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +enum class Direction : u8 { + None = 0, + XPositive = 1, + XNegative = 2, + YPositive = 3, + YNegative = 4, + ZPositive = 5, + ZNegative = 6, +}; + +struct Tube { + bool active { true }; + DoubleVector3 color; + IntVector3 position; + Direction direction { Direction::None }; + IntVector3 target_position { 0, 0, 0 }; + double progress_to_target { 0 }; +}; + +class Tubes final : public GUI::Widget { + C_OBJECT(Tubes) +public: + virtual ~Tubes() override = default; + + ErrorOr create_buffer(Gfx::IntSize); + void reset_tubes(); + void setup_view(); + void update_tubes(); + +private: + Tubes(int); + + void choose_new_direction_for_tube(Tube&); + u8 get_grid(IntVector3); + bool is_valid_grid_position(IntVector3); + void set_grid(IntVector3, u8 value); + + virtual void paint_event(GUI::PaintEvent&) override; + virtual void timer_event(Core::TimerEvent&) override; + virtual void keydown_event(GUI::KeyEvent&) override; + virtual void mousedown_event(GUI::MouseEvent& event) override; + virtual void mousemove_event(GUI::MouseEvent& event) override; + + RefPtr m_bitmap; + FixedArray m_grid; + OwnPtr m_gl_context; + Gfx::IntPoint m_mouse_origin; + u64 m_ticks { 0 }; + Vector m_tubes; +}; diff --git a/Userland/Demos/Tubes/main.cpp b/Userland/Demos/Tubes/main.cpp new file mode 100644 index 00000000000..2bb834bd70c --- /dev/null +++ b/Userland/Demos/Tubes/main.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments arguments) +{ + TRY(Core::System::pledge("stdio recvfd sendfd rpath unix prot_exec")); + + unsigned refresh_rate = 12; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Screensaver rendering colorful moving tubes using LibGL"); + args_parser.add_option(refresh_rate, "Refresh rate", "rate", 'r', "milliseconds"); + args_parser.parse(arguments); + + auto app = TRY(GUI::Application::try_create(arguments)); + + TRY(Core::System::pledge("stdio recvfd sendfd rpath prot_exec")); + + auto app_icon = GUI::Icon::default_icon("app-tubes"sv); + auto window = TRY(GUI::Window::try_create()); + + window->set_double_buffering_enabled(true); + window->set_title("Tubes"); + window->set_resizable(false); + window->set_frameless(true); + window->set_fullscreen(true); + window->set_minimizable(false); + window->set_icon(app_icon.bitmap_for_size(16)); + window->update(); + + auto tubes_widget = TRY(window->try_set_main_widget(refresh_rate)); + tubes_widget->set_fill_with_background_color(false); + tubes_widget->set_override_cursor(Gfx::StandardCursor::Hidden); + window->show(); + + TRY(tubes_widget->create_buffer(window->size())); + tubes_widget->setup_view(); + tubes_widget->reset_tubes(); + + window->move_to_front(); + window->set_cursor(Gfx::StandardCursor::Hidden); + + return app->exec(); +}