Documentation and some API cleaning

- Fleshing out Readme
- Renaming pong to breakout
- Renaming triangleGLES to triangle
- Move cheatsheets to their own directory
- Cleanup clock example to use in QuickStart
- QuickStart.md
- Some API cleaning:
    - oc_font_create_from_file()/oc_font_create_from_path()
    - oc_image_create_from_file/oc_image_create_from_path
    - oc_canvas_set_current() -> oc_canvas_select()
    - some matrix helpers
This commit is contained in:
Martin Fouilleul 2023-09-07 20:51:05 +02:00 committed by martinfouilleul
parent 9aad2c0ec8
commit 4b2580f510
33 changed files with 640 additions and 424 deletions

View File

@ -1,21 +1,35 @@
# Installing
--------
**DISCLAIMER: This project is very much a Work In Progress. We're making it accessible in this very early state so that participants to the [Wheel Reinvention Jam 2023](https://handmade.network/jam/2023) can try it out and maybe use it as their jamming platform. Expect bugs, missing and/or incomplete features, unstable APIs, and sparse documentation. Some current issues might be a show stopper for you, so make sure you can build and run the sample apps before jumping in.**
**If you do choose to try out Orca anyway, well thanks! We'll do our best to answer your questions, and we'd really appreciate to hear your feedback!**
--------
# Orca
--
Orca is a devlopment platform and runtime environment for cross-platform, sandboxed graphical WebAssembly applications.
## Installing
_//TODO: Ben can you complete this section?_
Clone the repo: `git clone git@git.handmade.network:hmn/orca.git`.
Cd to orca and build the runtime:
Cd to the orca directory and build the Orca runtime:
```
cd orca
./orca dev build-runtime
```
Install the orca tools:
Install the orca dev tools:
```
./orca dev install
```
# Building the sample orca apps:
### Building the sample Orca apps
Cd to the sample project directory and run its build script:
@ -24,5 +38,35 @@ cd samples/pong
./build.sh
```
On macOS this creates a `Pong.app` bundle in `samples/pong` that you can double click to run.
On Windows this creates a `Pong` directory in `samples/pong`. You can launch the app by running `Pong/bin/orca.exe`.
On macOS this creates a `Pong.app` bundle in `samples/pong` that you can double click to run. On Windows this creates a `Pong` directory in `samples/pong`. You can launch the app by running `Pong/bin/Pong.exe`.
## Writing an Orca app
The following documents can help you write an application using the Orca APIs:
- The [Quick Start Guide](./doc/QuickStart.md) will walk you through writing and building a simple example application.
- The [samples folder](./samples) contains sample applications that show various aspects of the Orca API and support library:
- [clock](./samples/clock) is a simple clock showcasing vector graphics and the time API.
- [breakout](./samples/breakout) is a mini breakout game making use of the vector graphics API.
- [triangle](./samples/triangle) shows how to draw a spining triangle using the GLES API.
- [fluid](./samples/fluid) is a fluid simulation using a more complex GLES setup.
- [ui](./samples/ui) showcases the UI API and Orca's default UI widgets.
- The [API Cheatsheets](./doc/cheatsheets) provide a list of Orca API functions, grouped by topic.
## Building and bundling an Orca app
_//TODO: Ben, can you complete this section? Or should that be in the QuickStart guide?_
You must compile your application along with the Orca support code, into a WebAssembly module. The command `orca src cflags` can help you set up your compiler's flags to do so.
Once you have built your WebAssembly module, you can invoke the command `orca bundle` to bundle it with your apps resources and the Orca runtime to produce an application.
## License
Orca is distributed under the terms of the GNU Affero General Public License version 3, with additional terms in accordance with section 7 of AGPLv3. These additional terms ensure that:
- Modified versions of Orca must reasonably inform users that they are modified.
- You can distribute your application's WebAssembly modules under the terms of your choice, and are not required to license them under the terms of the AGPLv3.
Copyright and License details can be found in [LICENSE.txt](./LICENSE.txt)

180
doc/QuickStart.md Normal file
View File

@ -0,0 +1,180 @@
--------
**DISCLAIMER: This project is very much a Work In Progress. We're making it accessible in this very early state so that participants to the [Wheel Reinvention Jam 2023](https://handmade.network/jam/2023) can try it out and maybe use it as their jamming platform. Expect bugs, missing and/or incomplete features, unstable APIs, and sparse documentation. Some current issues might be a show stopper for you, so make sure you can build and run the sample apps before jumping in.**
**If you do choose to try out Orca anyway, well thanks! We'll do our best to answer your questions, and we'd really appreciate to hear your feedback!**
--------
# Orca Quick Start Guide
--
This is a short introduction to developping an application that can be run by the Orca runtime. We'll present the basic structure of an Orca application, and walk through a simple example in C.
## Basic structure
Orca exposes a number of types and functions to applications. In order to use them the first thing to do is to include `orca.h`.
```
#include<orca.h>
```
The Orca runtime manages the application's window and event loop. In order to receive a specific kind of event, you can define an associated _event handler_ and export it to the runtime. For instance, to be notified when your application's window is resized, you should define the `oc_on_resize()` handler:
```
ORCA_EXPORT void oc_on_resize(u32 width, u32 height)
{
// handle the window resize event
}
```
The `ORCA_EXPORT` macro makes the handler visible to the orca runtime, which automatically binds it to the window resize event.
Handlers are optional. If you don't care about an event, you can just omit the associated handler. You probably want to define at least two important handlers:
- `oc_on_init()` is called once when your application starts and can be use to initialize your application's resources
- `oc_on_frame_refresh()` is called when your application needs to render a new frame, typically tied to the refresh rate of the monitor.
For a list of available handlers and their signatures, see the [app cheatsheet](../doc/cheatsheets/cheatsheet_app.h).
## Clock Example
Let's look at the [clock example](../samples/clock). This is a simple app that shows an analog clock and showcases a couple of interesting Orca APIs.
Open [`main.c`](../samples/clock/src/main.c) and look at the definition of `oc_on_init()`. This handler is called when the applications starts, right after the application window has been created.
The first thing we do here is set the title and dimensions of the window. We then create the graphics resources that we'll use to draw the clock onto the window.
### Graphics surfaces
The next line of `oc_init()` creates a _graphics surface_. A surface represents a destination you can draw into using a specific API. In this sample, we're going to use a canvas surface, which allows drawing with a 2D vector graphics API. Other samples use a GLES surface to draw with the OpenGL ES API.
Before drawing into it, the surface must be selected as the current surface by calling `oc_surface_select()`. Once all drawing is done you can display the result by calling `oc_surface_present()`.
### Canvas
After creating the surface, we create a _canvas_. A canvas holds some context for drawing commands, like the current color or stroke width, as well as a command buffer that records all drawing commands. All canvas drawing functions use an implicit _current canvas_. You can select a canvas to be the current canvas by calling `oc_canvas_select()`, as seen at the begining of `oc_on_frame_refresh()`.
Canvas drawing functions like `oc_fill()` or `oc_stroke` merely add to the current canvas command buffer. You can later render those commands onto a canvas surface by calling `oc_render()`.
To summarize, the general structure of canvas drawing code is like the following:
```
oc_canvas_select(canvas); // make the canvas current
//... add commands to the canvas command buffer using drawing functions
oc_surface_select(surface); // select the canvas surface
oc_render(surface, canvas); // render the canvas commands into it
oc_surface_present(surface); // display the result
```
### Drawing
Canvas drawing functions can be roughly divided into three groups:
- Path functions like `oc_line_to()` or `oc_cubic_to()` are used to specify paths using lines and curves.
- Attributes setup functions like `oc_set_color()` or `oc_set_width()` are used to set attributes used by subsequent commands.
- Command functions like `oc_stroke()` and `oc_fill()` encode commands into the canvas command buffer using the current path and attributes.
Some helpers combine a path specification and a command, like `oc_circle_fill()`.
As an example, the back of the clock is drawn using these two calls:
```
// clock backing
oc_set_color_rgba(1, 1, 1, 1);
oc_circle_fill(centerX, centerY, clockRadius);
```
For a list of canvas drawing functions, see the [graphics API cheatsheet](../doc/cheatsheets/cheatsheet_graphics.h).
#### Transforms
A special case of attribute setting function is the pair `oc_matrix_push()` and `oc_matrix_pop()`, which are used to manipulate a stack of transform matrices:
- `oc_matrix_push()` multiplies the matrix currently on top of the stack with its argument, and pushes the result on the stack.
- `oc_matrix_pop()` pops a matrix from the stack.
The matrix on the top of the stack at the time a command is encoded is used to transform the path of that command.
You can see an example of using transform matrices when drawing the clock's hands:
```
// hours hand
oc_matrix_push(mat_transform(centerX, centerY, hoursRotation));
{
oc_set_color_rgba(.2, 0.2, 0.2, 1);
oc_rounded_rectangle_fill(0, -7.5 * uiScale, clockRadius * 0.5f, 15 * uiScale, 5 * uiScale);
}
oc_matrix_pop();
```
### Fonts and text
Going back to `oc_init()`, after creating a surface and a canvas, we create a font that we will use to draw the numbers on the clock's face:
```
oc_unicode_range ranges[5] = {
OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS
};
font = oc_font_create_from_path(OC_STR8("/segoeui.ttf"), 5, ranges);
```
The font is loaded from a font file located in a data folder inside the app bundle. By default, Orca apps use this data folder as their "root" for file operations.
Along with the path of the font file, we pass to the creation function the unicode ranges we want to load.
We then use the font to draw the clock's face:
```
// clock face
for(int i = 0; i < oc_array_size(clockNumberStrings); ++i)
{
oc_rect textRect = oc_text_bounding_box(font, fontSize, clockNumberStrings[i]);
textRect.h -= 10 * uiScale; // oc_text_bounding_box height doesn't seem to be a tight fit around the glyph
const f32 angle = i * ((M_PI * 2) / 12.0f) - (M_PI / 2);
oc_mat2x3 transform = mat_transform(centerX - (textRect.w / 2), centerY + (textRect.h / 2), angle);
oc_vec2 pos = oc_mat2x3_mul(transform, (oc_vec2){ clockRadius * 0.8f, 0 });
oc_set_color_rgba(0.2, 0.2, 0.2, 1);
oc_text_fill(pos.x, pos.y, clockNumberStrings[i]);
}
```
### Logging and asserts
The runtime has a console overlay whose visiblity can be toggled on and off with `Shift + Cmd + D` on macOS, or `Shift + Ctrl + D` on Windows. Your application can log messages, warnings or errors to that console using the following functions:
```
void oc_log_info(const char* fmt, ...); // informational messages
void oc_log_warning(const char* fmt, ...); // warnings, displayed in orange.
void oc_log_error(const char* fmt, ...); // errors, displayed in red.
```
If you started the application from a terminal, the log entries are also duplicated there.
You can assert on a condition using `OC_ASSERT(test, fmt, ...)`. If the test fails, the runtime displays a message box including your message, and terminates the application.
You can unconditionally abort the application with a message box using `OC_ABORT(fmt, ...)`.
## Where to go next?
For more examples of how to use Orca APIs, you can look at the other [sample apps](../samples):
- [breakout](./samples/breakout) is a mini breakout game making use of the vector graphics API. It demonstrates using input and drawing images.
- [triangle](./samples/triangle) shows how to draw a spining triangle using the GLES API.
- [fluid](./samples/fluid) is a fluid simulation using a more complex GLES setup.
- [ui](./samples/ui) showcases the UI API and Orca's default UI widgets.
For a list of Orca APIs, you can look at the [API cheatsheets](../doc/cheatsheets).
You can also ask questions in the [Handmade Network Discord](https://discord.gg/hmn), in particular in the [#orca](https://discord.com/channels/239737791225790464/1121811864066732082) channel.

View File

@ -1,19 +1,19 @@
@echo off
:: compile wasm module
set wasmFlags=--target=wasm32^
--no-standard-libraries ^
-fno-builtin ^
-Wl,--no-entry ^
-Wl,--export-dynamic ^
-g ^
-O2 ^
-mbulk-memory ^
-D__ORCA__ ^
-isystem ..\..\src\libc-shim\include ^
-I..\..\ext -I ..\..\src
clang %wasmFlags% -o .\module.wasm ..\..\src\orca.c ..\..\src\libc-shim\src\*.c src\main.c
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
orca bundle --orca-dir ..\.. --name Pong --icon icon.png --resource-dir data module.wasm
@echo off
:: compile wasm module
set wasmFlags=--target=wasm32^
--no-standard-libraries ^
-fno-builtin ^
-Wl,--no-entry ^
-Wl,--export-dynamic ^
-g ^
-O2 ^
-mbulk-memory ^
-D__ORCA__ ^
-isystem ..\..\src\libc-shim\include ^
-I..\..\ext -I ..\..\src
clang %wasmFlags% -o .\module.wasm ..\..\src\orca.c ..\..\src\libc-shim\src\*.c src\main.c
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
orca bundle --orca-dir ..\.. --name Breakout --icon icon.png --resource-dir data module.wasm

View File

@ -29,4 +29,4 @@ wasmFlags="--target=wasm32 \
$CLANG $wasmFlags -o ./module.wasm ../../src/orca.c $STDLIB_DIR/src/*.c src/main.c
orca bundle --orca-dir ../.. --name Pong --icon icon.png --resource-dir data module.wasm
orca bundle --orca-dir ../.. --name Breakout --icon icon.png --resource-dir data module.wasm

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

View File

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -5,20 +5,13 @@
#define NUM_BLOCKS_PER_ROW 7
#define NUM_BLOCKS 42 // 7 * 6
#define NUM_BLOCKS_TO_WIN (NUM_BLOCKS - 2)
#define BLOCKS_WIDTH 810.0f
#define BLOCK_HEIGHT 30.0f
#define BLOCKS_PADDING 15.0f
#define BLOCKS_BOTTOM 300.0f
const f32 BLOCK_WIDTH = (BLOCKS_WIDTH - ((NUM_BLOCKS_PER_ROW + 1) * BLOCKS_PADDING)) / NUM_BLOCKS_PER_ROW;
#define PADDLE_MAX_LAUNCH_ANGLE 0.7f
const oc_color paddleColor = { 1, 0, 0, 1 };
oc_rect paddle = { 300, 50, 200, 24 };
const oc_color ballColor = { 1, 1, 0, 1 };
oc_rect ball = { 200, 200, 20, 20 };
const f32 BLOCK_WIDTH = (BLOCKS_WIDTH - ((NUM_BLOCKS_PER_ROW + 1) * BLOCKS_PADDING)) / NUM_BLOCKS_PER_ROW;
oc_vec2 velocity = { 5, 5 };
@ -33,74 +26,77 @@ int blockHealth[NUM_BLOCKS] = {
};
int score = 0;
oc_vec2 frameSize = { 100, 100 };
bool leftDown = false;
bool rightDown = false;
oc_vec2 frameSize = { 100, 100 };
oc_surface surface;
oc_canvas canvas;
oc_image waterImage;
oc_image brickImage;
oc_image ballImage;
oc_image paddleImage;
oc_font pongFont;
oc_font font;
f32 lerp(f32 a, f32 b, f32 t);
oc_rect blockRect(int i);
int checkCollision(oc_rect block);
oc_mat2x3 flipY(oc_rect r);
oc_mat2x3 flipYAt(oc_vec2 pos);
const oc_color paddleColor = { 1, 0, 0, 1 };
oc_rect paddle = { 300, 50, 200, 24 };
oc_rect ball = { 200, 200, 20, 20 };
oc_str8 loadFile(oc_arena* arena, oc_str8 filename)
f32 lerp(f32 a, f32 b, f32 t)
{
oc_file file = oc_file_open(filename, OC_FILE_ACCESS_READ, 0);
if(oc_file_last_error(file) != OC_IO_OK)
{
oc_arena_scope scope = oc_arena_scope_begin(arena);
oc_log_error("Couldn't open file %s\n", oc_str8_to_cstring(arena, filename));
oc_arena_scope_end(scope);
}
u64 size = oc_file_size(file);
char* buffer = oc_arena_push(arena, size);
oc_file_read(file, size, buffer);
oc_file_close(file);
return oc_str8_from_buffer(size, buffer);
return (1 - t) * a + t * b;
}
oc_mat2x3 flip_y(oc_rect r)
{
return (oc_mat2x3){
1, 0, 0,
0, -1, 2 * r.y + r.h
};
}
oc_mat2x3 flip_y_at(oc_vec2 pos)
{
return (oc_mat2x3){
1, 0, 0,
0, -1, 2 * pos.y
};
}
ORCA_EXPORT void oc_on_init(void)
{
oc_window_set_title(OC_STR8("pong"));
oc_window_set_title(OC_STR8("Breakout"));
surface = oc_surface_canvas();
canvas = oc_canvas_create();
waterImage = oc_image_create_from_memory(surface, loadFile(oc_scratch(), OC_STR8("/underwater.jpg")), false);
ballImage = oc_image_create_from_memory(surface, loadFile(oc_scratch(), OC_STR8("/ball.png")), false);
paddleImage = oc_image_create_from_memory(surface, loadFile(oc_scratch(), OC_STR8("/wall.png")), false);
waterImage = oc_image_create_from_path(surface, OC_STR8("/underwater.jpg"), false);
brickImage = oc_image_create_from_path(surface, OC_STR8("/brick.png"), false);
ballImage = oc_image_create_from_path(surface, OC_STR8("/ball.png"), false);
if(oc_image_is_nil(waterImage))
{
oc_log_error("couldn't load water image\n");
}
if(oc_image_is_nil(brickImage))
{
oc_log_error("couldn't load brick image\n");
}
if(oc_image_is_nil(ballImage))
{
oc_log_error("couldn't load ball image\n");
}
if(oc_image_is_nil(paddleImage))
{
oc_log_error("couldn't load paddle image\n");
}
oc_str8 fontStr = loadFile(oc_scratch(), OC_STR8("/Literata-SemiBoldItalic.ttf"));
oc_unicode_range ranges[5] = { OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS };
oc_unicode_range ranges[5] = {
OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS
};
pongFont = oc_font_create_from_memory(fontStr, 5, ranges);
oc_arena_clear(oc_scratch());
font = oc_font_create_from_path(OC_STR8("/Literata-SemiBoldItalic.ttf"), 5, ranges);
}
ORCA_EXPORT void oc_on_terminate(void)
@ -122,24 +118,8 @@ ORCA_EXPORT void oc_on_resize(u32 width, u32 height)
frameSize.y = height;
}
ORCA_EXPORT void oc_on_mouse_down(int button)
{
oc_log_info("mouse down!");
}
ORCA_EXPORT void oc_on_key_down(int key)
{
if(key == OC_KEY_SPACE)
{
oc_log_error("(this is just for testing errors)");
return;
}
if(key == OC_KEY_ENTER)
{
oc_log_warning("(this is just for testing warning)");
return;
}
oc_log_info("key down: %i", key);
if(key == OC_KEY_LEFT)
{
@ -153,11 +133,6 @@ ORCA_EXPORT void oc_on_key_down(int key)
ORCA_EXPORT void oc_on_key_up(int key)
{
if(key == OC_KEY_ENTER || key == OC_KEY_SPACE)
{
return;
}
oc_log_info("key up: %i", key);
if(key == OC_KEY_LEFT)
{
@ -169,192 +144,7 @@ ORCA_EXPORT void oc_on_key_up(int key)
}
}
ORCA_EXPORT void oc_on_frame_refresh(void)
{
f32 aspect = frameSize.x / frameSize.y;
if(leftDown)
{
paddle.x -= 10;
}
else if(rightDown)
{
paddle.x += 10;
}
paddle.x = oc_clamp(paddle.x, 0, frameSize.x - paddle.w);
ball.x += velocity.x;
ball.y += velocity.y;
ball.x = oc_clamp(ball.x, 0, frameSize.x - ball.w);
ball.y = oc_clamp(ball.y, 0, frameSize.y - ball.h);
if(ball.x + ball.w >= frameSize.x)
{
velocity.x = -velocity.x;
}
if(ball.x <= 0)
{
velocity.x = -velocity.x;
}
if(ball.y + ball.h >= frameSize.y)
{
velocity.y = -velocity.y;
}
if(
ball.y <= paddle.y + paddle.h && ball.x + ball.w >= paddle.x && ball.x <= paddle.x + paddle.w && velocity.y < 0)
{
f32 t = ((ball.x + ball.w / 2) - paddle.x) / paddle.w;
f32 launchAngle = lerp(-PADDLE_MAX_LAUNCH_ANGLE, PADDLE_MAX_LAUNCH_ANGLE, t);
f32 speed = sqrtf(velocity.x * velocity.x + velocity.y * velocity.y);
velocity = (oc_vec2){
sinf(launchAngle) * speed,
cosf(launchAngle) * speed,
};
ball.y = paddle.y + paddle.h;
oc_log_info("PONG!");
}
if(ball.y <= 0)
{
ball.x = frameSize.x / 2. - ball.w;
ball.y = frameSize.y / 2. - ball.h;
}
for(int i = 0; i < NUM_BLOCKS; i++)
{
if(blockHealth[i] <= 0)
{
continue;
}
oc_rect r = blockRect(i);
int result = checkCollision(r);
if(result)
{
oc_log_info("Collision! direction=%d", result);
blockHealth[i] -= 1;
if(blockHealth[i] == 0)
{
++score;
}
f32 vx = velocity.x;
f32 vy = velocity.y;
switch(result)
{
case 1:
case 5:
velocity.y = -vy;
break;
case 3:
case 7:
velocity.x = -vx;
break;
case 2:
case 6:
velocity.x = -vy;
velocity.y = -vx;
break;
case 4:
case 8:
velocity.x = vy;
velocity.y = vx;
break;
}
}
}
if(score == NUM_BLOCKS_TO_WIN)
{
oc_request_quit();
}
oc_canvas_set_current(canvas);
oc_set_color_rgba(10.0f / 255.0f, 31.0f / 255.0f, 72.0f / 255.0f, 1);
oc_clear();
oc_image_draw(waterImage, (oc_rect){ 0, 0, frameSize.x, frameSize.y });
oc_mat2x3 yUp = {
1, 0, 0,
0, -1, frameSize.y
};
oc_matrix_push(yUp);
{
for(int i = 0; i < NUM_BLOCKS; i++)
{
if(blockHealth[i] <= 0)
{
continue;
}
oc_rect r = blockRect(i);
oc_set_color_rgba(0, 0, 0, 0.2);
oc_rounded_rectangle_fill(r.x, r.y - 2, r.w, r.h, 4);
oc_set_color_rgba(0.9, 0.9, 0.9, 1);
oc_rounded_rectangle_fill(r.x, r.y, r.w, r.h, 4);
int fontSize = 18;
oc_str8 text = oc_str8_pushf(oc_scratch(),
"%d", blockHealth[i]);
oc_rect textRect = oc_text_bounding_box(pongFont, fontSize, text);
oc_vec2 textPos = {
r.x + r.w / 2 - textRect.w / 2,
r.y + 9, // TODO: oc_text_bounding_box is returning extremely wack results for height.
};
oc_set_color_rgba(0, 0, 0, 1);
oc_set_font(pongFont);
oc_set_font_size(18);
oc_move_to(textPos.x, textPos.y);
oc_matrix_push(flipYAt(textPos));
{
oc_text_outlines(text);
oc_fill();
}
oc_matrix_pop();
}
oc_set_color_rgba(0.9, 0.9, 0.9, 1);
oc_rounded_rectangle_fill(paddle.x, paddle.y, paddle.w, paddle.h, 4);
oc_matrix_push(flipY(ball));
{
oc_image_draw(ballImage, ball);
}
oc_matrix_pop();
// draw score text
{
oc_move_to(10, 10);
oc_str8 text = oc_str8_pushf(oc_scratch(), "Destroy all %d blocks to win! Current score: %d", NUM_BLOCKS_TO_WIN, score);
oc_rect textRect = oc_text_bounding_box(pongFont, 20, text);
oc_vec2 textPos = { 10, 10 };
oc_matrix_push(flipYAt(textPos));
{
oc_text_outlines(text);
oc_fill();
}
oc_matrix_pop();
}
}
oc_matrix_pop();
oc_surface_select(surface);
oc_render(surface, canvas);
oc_surface_present(surface);
oc_arena_clear(oc_scratch());
}
oc_rect blockRect(int i)
oc_rect block_rect(int i)
{
int row = i / NUM_BLOCKS_PER_ROW;
int col = i % NUM_BLOCKS_PER_ROW;
@ -368,7 +158,7 @@ oc_rect blockRect(int i)
// Returns a cardinal direction 1-8 for the collision with the block, or zero
// if no collision. 1 is straight up and directions proceed clockwise.
int checkCollision(oc_rect block)
int check_collision(oc_rect block)
{
// Note that all the logic for this game has the origin in the bottom left.
@ -383,13 +173,6 @@ int checkCollision(oc_rect block)
return 0;
}
// if ((block.x <= ball.x && ballx2 <= blockx2)
// && (block.y <= ball.y && bally2 <= blocky2))
// {
// // Ball is fully inside block; do not consider as a collision
// return 0;
// }
// If moving right, the ball can bounce off its top right corner, right
// side, or bottom right corner. Corner bounces occur if the block's bottom
// left corner is in the ball's top right quadrant, or if the block's top
@ -495,23 +278,195 @@ int checkCollision(oc_rect block)
return 0;
}
f32 lerp(f32 a, f32 b, f32 t)
ORCA_EXPORT void oc_on_frame_refresh(void)
{
return (1 - t) * a + t * b;
}
f32 aspect = frameSize.x / frameSize.y;
oc_mat2x3 flipY(oc_rect r)
{
return (oc_mat2x3){
1, 0, 0,
0, -1, 2 * r.y + r.h
};
}
if(leftDown)
{
paddle.x -= 10;
}
else if(rightDown)
{
paddle.x += 10;
}
paddle.x = oc_clamp(paddle.x, 0, frameSize.x - paddle.w);
oc_mat2x3 flipYAt(oc_vec2 pos)
{
return (oc_mat2x3){
ball.x += velocity.x;
ball.y += velocity.y;
ball.x = oc_clamp(ball.x, 0, frameSize.x - ball.w);
ball.y = oc_clamp(ball.y, 0, frameSize.y - ball.h);
if(ball.x + ball.w >= frameSize.x)
{
velocity.x = -velocity.x;
}
if(ball.x <= 0)
{
velocity.x = -velocity.x;
}
if(ball.y + ball.h >= frameSize.y)
{
velocity.y = -velocity.y;
}
if(
ball.y <= paddle.y + paddle.h && ball.x + ball.w >= paddle.x && ball.x <= paddle.x + paddle.w && velocity.y < 0)
{
f32 t = ((ball.x + ball.w / 2) - paddle.x) / paddle.w;
f32 launchAngle = lerp(-PADDLE_MAX_LAUNCH_ANGLE, PADDLE_MAX_LAUNCH_ANGLE, t);
f32 speed = sqrtf(velocity.x * velocity.x + velocity.y * velocity.y);
velocity = (oc_vec2){
sinf(launchAngle) * speed,
cosf(launchAngle) * speed,
};
ball.y = paddle.y + paddle.h;
oc_log_info("PONG!");
}
if(ball.y <= 0)
{
ball.x = frameSize.x / 2. - ball.w;
ball.y = frameSize.y / 2. - ball.h;
}
for(int i = 0; i < NUM_BLOCKS; i++)
{
if(blockHealth[i] <= 0)
{
continue;
}
oc_rect r = block_rect(i);
int result = check_collision(r);
if(result)
{
oc_log_info("Collision! direction=%d", result);
blockHealth[i] -= 1;
if(blockHealth[i] == 0)
{
++score;
}
f32 vx = velocity.x;
f32 vy = velocity.y;
switch(result)
{
case 1:
case 5:
velocity.y = -vy;
break;
case 3:
case 7:
velocity.x = -vx;
break;
case 2:
case 6:
velocity.x = -vy;
velocity.y = -vx;
break;
case 4:
case 8:
velocity.x = vy;
velocity.y = vx;
break;
}
}
}
if(score == NUM_BLOCKS_TO_WIN)
{
oc_request_quit();
}
oc_canvas_select(canvas);
oc_set_color_rgba(10.0f / 255.0f, 31.0f / 255.0f, 72.0f / 255.0f, 1);
oc_clear();
oc_image_draw(waterImage, (oc_rect){ 0, 0, frameSize.x, frameSize.y });
oc_mat2x3 yUp = {
1, 0, 0,
0, -1, 2 * pos.y
0, -1, frameSize.y
};
oc_matrix_push(yUp);
{
for(int i = 0; i < NUM_BLOCKS; i++)
{
if(blockHealth[i] <= 0)
{
continue;
}
oc_rect r = block_rect(i);
oc_set_image(brickImage);
oc_set_color_rgba(0.9, 0.9, 0.9, 1);
oc_rounded_rectangle_fill(r.x, r.y, r.w, r.h, 4);
oc_set_image(oc_image_nil());
oc_set_color_rgba(0.6, 0.6, 0.6, 1);
oc_set_width(2);
oc_rounded_rectangle_stroke(r.x, r.y, r.w, r.h, 4);
int fontSize = 18;
oc_str8 text = oc_str8_pushf(oc_scratch(), "%d", blockHealth[i]);
oc_rect textRect = oc_text_bounding_box(font, fontSize, text);
oc_vec2 textPos = {
r.x + r.w / 2 - textRect.w / 2,
r.y + 9, // TODO: oc_text_bounding_box is returning extremely wack results for height.
};
oc_set_color_rgba(0.9, 0.9, 0.9, 1);
oc_circle_fill(r.x + r.w / 2, r.y + r.h / 2, r.h / 2.5);
oc_set_color_rgba(0, 0, 0, 1);
oc_set_font(font);
oc_set_font_size(18);
oc_move_to(textPos.x, textPos.y);
oc_matrix_push(flip_y_at(textPos));
{
oc_text_outlines(text);
oc_fill();
}
oc_matrix_pop();
}
oc_set_color(paddleColor);
oc_rounded_rectangle_fill(paddle.x, paddle.y, paddle.w, paddle.h, 4);
oc_matrix_push(flip_y(ball));
{
oc_image_draw(ballImage, ball);
}
oc_matrix_pop();
// draw score text
{
oc_move_to(10, 10);
oc_str8 text = oc_str8_pushf(oc_scratch(), "Destroy all %d blocks to win! Current score: %d", NUM_BLOCKS_TO_WIN, score);
oc_rect textRect = oc_text_bounding_box(font, 20, text);
oc_vec2 textPos = { 10, 10 };
oc_matrix_push(flip_y_at(textPos));
{
oc_set_color_rgba(0.9, 0.9, 0.9, 1);
oc_text_outlines(text);
oc_fill();
}
oc_matrix_pop();
}
}
oc_matrix_pop();
oc_surface_select(surface);
oc_render(surface, canvas);
oc_surface_present(surface);
oc_arena_clear(oc_scratch());
}

View File

@ -1,21 +1,33 @@
#include <math.h>
#include <orca.h>
#define ARRAYSIZE(array) (sizeof(array) / sizeof(array[0]))
const oc_str8 clockNumberStrings[] = {
OC_STR8_LIT("12"),
OC_STR8_LIT("1"),
OC_STR8_LIT("2"),
OC_STR8_LIT("3"),
OC_STR8_LIT("4"),
OC_STR8_LIT("5"),
OC_STR8_LIT("6"),
OC_STR8_LIT("7"),
OC_STR8_LIT("8"),
OC_STR8_LIT("9"),
OC_STR8_LIT("10"),
OC_STR8_LIT("11"),
};
oc_surface surface = { 0 };
oc_canvas canvas = { 0 };
oc_font font = { 0 };
oc_vec2 frameSize = { 100, 100 };
oc_surface surface;
oc_canvas canvas;
oc_font font;
f64 lastSeconds = 0;
oc_mat2x3 mat_rotation(f32 radians);
oc_mat2x3 mat_translation(f32 x, f32 y);
oc_mat2x3 mat_transform(f32 x, f32 y, f32 radians);
f32 minf(f32 a, f32 b);
oc_mat2x3 mat_transform(f32 x, f32 y, f32 radians)
{
oc_mat2x3 rotation = oc_mat2x3_rotate(radians);
oc_mat2x3 translation = oc_mat2x3_translate(x, y);
return oc_mat2x3_mul_m(translation, rotation);
}
ORCA_EXPORT void oc_on_init(void)
{
@ -25,27 +37,15 @@ ORCA_EXPORT void oc_on_init(void)
surface = oc_surface_canvas();
canvas = oc_canvas_create();
{
oc_str8 filename = OC_STR8("/segoeui.ttf");
oc_file file = oc_file_open(filename, OC_FILE_ACCESS_READ, 0);
if(oc_file_last_error(file) != OC_IO_OK)
{
oc_log_error("Couldn't open file %s\n", oc_str8_to_cstring(oc_scratch(), filename));
}
u64 size = oc_file_size(file);
char* buffer = oc_arena_push(oc_scratch(), size);
oc_file_read(file, size, buffer);
oc_file_close(file);
oc_unicode_range ranges[5] = {
OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS
};
oc_unicode_range ranges[5] = { OC_UNICODE_BASIC_LATIN,
OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
OC_UNICODE_LATIN_EXTENDED_A,
OC_UNICODE_LATIN_EXTENDED_B,
OC_UNICODE_SPECIALS };
font = oc_font_create_from_memory(oc_str8_from_buffer(size, buffer), 5, ranges);
}
oc_arena_clear(oc_scratch());
font = oc_font_create_from_path(OC_STR8("/segoeui.ttf"), 5, ranges);
}
ORCA_EXPORT void oc_on_resize(u32 width, u32 height)
@ -56,23 +56,7 @@ ORCA_EXPORT void oc_on_resize(u32 width, u32 height)
ORCA_EXPORT void oc_on_frame_refresh(void)
{
const oc_str8 clock_number_strings[] = {
OC_STR8("12"),
OC_STR8("1"),
OC_STR8("2"),
OC_STR8("3"),
OC_STR8("4"),
OC_STR8("5"),
OC_STR8("6"),
OC_STR8("7"),
OC_STR8("8"),
OC_STR8("9"),
OC_STR8("10"),
OC_STR8("11"),
};
oc_canvas_set_current(canvas);
oc_surface_select(surface);
oc_canvas_select(canvas);
oc_set_color_rgba(.05, .05, .05, 1);
oc_clear();
@ -94,7 +78,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
const f32 centerX = frameSize.x / 2;
const f32 centerY = frameSize.y / 2;
const f32 clockRadius = minf(frameSize.x, frameSize.y) * 0.5f * 0.85f;
const f32 clockRadius = oc_min(frameSize.x, frameSize.y) * 0.5f * 0.85f;
const f32 DEFAULT_CLOCK_RADIUS = 260;
const f32 uiScale = clockRadius / DEFAULT_CLOCK_RADIUS;
@ -108,83 +92,47 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
oc_circle_fill(centerX, centerY, clockRadius);
// clock face
for(int i = 0; i < ARRAYSIZE(clock_number_strings); ++i)
for(int i = 0; i < oc_array_size(clockNumberStrings); ++i)
{
const f32 rot = -i * ((M_PI * 2) / 12.0f) + (M_PI / 2);
const f32 sinRot = sinf(rot);
const f32 cosRot = cosf(rot);
oc_rect textRect = oc_text_bounding_box(font, fontSize, clock_number_strings[i]);
oc_rect textRect = oc_text_bounding_box(font, fontSize, clockNumberStrings[i]);
textRect.h -= 10 * uiScale; // oc_text_bounding_box height doesn't seem to be a tight fit around the glyph
const f32 x = cosRot * clockRadius * 0.8f - (textRect.w / 2) + centerX;
const f32 y = -sinRot * clockRadius * 0.8f + (textRect.h / 2) + centerY;
const f32 angle = i * ((M_PI * 2) / 12.0f) - (M_PI / 2);
oc_mat2x3 transform = mat_transform(centerX - (textRect.w / 2), centerY + (textRect.h / 2), angle);
oc_vec2 pos = oc_mat2x3_mul(transform, (oc_vec2){ clockRadius * 0.8f, 0 });
oc_set_color_rgba(0.2, 0.2, 0.2, 1);
oc_move_to(x, y);
oc_text_outlines(clock_number_strings[i]);
oc_fill();
oc_text_fill(pos.x, pos.y, clockNumberStrings[i]);
}
// hours hand
oc_matrix_push(mat_transform(centerX, centerY, hoursRotation));
{
oc_set_color_rgba(.2, 0.2, 0.2, 1);
oc_rounded_rectangle_fill(0, -7.5 * uiScale, clockRadius * 0.5f, 15 * uiScale, 5 * uiScale);
oc_matrix_pop();
}
oc_matrix_pop();
// minutes hand
oc_matrix_push(mat_transform(centerX, centerY, minutesRotation));
{
oc_set_color_rgba(.2, 0.2, 0.2, 1);
oc_rounded_rectangle_fill(0, -5 * uiScale, clockRadius * 0.7f, 10 * uiScale, 5 * uiScale);
oc_matrix_pop();
}
oc_matrix_pop();
// seconds hand
oc_matrix_push(mat_transform(centerX, centerY, secondsRotation));
{
oc_set_color_rgba(1, 0.2, 0.2, 1);
oc_rounded_rectangle_fill(0, -2.5 * uiScale, clockRadius * 0.8f, 5 * uiScale, 5 * uiScale);
oc_matrix_pop();
}
oc_matrix_pop();
oc_set_color_rgba(.2, 0.2, 0.2, 1);
oc_circle_fill(centerX, centerY, 10 * uiScale);
oc_surface_select(surface);
oc_render(surface, canvas);
oc_surface_present(surface);
}
oc_mat2x3 mat_rotation(f32 radians)
{
const f32 sinRot = sinf(radians);
const f32 cosRot = cosf(radians);
oc_mat2x3 rot = {
cosRot, -sinRot, 0,
sinRot, cosRot, 0
};
return rot;
}
oc_mat2x3 mat_translation(f32 x, f32 y)
{
oc_mat2x3 translation = {
1, 0, x,
0, 1, y
};
return translation;
}
oc_mat2x3 mat_transform(f32 x, f32 y, f32 radians)
{
oc_mat2x3 rotation = mat_rotation(radians);
oc_mat2x3 translation = mat_translation(x, y);
return oc_mat2x3_mul_m(translation, rotation);
}
f32 minf(f32 a, f32 b)
{
return (a < b) ? a : b;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -352,7 +352,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
}
}
oc_canvas_set_current(canvas);
oc_canvas_select(canvas);
oc_surface_select(surface);
oc_ui_draw();
oc_render(surface, canvas);

View File

@ -87,7 +87,7 @@ int main()
}
oc_surface_select(surface1);
oc_canvas_set_current(canvas1);
oc_canvas_select(canvas1);
oc_set_color_rgba(0, 0, 0.5, 0.5);
oc_clear();
@ -99,7 +99,7 @@ int main()
//*
oc_surface_select(surface2);
oc_canvas_set_current(canvas2);
oc_canvas_select(canvas2);
oc_set_color_rgba(0, 0, 0, 0);
oc_clear();

View File

@ -28,7 +28,7 @@ i32 render_thread(void* user)
}
oc_surface_select(surface);
oc_canvas_set_current(canvas);
oc_canvas_select(canvas);
oc_set_color_rgba(0, 0, 0.5, 0.5);
oc_clear();

View File

@ -213,7 +213,7 @@ ORCA_API bool oc_canvas_is_nil(oc_canvas canvas); //DOC: true if canvas is nil
ORCA_API oc_canvas oc_canvas_create(void); //DOC: create a new canvas
ORCA_API void oc_canvas_destroy(oc_canvas canvas); //DOC: destroys canvas
ORCA_API oc_canvas oc_canvas_set_current(oc_canvas canvas); //DOC: selects canvas in the current thread
ORCA_API oc_canvas oc_canvas_select(oc_canvas canvas); //DOC: selects canvas in the current thread
ORCA_API void oc_render(oc_surface surface, oc_canvas canvas); //DOC: renders all canvas commands onto surface
//------------------------------------------------------------------------------------------
@ -221,6 +221,9 @@ ORCA_API void oc_render(oc_surface surface, oc_canvas canvas); //DOC: renders al
//------------------------------------------------------------------------------------------
ORCA_API oc_font oc_font_nil(void);
ORCA_API oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API oc_font oc_font_create_from_file(oc_file file, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API oc_font oc_font_create_from_path(oc_str8 path, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API void oc_font_destroy(oc_font font);
//NOTE(martin): the following int valued functions return -1 if font is invalid or codepoint is not present in font//
@ -253,7 +256,8 @@ ORCA_API bool oc_image_is_nil(oc_image a);
ORCA_API oc_image oc_image_create(oc_surface surface, u32 width, u32 height);
ORCA_API oc_image oc_image_create_from_rgba8(oc_surface surface, u32 width, u32 height, u8* pixels);
ORCA_API oc_image oc_image_create_from_memory(oc_surface surface, oc_str8 mem, bool flip);
ORCA_API oc_image oc_image_create_from_file(oc_surface surface, oc_str8 path, bool flip);
ORCA_API oc_image oc_image_create_from_file(oc_surface surface, oc_file file, bool flip);
ORCA_API oc_image oc_image_create_from_path(oc_surface surface, oc_str8 path, bool flip);
ORCA_API void oc_image_destroy(oc_image image);
@ -356,6 +360,8 @@ ORCA_API void oc_circle_fill(f32 x, f32 y, f32 r);
ORCA_API void oc_circle_stroke(f32 x, f32 y, f32 r);
ORCA_API void oc_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle);
ORCA_API void oc_text_fill(f32 x, f32 y, oc_str8 text);
//NOTE: image helpers
ORCA_API void oc_image_draw(oc_image image, oc_rect rect);
ORCA_API void oc_image_draw_region(oc_image image, oc_rect srcRegion, oc_rect dstRegion);

View File

@ -542,6 +542,46 @@ oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range
return (fontHandle);
}
oc_font oc_font_create_from_file(oc_file file, u32 rangeCount, oc_unicode_range* ranges)
{
oc_font font = oc_font_nil();
oc_arena_scope scratch = oc_scratch_begin();
u64 size = oc_file_size(file);
char* buffer = oc_arena_push(scratch.arena, size);
u64 read = oc_file_read(file, size, buffer);
if(read != size)
{
oc_log_error("Couldn't read font data\n");
}
else
{
font = oc_font_create_from_memory(oc_str8_from_buffer(size, buffer), rangeCount, ranges);
}
oc_scratch_end(scratch);
return (font);
}
oc_font oc_font_create_from_path(oc_str8 path, u32 rangeCount, oc_unicode_range* ranges)
{
oc_font font = oc_font_nil();
oc_file file = oc_file_open(path, OC_FILE_ACCESS_READ, OC_FILE_OPEN_NONE);
if(oc_file_last_error(file) != OC_IO_OK)
{
oc_log_error("Could not open file %*.s\n", oc_str8_ip(path));
}
else
{
font = oc_font_create_from_file(file, rangeCount, ranges);
}
oc_file_close(file);
return (font);
}
void oc_font_destroy(oc_font fontHandle)
{
oc_font_data* fontData = oc_font_data_from_handle(fontHandle);
@ -858,7 +898,7 @@ oc_canvas oc_canvas_create()
canvasHandle = oc_canvas_handle_alloc(canvas);
oc_canvas_set_current(canvasHandle);
oc_canvas_select(canvasHandle);
}
return (canvasHandle);
}
@ -878,7 +918,7 @@ void oc_canvas_destroy(oc_canvas handle)
}
}
oc_canvas oc_canvas_set_current(oc_canvas canvas)
oc_canvas oc_canvas_select(oc_canvas canvas)
{
oc_canvas old = __mgCurrentCanvasHandle;
__mgCurrentCanvasHandle = canvas;
@ -1595,6 +1635,13 @@ void oc_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle)
}
}
void oc_text_fill(f32 x, f32 y, oc_str8 text)
{
oc_move_to(x, y);
oc_text_outlines(text);
oc_fill();
}
//------------------------------------------------------------------------------------------
//NOTE(martin): images
//------------------------------------------------------------------------------------------
@ -1633,34 +1680,44 @@ oc_image oc_image_create_from_memory(oc_surface surface, oc_str8 mem, bool flip)
return (image);
}
#if !OC_PLATFORM_ORCA
oc_image oc_image_create_from_file(oc_surface surface, oc_str8 path, bool flip)
oc_image oc_image_create_from_file(oc_surface surface, oc_file file, bool flip)
{
oc_image image = oc_image_nil();
int width, height, channels;
oc_arena_scope scratch = oc_scratch_begin();
const char* cpath = oc_str8_to_cstring(scratch.arena, path);
u64 size = oc_file_size(file);
char* buffer = oc_arena_push(scratch.arena, size);
u64 read = oc_file_read(file, size, buffer);
stbi_set_flip_vertically_on_load(flip ? 1 : 0);
u8* pixels = stbi_load(cpath, &width, &height, &channels, 4);
if(pixels)
if(read != size)
{
image = oc_image_create_from_rgba8(surface, width, height, pixels);
free(pixels);
oc_log_error("Couldn't read image data\n");
}
else
{
oc_log_error("stbi_load() failed: %s\n", stbi_failure_reason());
image = oc_image_create_from_memory(surface, oc_str8_from_buffer(size, buffer), flip);
}
oc_scratch_end(scratch);
oc_scratch_end(scratch);
return (image);
}
#endif // !OC_PLATFORM_ORCA
oc_image oc_image_create_from_path(oc_surface surface, oc_str8 path, bool flip)
{
oc_image image = oc_image_nil();
oc_file file = oc_file_open(path, OC_FILE_ACCESS_READ, OC_FILE_OPEN_NONE);
if(oc_file_last_error(file) != OC_IO_OK)
{
oc_log_error("Could not open file %*.s\n", oc_str8_ip(path));
}
else
{
image = oc_image_create_from_file(surface, file, flip);
}
oc_file_close(file);
return (image);
}
void oc_image_draw_region(oc_image image, oc_rect srcRegion, oc_rect dstRegion)
{

View File

@ -695,7 +695,7 @@ i32 orca_runloop(void* user)
}
oc_surface_select(app->debugOverlay.surface);
oc_canvas_set_current(app->debugOverlay.canvas);
oc_canvas_select(app->debugOverlay.canvas);
if(app->debugOverlay.show)
{
@ -899,7 +899,7 @@ int main(int argc, char** argv)
for(int i = 0; i < 3; i++)
{
oc_surface_select(app->debugOverlay.surface);
oc_canvas_set_current(app->debugOverlay.canvas);
oc_canvas_select(app->debugOverlay.canvas);
oc_render(app->debugOverlay.surface, app->debugOverlay.canvas);
oc_surface_present(app->debugOverlay.surface);
}

View File

@ -5,6 +5,7 @@
* @date: 15/08/2023
*
*****************************************************************/
#include <math.h>
#include "algebra.h"
bool oc_vec2_equal(oc_vec2 v0, oc_vec2 v1)
@ -53,3 +54,23 @@ oc_vec2 oc_mat2x3_mul(oc_mat2x3 m, oc_vec2 p)
f32 y = p.x * m.m[3] + p.y * m.m[4] + m.m[5];
return ((oc_vec2){ x, y });
}
oc_mat2x3 oc_mat2x3_rotate(f32 radians)
{
const f32 sinRot = sinf(radians);
const f32 cosRot = cosf(radians);
oc_mat2x3 rot = {
cosRot, -sinRot, 0,
sinRot, cosRot, 0
};
return rot;
}
oc_mat2x3 oc_mat2x3_translate(f32 x, f32 y)
{
oc_mat2x3 translate = {
1, 0, x,
0, 1, y
};
return translate;
}

View File

@ -18,6 +18,9 @@ oc_vec2 oc_mat2x3_mul(oc_mat2x3 m, oc_vec2 p);
oc_mat2x3 oc_mat2x3_mul_m(oc_mat2x3 lhs, oc_mat2x3 rhs);
oc_mat2x3 oc_mat2x3_inv(oc_mat2x3 x);
oc_mat2x3 oc_mat2x3_rotate(f32 radians);
oc_mat2x3 oc_mat2x3_translate(f32 x, f32 y);
//TODO: complete
#endif //__ALGEBRA_H_

View File

@ -55,6 +55,8 @@
begin; \
for(int __i__ = 0; __i__ < 1; __i__++, end)
#define oc_array_size(array) (sizeof(array) / sizeof((array)[0]))
//----------------------------------------------------------------------------------------
//NOTE(martin): bit-twiddling & arithmetic helpers
//----------------------------------------------------------------------------------------