Initial commit

This commit is contained in:
Alexander Rosenberg 2022-08-28 14:15:17 -07:00
commit 246f5699f9
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
26 changed files with 1120 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build
compile_commands.json

42
CMakeLists.txt Normal file
View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.16.0)
set(CMAKE_C_STANDARD 90)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(IC_Explorer)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_mixer SDL2_image SDL2_ttf)
function(add_resources out_var)
set(result)
foreach(in_f ${ARGN})
file(RELATIVE_PATH src_f ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${in_f})
set(out_f ${PROJECT_BINARY_DIR}/${in_f}.o)
get_filename_component(f_name ${src_f} NAME)
get_filename_component(f_dir ${src_f} DIRECTORY)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/${f_dir})
add_custom_command(OUTPUT ${out_f}
COMMAND ld -r -b binary -o ${out_f} ${f_name}
DEPENDS ${in_f}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/${f_dir}
COMMENT "Building resource ${out_f}"
VERBATIM)
list(APPEND result ${out_f})
endforeach()
set(${out_var} "${result}" PARENT_SCOPE)
endfunction()
add_resources(RESOURCES assets/font.ttf assets/background.png
assets/button.png assets/button-pressed.png assets/mute.png
assets/volume.png assets/bgm.wav assets/hover.wav assets/click.wav
assets/release.wav)
include_directories(${SDL2_INCLUDE_DIRS})
link_directories(${SDL2_LIBRARY_DIRS})
add_definitions(${SDL2_CFLAGS_OTHER} "-Wall")
add_executable(IC_Explorer main.c window.c button.c textbox.c ${RESOURCES})
target_link_libraries(IC_Explorer ${SDL2_LIBRARIES})

121
LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# IC Explorer
A small program for interactively exploring the IC 1101 galaxy. I made this for a project in my 11th grade physics class.
### Building
Building is done through CMake (obviously, there is a CMakeLists.txt file).
Building requires:
* PkgConfig for CMake
* SDL2
* SDL2_mixer
* SDL2_ttf
* SDL2_image
* GNU ld (or similar version with the ability to create binaries from images, etc.)
### Usage
Just run the executable, could not be simpler!

BIN
assets/background-full.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

BIN
assets/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
assets/background.xcf Normal file

Binary file not shown.

BIN
assets/bgm.wav Normal file

Binary file not shown.

BIN
assets/button-pressed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
assets/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

BIN
assets/click.wav Normal file

Binary file not shown.

BIN
assets/font.ttf Normal file

Binary file not shown.

BIN
assets/hover.wav Normal file

Binary file not shown.

BIN
assets/mute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/mute.xcf Normal file

Binary file not shown.

BIN
assets/release.wav Normal file

Binary file not shown.

BIN
assets/volume.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/volume.xcf Normal file

Binary file not shown.

170
button.c Normal file
View File

@ -0,0 +1,170 @@
#include "button.h"
#include <stdio.h>
#include <stdlib.h>
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
Button *buttonNew(const char *text, float x, float y, float w, float h,
SDL_Texture *inactive, SDL_Texture *active,
SDL_Color textColor, TTF_Font *font, ButtonSoundSet sounds,
const Window *window) {
Button *button = malloc(sizeof(Button));
if (!button) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
if (text) {
button->text = strdup(text);
if (!text) {
ERROR("error: out of memory\n");
free(button);
exit(EXIT_FAILURE);
}
} else {
button->text = NULL;
}
button->x = x;
button->y = y;
button->w = w;
button->h = h;
button->inactive = inactive;
button->active = active;
button->current = inactive;
button->font = font;
button->textColor = textColor;
button->sounds = sounds;
button->action = NULL;
button->renderedText = NULL;
button->down = 0;
buttonUpdate(button, window);
return button;
}
void buttonDelete(Button *button) {
if (button->text && strlen(button->text)) {
free(button->text);
SDL_DestroyTexture(button->renderedText);
}
free(button);
}
void buttonDraw(const Button *button, const Window *window) {
float bw = button->w;
float bh = button->h;
int ww, wh;
SDL_GetWindowSize(window->obj, &ww, &wh);
if (button->w == 0 && button->h == 0) {
bh = (float)button->textW / ww;
bw = (float)button->textH / wh;
} else if (button->w == 0) {
bw = (((wh * button->h) * button->textH) / button->textW) / ww;
} else if (button->h == 0) {
bh = (((ww * button->w) * button->textH) / button->textW) / wh;
}
SDL_Rect backArea = {ww * button->x, wh * button->y, ww * bw, wh * bh};
SDL_RenderCopy(window->render, button->current, NULL, &backArea);
if (button->text && strlen(button->text)) {
SDL_Rect textArea = {backArea.x * (1.0f + BUTTON_PADDING),
backArea.y * (1.0f + BUTTON_PADDING),
backArea.w * (1.0f - (2 * BUTTON_PADDING)),
backArea.h * (1.0f - (2 * BUTTON_PADDING))};
SDL_RenderCopy(window->render, button->renderedText, NULL, &textArea);
}
}
void buttonUpdate(Button *button, const Window *window) {
if (button->renderedText) {
SDL_DestroyTexture(button->renderedText);
}
if (button->text && strlen(button->text)) {
SDL_Surface *surface = TTF_RenderText_Blended(
button->font, button->text, button->textColor);
if (!surface) {
ERROR("error: sdl2: ttf: %s\n", TTF_GetError());
exit(EXIT_FAILURE);
}
button->textW = surface->w;
button->textH = surface->h;
button->renderedText =
SDL_CreateTextureFromSurface(window->render, surface);
SDL_FreeSurface(surface);
if (!button->renderedText) {
ERROR("error: sdl2: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
}
}
static int intersectsButton(const Button *button, float bw, float bh, float x,
float y) {
return x >= button->x && y >= button->y && x <= button->x + bw &&
y <= button->y + bh;
}
void buttonProcessInput(Button *button, Window *window,
const SDL_Event *event) {
int ww, wh;
SDL_GetWindowSize(window->obj, &ww, &wh);
float bw = button->w;
float bh = button->h;
if (button->w == 0 && button->h == 0) {
bh = (float)button->textW / ww;
bw = (float)button->textH / wh;
} else if (button->w == 0) {
bw = (((wh * button->h) * button->textH) / button->textW) / ww;
} else if (button->h == 0) {
bh = (((ww * button->w) * button->textH) / button->textW) / wh;
}
if (event->type == SDL_MOUSEMOTION) {
float x = (float)event->motion.x / ww;
float y = (float)event->motion.y / wh;
if (intersectsButton(button, bw, bh, x, y)) {
if (button->active && button->current != button->active) {
if (button->sounds.hover) {
Mix_PlayChannel(-1, button->sounds.hover, 0);
}
button->current = button->active;
}
} else {
if (button->current != button->inactive) {
if (button->sounds.hover) {
Mix_PlayChannel(-1, button->sounds.hover, 0);
}
button->current = button->inactive;
}
if (button->down) {
button->down = 0;
}
}
} else if (event->type == SDL_MOUSEBUTTONDOWN &&
event->button.button == SDL_BUTTON_LEFT) {
float x = (float)event->button.x / ww;
float y = (float)event->button.y / wh;
if (intersectsButton(button, bw, bh, x, y)) {
button->down = 1;
if (button->sounds.down) {
Mix_PlayChannel(-1, button->sounds.down, 0);
}
}
} else if (event->type == SDL_MOUSEBUTTONUP &&
event->button.button == SDL_BUTTON_LEFT) {
float x = (float)event->button.x / ww;
float y = (float)event->button.y / wh;
if (intersectsButton(button, bw, bh, x, y) && button->down) {
button->down = 0;
if (button->sounds.up) {
Mix_PlayChannel(-1, button->sounds.up, 0);
}
if (button->action) {
button->action(button, window, x - button->x, y - button->y);
}
button->current = button->inactive;
}
}
}
void buttonGetCenter(const Button *button, Point *point) {
point->x = button->x + (button->w / 2);
point->y = button->y + (button->w / 2);
}

56
button.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef IC_EXPLORER_BUTTON_H
#define IC_EXPLORER_BUTTON_H
#include "window.h"
#include <SDL_mixer.h>
#include <SDL_ttf.h>
#define BUTTON_PADDING 0.005f
typedef struct {
Mix_Chunk *down;
Mix_Chunk *up;
Mix_Chunk *hover;
} ButtonSoundSet;
struct Button;
typedef void (*ButtonAction)(struct Button *button, Window *window, float x,
float y);
typedef struct Button {
float x;
float y;
float w;
float h;
char *text;
SDL_Texture *current;
SDL_Texture *inactive;
SDL_Texture *active;
SDL_Color textColor;
TTF_Font *font;
SDL_Texture *renderedText;
ButtonSoundSet sounds;
int textW;
int textH;
ButtonAction action;
int down;
} Button;
Button *buttonNew(const char *text, float x, float y, float w, float h,
SDL_Texture *inactive, SDL_Texture *active,
SDL_Color textColor, TTF_Font *font, ButtonSoundSet sounds,
const Window *window);
void buttonDelete(Button *button);
void buttonDraw(const Button *button, const Window *window);
void buttonUpdate(Button *button, const Window *window);
void buttonProcessInput(Button *button, Window *window, const SDL_Event *event);
void buttonGetCenter(const Button *button, Point *point);
#endif /* IC_EXPLORER_BUTTON_H */

372
main.c Normal file
View File

@ -0,0 +1,372 @@
#include "button.h"
#include "strings.h"
#include "textbox.h"
#include "window.h"
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <stdio.h>
#include <stdlib.h>
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
#define MUSIC_VOLUME 48
/* Resources */
#define DEFINE_RESOURCE(rec_name) extern const char _binary_##rec_name##_start, _binary_##rec_name##_end
#define RESOURCE_DATA(rec_name) ((const void *) &_binary_##rec_name##_start)
#define RESOURCE_SIZE(rec_name) ((size_t) (&_binary_##rec_name##_end - &_binary_##rec_name##_start))
DEFINE_RESOURCE(font_ttf);
DEFINE_RESOURCE(background_png);
DEFINE_RESOURCE(button_png);
DEFINE_RESOURCE(button_pressed_png);
DEFINE_RESOURCE(mute_png);
DEFINE_RESOURCE(volume_png);
DEFINE_RESOURCE(bgm_wav);
DEFINE_RESOURCE(click_wav);
DEFINE_RESOURCE(hover_wav);
DEFINE_RESOURCE(release_wav);
/* Constants */
static const SDL_Color COLOR_BLACK = {0x0, 0x0, 0x0, 0xFF};
static const SDL_Color COLOR_WHITE = {0xFF, 0xFF, 0xFF, 0xFF};
static const ButtonSoundSet NO_BUTTON_SOUNDS = {NULL, NULL, NULL};
/* Assets */
static TTF_Font *font;
static SDL_Texture *backgroundTexture;
static SDL_Texture *buttonTexture;
static SDL_Texture *buttonPressedTexture;
static SDL_Texture *muteTexture;
static SDL_Texture *volumeTexture;
static Mix_Music *bgm;
static ButtonSoundSet buttonSounds = {NULL, NULL, NULL};
/* UI Elements */
static Button *backButton;
static Button *muteButton;
static TextBox *nameBox;
static Button *blackHoleButton;
static TextBox *blackHoleInfo;
static Button *haloButton;
static TextBox *haloInfo;
static Button *lightButton;
static TextBox *lightInfo;
static Button *planetsAndStarsButton;
static TextBox *planetsAndStarsInfo;
static Button *deathButton;
static TextBox *deathInfo;
/* Variables */
static Window *window;
static size_t locationCount;
static Button **locations;
static TextBox *currentBox = NULL;
static int quit = 0;
static int mute = 0;
/* Functions */
static void init(void);
static void loadAssets(void);
static void createUI(void);
static void drawLoop(void);
static void cleanupAssets(void);
static void cleanupObjects(void);
static void addLocation(Button *button);
static SDL_Texture *loadTexture(const char *data, size_t size);
static Mix_Music *loadMusic(const char *data, size_t size);
static Mix_Chunk *loadSound(const char *data, size_t size);
static void backButtonAction(Button *button, Window *window, float x, float y);
static void muteButtonAction(Button *button, Window *window, float x, float y);
static void blackHoleZoomAction(Button *button, Window *window, float x,
float y);
static void haloZoomAction(Button *button, Window *window, float x, float y);
static void lightZoomAction(Button *button, Window *window, float x, float y);
static void planetsAndStarsZoomAction(Button *button, Window *window, float x,
float y);
static void deathZoomAction(Button *button, Window *window, float x, float y);
int main() {
init();
window = windowNew("IC Explorer", 500, 500);
loadAssets();
createUI();
drawLoop();
cleanupObjects();
cleanupAssets();
SDL_Quit();
return 0;
}
void init() {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO)) {
ERROR("error: sdl2: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
if (!IMG_Init(IMG_INIT_PNG)) {
ERROR("error: sdl2: image: %s\n", IMG_GetError());
SDL_Quit();
exit(EXIT_FAILURE);
}
if (TTF_Init() != 0) {
ERROR("error: sdl2: ttf: %s\n", TTF_GetError());
SDL_Quit();
exit(EXIT_FAILURE);
}
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) != 0) {
ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
SDL_Quit();
exit(EXIT_FAILURE);
}
Mix_VolumeMusic(MUSIC_VOLUME);
locationCount = 0;
locations = malloc(1);
if (!locations) {
ERROR("error: out of memory\n");
SDL_Quit();
exit(EXIT_FAILURE);
}
}
void loadAssets() {
font = TTF_OpenFontRW(SDL_RWFromConstMem(RESOURCE_DATA(font_ttf), RESOURCE_SIZE(font_ttf)), SDL_TRUE, 512);
backgroundTexture = loadTexture(RESOURCE_DATA(background_png), RESOURCE_SIZE(background_png));
buttonTexture = loadTexture(RESOURCE_DATA(button_png), RESOURCE_SIZE(button_png));
buttonPressedTexture = loadTexture(RESOURCE_DATA(button_pressed_png), RESOURCE_SIZE(button_pressed_png));
muteTexture = loadTexture(RESOURCE_DATA(mute_png), RESOURCE_SIZE(mute_png));
volumeTexture = loadTexture(RESOURCE_DATA(volume_png), RESOURCE_SIZE(volume_png));
bgm = loadMusic(RESOURCE_DATA(bgm_wav), RESOURCE_SIZE(bgm_wav));
buttonSounds.hover = loadSound(RESOURCE_DATA(hover_wav), RESOURCE_SIZE(hover_wav));
buttonSounds.down = loadSound(RESOURCE_DATA(click_wav), RESOURCE_SIZE(click_wav));
buttonSounds.up = loadSound(RESOURCE_DATA(release_wav), RESOURCE_SIZE(release_wav));
}
void createUI() {
SDL_SetWindowMinimumSize(window->obj, 500, 500);
window->background = backgroundTexture;
backButton = buttonNew("Back", 0.05f, 0.05f, 0.15f, AUTOMATIC_SCALE,
buttonTexture, buttonPressedTexture, COLOR_BLACK,
font, buttonSounds, window);
backButton->action = &backButtonAction;
muteButton = buttonNew(NULL, 0.9f, 0.9f, 0.075f, 0.075f, volumeTexture,
NULL, COLOR_BLACK, font, NO_BUTTON_SOUNDS, window);
muteButton->action = &muteButtonAction;
nameBox = textBoxNew("IC 1101", 0.02, 0.02, 0.07f, COLOR_WHITE, NULL, font,
window);
/* Black Hole */
blackHoleButton =
buttonNew(BLACK_HOLE_BUTTON_TEXT, 0.55f, 0.55f, 0.15f, AUTOMATIC_SCALE,
buttonTexture, buttonPressedTexture, COLOR_BLACK, font,
buttonSounds, window);
blackHoleButton->action = &blackHoleZoomAction;
addLocation(blackHoleButton);
blackHoleInfo = textBoxNew(BLACK_HOLE_INFO_TEXT, 0.6f, 0.3f, 0.04,
COLOR_BLACK, buttonTexture, font, window);
/* Halo */
haloButton = buttonNew(HALO_BUTTON_TEXT, 0.2f, 0.25f, 0.1f, AUTOMATIC_SCALE,
buttonTexture, buttonPressedTexture, COLOR_BLACK,
font, buttonSounds, window);
haloButton->action = &haloZoomAction;
addLocation(haloButton);
haloInfo = textBoxNew(HALO_INFO_TEXT, 0.5f, 0.3f, 0.04, COLOR_BLACK,
buttonTexture, font, window);
/* Light */
lightButton = buttonNew(
LIGHT_BUTTON_TEXT, 0.55f, 0.4f, 0.12f, AUTOMATIC_SCALE, buttonTexture,
buttonPressedTexture, COLOR_BLACK, font, buttonSounds, window);
lightButton->action = &lightZoomAction;
addLocation(lightButton);
lightInfo = textBoxNew(LIGHT_INFO_TEXT, 0.45f, 0.15f, 0.04, COLOR_BLACK,
buttonTexture, font, window);
/* Planets & Stars */
planetsAndStarsButton =
buttonNew(PLANETS_AND_STARS_BUTTON_TEXT, 0.6f, 0.08f, 0.34f,
AUTOMATIC_SCALE, buttonTexture, buttonPressedTexture,
COLOR_BLACK, font, buttonSounds, window);
planetsAndStarsButton->action = &planetsAndStarsZoomAction;
addLocation(planetsAndStarsButton);
planetsAndStarsInfo =
textBoxNew(PLANETS_AND_STARS_INFO_TEXT, 0.05f, 0.3f, 0.04, COLOR_BLACK,
buttonTexture, font, window);
/* Death */
deathButton = buttonNew(
DEATH_BUTTON_TEXT, 0.1f, 0.85f, 0.12f, AUTOMATIC_SCALE, buttonTexture,
buttonPressedTexture, COLOR_BLACK, font, buttonSounds, window);
deathButton->action = &deathZoomAction;
addLocation(deathButton);
deathInfo = textBoxNew(DEATH_INFO_TEXT, 0.5f, 0.15f, 0.04, COLOR_BLACK,
buttonTexture, font, window);
}
void drawLoop() {
while (!quit) {
if (!Mix_PlayingMusic()) {
Mix_FadeInMusic(bgm, 0, 1000);
}
windowDraw(window);
buttonDraw(muteButton, window);
if (window->state == WINDOW_STATE_NORMAL) {
textBoxDraw(nameBox, window);
size_t i;
for (i = 0; i < locationCount; ++i) {
buttonDraw(locations[i], window);
}
} else if (window->state == WINDOW_STATE_ZOOMED) {
buttonDraw(backButton, window);
if (currentBox) {
textBoxDraw(currentBox, window);
}
}
windowUpdate(window);
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
quit = 1;
} else {
buttonProcessInput(muteButton, window, &e);
if (window->state == WINDOW_STATE_NORMAL) {
size_t i;
for (i = 0; i < locationCount; ++i) {
buttonProcessInput(locations[i], window, &e);
}
} else if (window->state == WINDOW_STATE_ZOOMED) {
if (e.type == SDL_KEYDOWN &&
e.key.keysym.sym == SDLK_ESCAPE) {
windowZoomOut(window);
} else {
buttonProcessInput(backButton, window, &e);
}
}
}
}
}
}
void cleanupAssets() {
TTF_CloseFont(font);
SDL_DestroyTexture(backgroundTexture);
SDL_DestroyTexture(buttonTexture);
SDL_DestroyTexture(buttonPressedTexture);
SDL_DestroyTexture(muteTexture);
SDL_DestroyTexture(volumeTexture);
Mix_CloseAudio();
Mix_FreeMusic(bgm);
Mix_FreeChunk(buttonSounds.hover);
Mix_FreeChunk(buttonSounds.down);
Mix_FreeChunk(buttonSounds.up);
}
void cleanupObjects() {
size_t i;
for (i = 0; i < locationCount; ++i) { buttonDelete(locations[i]); }
free(locations);
buttonDelete(backButton);
buttonDelete(muteButton);
textBoxDelete(nameBox);
textBoxDelete(blackHoleInfo);
textBoxDelete(haloInfo);
textBoxDelete(lightInfo);
textBoxDelete(planetsAndStarsInfo);
textBoxDelete(deathInfo);
windowDelete(window);
}
void addLocation(Button *button) {
locations = realloc(locations, sizeof(Button *) * (++locationCount));
if (!locations) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
locations[locationCount - 1] = button;
}
SDL_Texture *loadTexture(const char *data, size_t size) {
SDL_Surface *surface = IMG_Load_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
if (!surface) {
ERROR("error: sdl2: image: %s\n", IMG_GetError());
exit(EXIT_FAILURE);
}
SDL_Texture *texture =
SDL_CreateTextureFromSurface(window->render, surface);
SDL_FreeSurface(surface);
if (!texture) {
ERROR("error: sdl2: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
return texture;
}
Mix_Music *loadMusic(const char *data, size_t size) {
Mix_Music *m = Mix_LoadMUS_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
if (!m) {
ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
exit(EXIT_FAILURE);
}
return m;
}
Mix_Chunk *loadSound(const char *data, size_t size) {
Mix_Chunk *c = Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), SDL_TRUE);
if (!c) {
ERROR("error: sdl2: mixer: %s\n", Mix_GetError());
exit(EXIT_FAILURE);
}
return c;
}
void backButtonAction(Button *button, Window *window, float x, float y) {
windowZoomOut(window);
}
void muteButtonAction(Button *button, Window *window, float x, float y) {
if (mute) {
Mix_Volume(-1, 128);
Mix_VolumeMusic(MUSIC_VOLUME);
mute = 0;
button->inactive = volumeTexture;
button->current = volumeTexture;
} else {
Mix_Volume(-1, 0);
Mix_VolumeMusic(0);
mute = 1;
button->inactive = muteTexture;
button->current = muteTexture;
}
}
void blackHoleZoomAction(Button *button, Window *window, float x, float y) {
windowZoomTo(window, 0.6f, 0.5f);
currentBox = blackHoleInfo;
}
void haloZoomAction(Button *button, Window *window, float x, float y) {
windowZoomTo(window, 0.25f, 0.3f);
currentBox = haloInfo;
}
void lightZoomAction(Button *button, Window *window, float x, float y) {
windowZoomTo(window, 0.5f, 0.35f);
currentBox = lightInfo;
}
void planetsAndStarsZoomAction(Button *button, Window *window, float x,
float y) {
windowZoomTo(window, 0.55f, 0.03f);
currentBox = planetsAndStarsInfo;
}
void deathZoomAction(Button *button, Window *window, float x, float y) {
windowZoomTo(window, 0.05f, 0.8f);
currentBox = deathInfo;
}

24
strings.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef IC_EXPLORER_ASSETS_STRINGS_H
#define IC_EXPLORER_ASSETS_STRINGS_H
/* Black Hole */
static const char *BLACK_HOLE_BUTTON_TEXT = "Center";
static const char *BLACK_HOLE_INFO_TEXT = "In the center of\nIC 1101 there is\na large black hole.\nIt is one of the\nlargest black holes\nknown to exist.\nIt has a mass\nrange of 40-100\nbillion solar masses.\nThis is about \n80*10^39 to\n200*10^39 kg\nof mass!";
/* Halo */
static const char *HALO_BUTTON_TEXT = "Halo";
static const char *HALO_INFO_TEXT = "The halo is the fading\n,\"diffused\", light that\nextends beyond the\nmain \"sharp\" light\nradiating from the\ncenter of the galaxy.\nIt extends about 600 kpc\n(kilo-parsecs). That is \naround 2 million light\nyears. This makes it one\n of, if not the, largest\nand most luminous\ngalaxies in the universe!";
/* Light */
static const char *LIGHT_BUTTON_TEXT = "Light";
static const char *LIGHT_INFO_TEXT = "It is debated by scientists\nwhat the best way to\nmeasure the size of\na galaxy is. However,\nthe leading way seems to be\nby measuring its \"effective\nradius\". This is the area in\nwhich the galaxy lets off at\nleast half of its total light.\nThis area is different\nfrom the halo of the galaxy.\nIC 1101 has an \"effective\nradius\", of around 43 to\n77 kpc (kilo-parsecs). This\nmeans its radius is about\n173 to 251 light years!";
/* Planets & Stars */
static const char *PLANETS_AND_STARS_BUTTON_TEXT = "Planets & Stars";
static const char *PLANETS_AND_STARS_INFO_TEXT = "IC 1101 is filled\nwith about 10 trillion\nstars and countless\nplanets. All of these\nobjecs orbit around\nthe black hole\nlocated in the\ncenter of the galaxy.\nMany of these planets\nand stars also orbit\naround each other\ncreating solar systems\nmuch like the one we\nlive in. It is even\npossible that one of\nthe many planets has\nlife on it!";
/* Death */
static const char *DEATH_BUTTON_TEXT = "Death";
static const char *DEATH_INFO_TEXT = "IC 1101 is currently,\nclassified as a \"dying\ngalaxy\". This does not\nmean that there is no\nlife living on its\nplanets. It instead means\nthat it is losing more\nstars than it gains. This,\nhowever, does not mean\nit will die anytime\nsoon. It has likely been\na \"dying galaxy\"\nbefore. It will most\nlikely absorb another\ngalaxy and get the fuel\nit needs to keep\nproducing more stars.";
#endif /* IC_EXPLORER_ASSETS_STRINGS_H */

122
textbox.c Normal file
View File

@ -0,0 +1,122 @@
#include "textbox.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
TextBox *textBoxNew(const char *text, float x, float y, float textSize,
SDL_Color color, SDL_Texture *background, TTF_Font *font,
const Window *window) {
TextBox *box = malloc(sizeof(TextBox));
if (!box) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
box->text = strdup(text);
if (!box->text) {
ERROR("error: out of memory\n");
free(box);
exit(EXIT_FAILURE);
}
box->x = x;
box->y = y;
box->textColor = color;
box->background = background;
box->font = font;
box->textSize = textSize;
textBoxUpdate(box, window);
return box;
}
void textBoxDelete(TextBox *textBox) {
free(textBox->text);
size_t i;
for (i = 0; i < textBox->lineCount; ++i) {
SDL_DestroyTexture(textBox->renderedText[i]);
}
free(textBox->renderedText);
free(textBox);
}
static void getLineSizes(TextBox *textBox, int ww, int wh, int *widths,
int *width, int *height) {
int yInc = textBox->textSize * wh;
*width = 0;
size_t i;
for (i = 0; i < textBox->lineCount; ++i) {
int tw, th;
SDL_QueryTexture(textBox->renderedText[i], NULL, NULL, &tw, &th);
int lw = ((float)tw / (float)th) * (float)yInc;
widths[i] = lw;
if (lw > *width) {
*width = lw;
}
}
*width += ww * TEXT_BOX_PADDING;
*height = yInc * textBox->lineCount;
}
void textBoxDraw(TextBox *textBox, const Window *window) {
int ww, wh;
SDL_GetWindowSize(window->obj, &ww, &wh);
int widths[textBox->lineCount];
int bw, bh;
getLineSizes(textBox, ww, wh, widths, &bw, &bh);
SDL_Rect backArea = {textBox->x * ww, textBox->y * wh, bw, bh};
if (textBox->background != NULL) {
SDL_RenderCopy(window->render, textBox->background, NULL, &backArea);
}
int yInc = textBox->textSize * wh;
size_t i;
for (i = 0; i < textBox->lineCount; ++i) {
int th;
SDL_QueryTexture(textBox->renderedText[i], NULL, NULL, NULL, &th);
SDL_Rect area = {backArea.x * (1.0f + TEXT_BOX_PADDING),
(backArea.y * (1.0f + TEXT_BOX_PADDING)) + (yInc * i),
widths[i] * (1.0f - (2 * TEXT_BOX_PADDING)),
yInc * (1.0f - (2 * TEXT_BOX_PADDING))};
SDL_RenderCopy(window->render, textBox->renderedText[i], NULL, &area);
}
}
static SDL_Texture *renderLine(const char *text, const TextBox *textBox,
const Window *window) {
SDL_Surface *surface =
TTF_RenderText_Blended(textBox->font, text, textBox->textColor);
if (!surface) {
ERROR("error: sdl2: ttf: %s\n", TTF_GetError());
exit(EXIT_FAILURE);
}
SDL_Texture *texture =
SDL_CreateTextureFromSurface(window->render, surface);
SDL_FreeSurface(surface);
if (!texture) {
ERROR("error: sdl2: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
return texture;
}
void textBoxUpdate(TextBox *textBox, const Window *window) {
textBox->renderedText = malloc(1);
if (!textBox->renderedText) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
char *savePtr;
char *line = strtok_r(textBox->text, "\n", &savePtr);
for (textBox->lineCount = 0; line != NULL; ++textBox->lineCount) {
textBox->renderedText =
realloc(textBox->renderedText,
sizeof(SDL_Texture *) * (textBox->lineCount + 1));
if (!textBox->renderedText) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
textBox->renderedText[textBox->lineCount] =
renderLine(line, textBox, window);
line = strtok_r(NULL, "\n", &savePtr);
}
}

32
textbox.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef IC_EXPLORER_TEXTBOX_H
#define IC_EXPLORER_TEXTBOX_H
#include "window.h"
#include <SDL_ttf.h>
#define TEXT_BOX_PADDING 0.005f
typedef struct {
float x;
float y;
float textSize;
SDL_Color textColor;
SDL_Texture *background;
TTF_Font *font;
char *text;
size_t lineCount;
SDL_Texture **renderedText;
} TextBox;
TextBox *textBoxNew(const char *text, float x, float y, float textSize,
SDL_Color color, SDL_Texture *background, TTF_Font *font,
const Window *window);
void textBoxDelete(TextBox *textBox);
void textBoxDraw(TextBox *textBox, const Window *window);
void textBoxUpdate(TextBox *textBox, const Window *window);
#endif /* IC_EXPLORER_TEXTBOX_H */

114
window.c Normal file
View File

@ -0,0 +1,114 @@
#include "window.h"
#include <stdio.h>
#include <stdlib.h>
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
Window *windowNew(const char *title, int width, int height) {
Window *window = malloc(sizeof(Window));
if (!window) {
ERROR("error: out of memory\n");
exit(EXIT_FAILURE);
}
window->obj = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, width, height,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (!window->obj) {
ERROR("error: sdl2: %s\n", SDL_GetError());
free(window);
exit(EXIT_FAILURE);
}
window->render = SDL_CreateRenderer(
window->obj, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!window->render) {
ERROR("error: sdl2: %s\n", SDL_GetError());
SDL_DestroyWindow(window->obj);
free(window);
exit(EXIT_FAILURE);
}
window->background = NULL;
window->focus.x = 0.0f;
window->focus.y = 0.0f;
window->targetFocus.x = 0.0f;
window->targetFocus.y = 0.0f;
window->zoom = 1.0f;
window->state = WINDOW_STATE_NORMAL;
return window;
}
void windowDelete(Window *window) {
SDL_DestroyRenderer(window->render);
SDL_DestroyWindow(window->obj);
free(window);
}
static void zoomRect(const Window *window, SDL_Rect *rect) {
int winW, winH;
SDL_GetWindowSize(window->obj, &winW, &winH);
int texW, texH;
SDL_QueryTexture(window->background, NULL, NULL, &texW, &texH);
const Point *focus = &window->focus;
rect->x = (texW * focus->x) - (texW / window->zoom / 2);
rect->y = (texH * focus->y) - (texH / window->zoom / 2);
rect->w = texW / window->zoom;
rect->h = texH / window->zoom;
}
void windowDraw(Window *window) {
window->lastTime = window->nowTime;
window->nowTime = SDL_GetPerformanceCounter();
window->delta = (double)(((window->nowTime - window->lastTime) * 1000) /
SDL_GetPerformanceFrequency());
SDL_SetRenderDrawColor(window->render, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
SDL_RenderClear(window->render);
if (window->background) {
if (window->state == WINDOW_STATE_ZOOMING_IN) {
window->zoom += MAX_ZOOM * (window->delta / ZOOM_TIME);
window->focus.x += (window->targetFocus.x - window->startFocus.x) *
(window->delta / ZOOM_TIME);
window->focus.y += (window->targetFocus.y - window->startFocus.y) *
(window->delta / ZOOM_TIME);
if (window->zoom >= MAX_ZOOM) {
window->state = WINDOW_STATE_ZOOMED;
}
} else if (window->state == WINDOW_STATE_ZOOMING_OUT) {
window->zoom -= MAX_ZOOM * (window->delta / ZOOM_TIME);
window->focus.x += (window->targetFocus.x - window->startFocus.x) *
(window->delta / ZOOM_TIME);
window->focus.y += (window->targetFocus.y - window->startFocus.y) *
(window->delta / ZOOM_TIME);
if (window->zoom <= 1.0f) {
window->state = WINDOW_STATE_NORMAL;
}
}
if (window->state == WINDOW_STATE_NORMAL) {
SDL_RenderCopy(window->render, window->background, NULL, NULL);
} else {
SDL_Rect area;
zoomRect(window, &area);
SDL_RenderCopy(window->render, window->background, &area, NULL);
}
}
}
void windowUpdate(Window *window) {
SDL_RenderPresent(window->render);
}
void windowZoomTo(Window *window, float x, float y) {
window->zoom = 1.0f;
window->startFocus.x = 0.5f;
window->startFocus.y = 0.5f;
window->focus = window->startFocus;
window->targetFocus.x = x;
window->targetFocus.y = y;
window->state = WINDOW_STATE_ZOOMING_IN;
}
void windowZoomOut(Window *window) {
window->startFocus = window->focus;
window->targetFocus.x = 0.5f;
window->targetFocus.y = 0.5f;
window->state = WINDOW_STATE_ZOOMING_OUT;
}

47
window.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef IC_EXPLORER_WINDOW_H
#define IC_EXPLORER_WINDOW_H
#include <SDL.h>
#define AUTOMATIC_SCALE 0.0f
#define MAX_ZOOM 2.0f
#define ZOOM_TIME 750
#define WINDOW_STATE_NORMAL 0
#define WINDOW_STATE_ZOOMING_IN 1
#define WINDOW_STATE_ZOOMING_OUT 2
#define WINDOW_STATE_ZOOMED 3
typedef struct {
float x;
float y;
} Point;
typedef struct {
SDL_Window *obj;
SDL_Renderer *render;
SDL_Texture *background;
Point focus;
Point targetFocus;
Point startFocus;
float zoom;
int state;
double nowTime;
double lastTime;
double delta;
} Window;
Window *windowNew(const char *title, int width, int height);
void windowDelete(Window *window);
void windowDraw(Window *window);
void windowUpdate(Window *window);
void windowZoomTo(Window *window, float x, float y);
void windowZoomOut(Window *window);
#endif /* IC_EXPLORER_WINDOW_H */