clay/bindings/c3/source/clay-raylib-renderer.c3
2025-01-29 12:17:46 -05:00

447 lines
18 KiB
Plaintext

module raylib5::rl @if($feature(NO_STDLIB));
distinct ZString = inline char*;
module clay::renderer;
import raylib5::rl;
// TODO: this entire file was very rushed so it can probably be cleaned up a LOT
fn double calculate_sine(double x) {
double term = x;
double sum = term;
double n = 1;
for (int i = 1; i <= 10; i++) {
term *= (-1) * x * x / ((2 * n) * (2 * n + 1));
sum += term;
n++;
}
return sum;
}
fn double calculate_cosine(double x) {
double term = 1;
double sum = term;
double n = 1;
for (int i = 1; i <= 10; i++) {
term *= (-1) * x * x / ((2 * n - 1) * (2 * n));
sum += term;
n++;
}
return sum;
}
macro double calculate_tangent(double x) {
double sine = calculate_sine(x);
double cosine = calculate_cosine(x);
// Check for cases where cosine is zero (tangent is undefined)
if (cosine == 0.0) {
return 0.0;
}
return sine / cosine;
}
fn Color clayToRaylibColor(ClayColor color) @inline
{
return { (char)$$round(color.r), (char)$$round(color.g), (char)$$round(color.b), (char)$$round(color.a)};
}
struct RaylibFont
{
int fontId;
rl::Font font;
}
RaylibFont[10] raylibFonts;
rl::Camera raylibCamera;
const Matrix MATRIX_IDENTITY = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
};
enum CustomLayoutElementType
{
MODEL_3D
}
struct Model3DLayoutElement
{
rl::Model model;
float scale;
rl::Vector3 position;
rl::Matrix rotation;
}
struct CustomLayoutElement
{
CustomLayoutElementType type;
union element
{
Model3DLayoutElement model;
}
}
fn Matrix matrixPerspective(double fovY, double aspect, double nearPlane, double farPlane)
{
Matrix result = { 0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0 };
double top = nearPlane * calculate_tangent(fovY*0.5*rl::RAD2DEG);
double bottom = -top;
double right = top*aspect;
double left = -right;
// MatrixFrustum(-right, right, -top, top, near, far);
float rl = (float)(right - left);
float tb = (float)(top - bottom);
float far_near = (float)(farPlane - nearPlane);
result.m0 = ((float)nearPlane*2.0f)/rl;
result.m5 = ((float)nearPlane*2.0f)/tb;
result.m8 = ((float)right + (float)left)/rl;
result.m9 = ((float)top + (float)bottom)/tb;
result.m10 = -((float)farPlane + (float)nearPlane)/far_near;
result.m11 = -1.0f;
result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/far_near;
return result;
}
fn Vector3 vector3Unproject(Vector3 source, Matrix projection, Matrix view)
{
Vector3 result = { 0, 0, 0 };
// Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it
Matrix matViewProj = { // MatrixMultiply(view, projection);
view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12,
view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13,
view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14,
view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15,
view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12,
view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13,
view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14,
view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15,
view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12,
view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13,
view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14,
view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15,
view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12,
view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13,
view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14,
view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 };
// Calculate inverted matrix . MatrixInvert(matViewProj);
// Cache the matrix values (speed optimization)
float a00 = matViewProj.m0;
float a01 = matViewProj.m1;
float a02 = matViewProj.m2;
float a03 = matViewProj.m3;
float a10 = matViewProj.m4;
float a11 = matViewProj.m5;
float a12 = matViewProj.m6;
float a13 = matViewProj.m7;
float a20 = matViewProj.m8;
float a21 = matViewProj.m9;
float a22 = matViewProj.m10;
float a23 = matViewProj.m11;
float a30 = matViewProj.m12;
float a31 = matViewProj.m13;
float a32 = matViewProj.m14;
float a33 = matViewProj.m15;
float b00 = a00*a11 - a01*a10;
float b01 = a00*a12 - a02*a10;
float b02 = a00*a13 - a03*a10;
float b03 = a01*a12 - a02*a11;
float b04 = a01*a13 - a03*a11;
float b05 = a02*a13 - a03*a12;
float b06 = a20*a31 - a21*a30;
float b07 = a20*a32 - a22*a30;
float b08 = a20*a33 - a23*a30;
float b09 = a21*a32 - a22*a31;
float b10 = a21*a33 - a23*a31;
float b11 = a22*a33 - a23*a32;
// Calculate the invert determinant (inlined to avoid double-caching)
float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06);
Matrix matViewProjInv = {
(a11*b11 - a12*b10 + a13*b09)*invDet,
(-a01*b11 + a02*b10 - a03*b09)*invDet,
(a31*b05 - a32*b04 + a33*b03)*invDet,
(-a21*b05 + a22*b04 - a23*b03)*invDet,
(-a10*b11 + a12*b08 - a13*b07)*invDet,
(a00*b11 - a02*b08 + a03*b07)*invDet,
(-a30*b05 + a32*b02 - a33*b01)*invDet,
(a20*b05 - a22*b02 + a23*b01)*invDet,
(a10*b10 - a11*b08 + a13*b06)*invDet,
(-a00*b10 + a01*b08 - a03*b06)*invDet,
(a30*b04 - a31*b02 + a33*b00)*invDet,
(-a20*b04 + a21*b02 - a23*b00)*invDet,
(-a10*b09 + a11*b07 - a12*b06)*invDet,
(a00*b09 - a01*b07 + a02*b06)*invDet,
(-a30*b03 + a31*b01 - a32*b00)*invDet,
(a20*b03 - a21*b01 + a22*b00)*invDet };
// Create quaternion from source point
rl::Quaternion quat = { source.x, source.y, source.z, 1.0f };
// Multiply quat point by unprojecte matrix
rl::Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv)
matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w,
matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w,
matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w,
matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w };
// Normalized world points in vectors
result.x = qtransformed.x/qtransformed.w;
result.y = qtransformed.y/qtransformed.w;
result.z = qtransformed.z/qtransformed.w;
return result;
}
fn Matrix matrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane)
{
Matrix result = { 0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0 };
float rl = (float)(right - left);
float tb = (float)(top - bottom);
float far_near = (float)(farPlane - nearPlane);
result.m0 = 2.0f/rl;
result.m1 = 0.0f;
result.m2 = 0.0f;
result.m3 = 0.0f;
result.m4 = 0.0f;
result.m5 = 2.0f/tb;
result.m6 = 0.0f;
result.m7 = 0.0f;
result.m8 = 0.0f;
result.m9 = 0.0f;
result.m10 = -2.0f/far_near;
result.m11 = 0.0f;
result.m12 = -((float)left + (float)right)/rl;
result.m13 = -((float)top + (float)bottom)/tb;
result.m14 = -((float)farPlane + (float)nearPlane)/far_near;
result.m15 = 1.0f;
return result;
}
fn Vector3 vector3Normalize(Vector3 v)
{
Vector3 result = v;
float length = $$sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
if (length != 0.0f)
{
float ilength = 1.0f/length;
result.x *= ilength;
result.y *= ilength;
result.z *= ilength;
}
return result;
}
fn Vector3 vector3Subtract(Vector3 v1, Vector3 v2)
{
Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
return result;
}
fn Ray getScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int screenWidth, int screenHeight, float zDistance)
{
Ray ray = { {0,0,0}, {0,0,0} };
float x = (2.0f*position.x)/(float)screenWidth - 1.0f;
float y = 1.0f - (2.0f*position.y)/(float)screenHeight;
float z = 1.0f;
// Store values in a vector
Vector3 deviceCoords = { x, y, z };
// Calculate view matrix from camera look at
Matrix matView = rl::getCameraMatrix(camera);
Matrix matProj = MATRIX_IDENTITY;
if (camera.projection == CameraProjection.PERSPECTIVE)
{
// Calculate projection matrix from perspective
matProj = matrixPerspective((double)(camera.fovy*rl::DEG2RAD), ((double)screenWidth/(double)screenHeight), 0.01f, zDistance);
}
else if (camera.projection == CameraProjection.ORTHOGRAPHIC)
{
double aspect = (double)screenWidth/(double)screenHeight;
double top = camera.fovy/2.0;
double right = top*aspect;
// Calculate projection matrix from orthographic
matProj = matrixOrtho(-right, right, -top, top, 0.01, 1000.0);
}
// Unproject far/near points
Vector3 nearPoint = vector3Unproject({ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView);
Vector3 farPoint = vector3Unproject({ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView);
// Calculate normalized direction vector
Vector3 direction = vector3Normalize(vector3Subtract(farPoint, nearPoint));
ray.position = farPoint;
// Apply calculated vectors to ray
ray.direction = direction;
return ray;
}
fn Dimensions raylibMeasureText(ClayString *text, TextElementConfig *config)
{
// Measure string size for Font
Dimensions textSize = { 0, 0 };
float maxTextWidth = 0.0f;
float lineTextWidth = 0;
float textHeight = config.fontSize;
Font fontToUse = raylibFonts[config.fontId].font;
float scaleFactor = config.fontSize/(float)fontToUse.baseSize;
for (int i = 0; i < text.length; ++i)
{
if (text.chars[i] == '\n') {
maxTextWidth = $$max(maxTextWidth, lineTextWidth);
lineTextWidth = 0;
continue;
}
int index = text.chars[i] - 32;
if (fontToUse.glyphs[index].advanceX != 0) {lineTextWidth += fontToUse.glyphs[index].advanceX;}
else {lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX);}
}
maxTextWidth = $$max(maxTextWidth, lineTextWidth);
textSize.width = maxTextWidth * scaleFactor;
textSize.height = textHeight;
return textSize;
}
fn void raylibRender(RenderCommandArray renderCommands)
{
for (int j = 0; j < renderCommands.length; j++)
{
RenderCommand *renderCommand = renderCommands.get(j);
ClayBoundingBox boundingBox = renderCommand.boundingBox;
switch (renderCommand.commandType)
{
case RenderCommandType.TEXT: {
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
ClayString text = renderCommand.text;
ZString cloned = (ZString)malloc((ulong)text.length + 1);
$$memcpy(cloned, text.chars, (isz)text.length, false, (isz)0, (isz)0);
cloned[text.length] = '\0';
Font fontToUse = raylibFonts[renderCommand.config.textElementConfig.fontId].font;
rl::drawTextEx(fontToUse, cloned, {boundingBox.x, boundingBox.y}, (float)renderCommand.config.textElementConfig.fontSize, (float)renderCommand.config.textElementConfig.letterSpacing, clayToRaylibColor(renderCommand.config.textElementConfig.textColor));
free(cloned);
break;
}
case RenderCommandType.IMAGE: {
Texture2D imageTexture = *(Texture2D *)renderCommand.config.imageElementConfig.imageData;
rl::drawTextureEx(
imageTexture,
{boundingBox.x, boundingBox.y},
0,
boundingBox.width / (float)imageTexture.width,
rl::WHITE);
break;
}
case RenderCommandType.SCISSOR_START: {
rl::beginScissorMode((int)$$round(boundingBox.x), (int)$$round(boundingBox.y), (int)$$round(boundingBox.width), (int)$$round(boundingBox.height));
break;
}
case RenderCommandType.SCISSOR_END: {
rl::endScissorMode();
break;
}
case RenderCommandType.RECTANGLE: {
RectangleElementConfig *config = renderCommand.config.rectangleElementConfig;
if (config.cornerRadius.topLeft > 0) {
float radius = (config.cornerRadius.topLeft * 2) / (float)(boundingBox.width > boundingBox.height ? boundingBox.height : boundingBox.width);
rl::drawRectangleRounded({ boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height }, radius, 8, clayToRaylibColor(config.color));
} else {
rl::drawRectangle((int)$$round(boundingBox.x), (int)$$round(boundingBox.y), (int)$$round(boundingBox.width), (int)$$round(boundingBox.height), clayToRaylibColor(config.color));
}
break;
}
case RenderCommandType.BORDER: {
BorderElementConfig *config = renderCommand.config.borderElementConfig;
// Left border
if (config.left.width > 0) {
rl::drawRectangle((int)$$round(boundingBox.x), (int)$$round(boundingBox.y + config.cornerRadius.topLeft), (int)config.left.width, (int)$$round(boundingBox.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft), clayToRaylibColor(config.left.color));
}
// Right border
if (config.right.width > 0) {
rl::drawRectangle((int)$$round(boundingBox.x + boundingBox.width - config.right.width), (int)$$round(boundingBox.y + config.cornerRadius.topRight), (int)config.right.width, (int)$$round(boundingBox.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight), clayToRaylibColor(config.right.color));
}
// Top border
if (config.top.width > 0) {
rl::drawRectangle((int)$$round(boundingBox.x + config.cornerRadius.topLeft), (int)$$round(boundingBox.y), (int)$$round(boundingBox.width - config.cornerRadius.topLeft - config.cornerRadius.topRight), (int)config.top.width, clayToRaylibColor(config.top.color));
}
// Bottom border
if (config.bottom.width > 0) {
rl::drawRectangle((int)$$round(boundingBox.x + config.cornerRadius.bottomLeft), (int)$$round(boundingBox.y + boundingBox.height - config.bottom.width), (int)$$round(boundingBox.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight), (int)config.bottom.width, clayToRaylibColor(config.bottom.color));
}
if (config.cornerRadius.topLeft > 0) {
rl::drawRing({ $$round(boundingBox.x + config.cornerRadius.topLeft), $$round(boundingBox.y + config.cornerRadius.topLeft) }, $$round(config.cornerRadius.topLeft - config.top.width), config.cornerRadius.topLeft, 180, 270, 10, clayToRaylibColor(config.top.color));
}
if (config.cornerRadius.topRight > 0) {
rl::drawRing({ $$round(boundingBox.x + boundingBox.width - config.cornerRadius.topRight), $$round(boundingBox.y + config.cornerRadius.topRight) }, $$round(config.cornerRadius.topRight - config.top.width), config.cornerRadius.topRight, 270, 360, 10, clayToRaylibColor(config.top.color));
}
if (config.cornerRadius.bottomLeft > 0) {
rl::drawRing({ $$round(boundingBox.x + config.cornerRadius.bottomLeft), $$round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft) }, $$round(config.cornerRadius.bottomLeft - config.top.width), config.cornerRadius.bottomLeft, 90, 180, 10, clayToRaylibColor(config.bottom.color));
}
if (config.cornerRadius.bottomRight > 0) {
rl::drawRing({ $$round(boundingBox.x + boundingBox.width - config.cornerRadius.bottomRight), $$round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomRight) }, $$round(config.cornerRadius.bottomRight - config.bottom.width), config.cornerRadius.bottomRight, 0.1, 90, 10, clayToRaylibColor(config.bottom.color));
}
break;
}
case RenderCommandType.CUSTOM: {
CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand.config.customElementConfig.customData;
if (!customElement) continue;
switch (customElement.type) {
case CustomLayoutElementType.MODEL_3D: {
ClayBoundingBox rootBox = renderCommands.data[0].boundingBox;
float scaleValue = $$min($$min(1f, 768f / rootBox.height) * $$max(1f, rootBox.width / 1024f), 1.5f);
Ray positionRay = getScreenToWorldPointWithZDistance({ renderCommand.boundingBox.x + renderCommand.boundingBox.width / 2, renderCommand.boundingBox.y + (renderCommand.boundingBox.height / 2) + 20 }, raylibCamera, (int)$$round(rootBox.width), (int)$$round(rootBox.height), 140);
rl::beginMode3D(raylibCamera);
rl::drawModel(customElement.element.model.model, positionRay.position, customElement.element.model.scale * scaleValue, rl::WHITE); // Draw 3d model with texture
rl::endMode3D();
break;
}
default: break;
}
break;
}
default: {
// std::io::printfn("ERROR: unhandled render command."); TODO: maybe make a workout for this
}
}
}
}