Compare commits
97 Commits
a5fc1c0f3d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b88d22aefc | ||
|
|
9147db6fdc | ||
|
|
ce6b0ddb3a | ||
|
|
ef7e3e6449 | ||
|
|
0262ed7388 | ||
|
|
a840f32c4e | ||
|
|
29818b1b0b | ||
|
|
250f535abe | ||
|
|
20376aa5e4 | ||
|
|
3d87fdeb5f | ||
|
|
f87854fca1 | ||
|
|
2fc5e2db3f | ||
|
|
c15919074c | ||
|
|
83b798e365 | ||
|
|
1619122a58 | ||
|
|
ff2e4bd578 | ||
|
|
e78faccab8 | ||
|
|
4e1b63fd0e | ||
|
|
f310c18b84 | ||
|
|
6c07f11992 | ||
|
|
4f7e7ab591 | ||
|
|
dd31a1c2b0 | ||
|
|
f7be9c3344 | ||
|
|
18ca9e14ba | ||
|
|
313a2e7eeb | ||
|
|
187879a7c6 | ||
|
|
6e78e8f9c2 | ||
|
|
027435d193 | ||
|
|
1d78e725bb | ||
|
|
86530871e6 | ||
|
|
a199278e7d | ||
|
|
23dc5820ee | ||
|
|
92b1252c82 | ||
|
|
22962bbd63 | ||
|
|
9f6435ece9 | ||
|
|
f591fff8ac | ||
|
|
548bad0c57 | ||
|
|
6e752e5a06 | ||
|
|
56cad63541 | ||
|
|
66e1dda563 | ||
|
|
ce479299eb | ||
|
|
dec26f6b41 | ||
|
|
1ed5562650 | ||
|
|
9ee3428578 | ||
|
|
4ea9d43a94 | ||
|
|
0522b967ca | ||
|
|
50bee7a5c0 | ||
|
|
249b78d141 | ||
|
|
b61b85c3a8 | ||
|
|
813d4e771c | ||
|
|
837659fcdc | ||
|
|
37c360dc67 | ||
|
|
971299f062 | ||
|
|
03159c66d7 | ||
|
|
3beb5f7091 | ||
|
|
5c79173fa5 | ||
|
|
9d2d1a8498 | ||
|
|
e5232a3009 | ||
|
|
54f8c6539d | ||
|
|
4a29a49f28 | ||
|
|
2d6fa94db1 | ||
|
|
3ee8ff8cf4 | ||
|
|
22adec77cd | ||
|
|
3525a1610b | ||
|
|
802ab0dc14 | ||
|
|
3d9b7342e2 | ||
|
|
45e2fc7f2c | ||
|
|
4bdbde52a0 | ||
|
|
0bc0472a2f | ||
|
|
b0f37a6a9c | ||
|
|
f6977b8864 | ||
|
|
19880c6826 | ||
|
|
5a0949058a | ||
|
|
a82e93dabf | ||
|
|
4e6f64a28e | ||
|
|
1395d4b630 | ||
|
|
49b437687d | ||
|
|
52a72aeee4 | ||
|
|
0b4b9f1fb7 | ||
|
|
f56a7e92e0 | ||
|
|
80a6e8ad26 | ||
|
|
345ddebfc7 | ||
|
|
91626b8efd | ||
|
|
91d7d6f1df | ||
|
|
079ce7ccc1 | ||
|
|
c1db96126e | ||
|
|
560e62d568 | ||
|
|
c1534dfe44 | ||
|
|
5fd83c6928 | ||
|
|
36930ef007 | ||
|
|
4f97a3f18e | ||
|
|
7502f8d5b1 | ||
|
|
db82e0b370 | ||
|
|
f1cec89f9e | ||
|
|
bcfff84461 | ||
|
|
7aa49c9b76 | ||
|
|
b7616c61e2 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
2019
HandmadeMath.c
Normal file
2019
HandmadeMath.c
Normal file
File diff suppressed because it is too large
Load Diff
673
HandmadeMath.h
Normal file
673
HandmadeMath.h
Normal file
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
HandmadeMath.h v2.0.0
|
||||
|
||||
This is a single header file with a bunch of useful types and functions for
|
||||
games and graphics. Consider it a lightweight alternative to GLM that works
|
||||
both C and C++.
|
||||
|
||||
=============================================================================
|
||||
CONFIG
|
||||
=============================================================================
|
||||
|
||||
By default, all angles in Handmade Math are specified in radians. However, it
|
||||
can be configured to use degrees or turns instead. Use one of the following
|
||||
defines to specify the default unit for angles:
|
||||
|
||||
#define HANDMADE_MATH_USE_RADIANS
|
||||
#define HANDMADE_MATH_USE_DEGREES
|
||||
#define HANDMADE_MATH_USE_TURNS
|
||||
|
||||
Regardless of the default angle, you can use the following functions to
|
||||
specify an angle in a particular unit:
|
||||
|
||||
HMM_AngleRad(radians)
|
||||
HMM_AngleDeg(degrees)
|
||||
HMM_AngleTurn(turns)
|
||||
|
||||
The definitions of these functions change depending on the default unit.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Handmade Math ships with SSE (SIMD) implementations of several common
|
||||
operations. To disable the use of SSE intrinsics, you must define
|
||||
HANDMADE_MATH_NO_SSE before including this file:
|
||||
|
||||
#define HANDMADE_MATH_NO_SSE
|
||||
#include "HandmadeMath.h"
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
To use Handmade Math without the C runtime library, you must provide your own
|
||||
implementations of basic math functions. Otherwise, HandmadeMath.h will use
|
||||
the runtime library implementation of these functions.
|
||||
|
||||
Define HANDMADE_MATH_PROVIDE_MATH_FUNCTIONS and provide your own
|
||||
implementations of HMM_SINF, HMM_COSF, HMM_TANF, HMM_ACOSF, and HMM_SQRTF
|
||||
before including HandmadeMath.h, like so:
|
||||
|
||||
#define HANDMADE_MATH_PROVIDE_MATH_FUNCTIONS
|
||||
#define HMM_SINF MySinF
|
||||
#define HMM_COSF MyCosF
|
||||
#define HMM_TANF MyTanF
|
||||
#define HMM_ACOSF MyACosF
|
||||
#define HMM_SQRTF MySqrtF
|
||||
#include "HandmadeMath.h"
|
||||
|
||||
By default, it is assumed that your math functions take radians. To use
|
||||
different units, you must define HMM_ANGLE_USER_TO_INTERNAL and
|
||||
HMM_ANGLE_INTERNAL_TO_USER. For example, if you want to use degrees in your
|
||||
code but your math functions use turns:
|
||||
|
||||
#define HMM_ANGLE_USER_TO_INTERNAL(a) ((a)*HMM_DegToTurn)
|
||||
#define HMM_ANGLE_INTERNAL_TO_USER(a) ((a)*HMM_TurnToDeg)
|
||||
|
||||
=============================================================================
|
||||
|
||||
LICENSE
|
||||
|
||||
This software is in the public domain. Where that dedictaion is not
|
||||
recognized, you are granted a perpetual, irrevocable license to copy,
|
||||
distribute, and modify this file as you see fit.
|
||||
|
||||
=============================================================================
|
||||
|
||||
CREDITS
|
||||
|
||||
Originally written by Zakary Strange.
|
||||
|
||||
Functionality:
|
||||
Zakary Strange (strangezak@protonmail.com && @strangezak)
|
||||
Matt Mascarenhas (@miblo_)
|
||||
Aleph
|
||||
FieryDrake (@fierydrake)
|
||||
Gingerbill (@TheGingerBill)
|
||||
Ben Visness (@bvisness)
|
||||
Trinton Bullard (@Peliex_Dev)
|
||||
@AntonDan
|
||||
Logan Forman (@dev_dwarf)
|
||||
|
||||
Fixes:
|
||||
Jeroen van Rijn (@J_vanRijn)
|
||||
Kiljacken (@Kiljacken)
|
||||
Insofaras (@insofaras)
|
||||
Daniel Gibson (@DanielGibson)
|
||||
*/
|
||||
|
||||
#ifndef HANDMADE_MATH_H
|
||||
#define HANDMADE_MATH_H
|
||||
|
||||
#if !defined(HANDMADE_MATH_NO_SIMD)
|
||||
#if defined(__ARM_NEON) || defined(__ARM_NEON__)
|
||||
#define HANDMADE_MATH__USE_NEON 1
|
||||
#include <arm_neon.h>
|
||||
#elif defined(_MSC_VER)
|
||||
#if defined(_M_AMD64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)
|
||||
#define HANDMADE_MATH__USE_SSE 1
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
#elif defined(__SSE__)
|
||||
#define HANDMADE_MATH__USE_SSE 1
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define HMM_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#elif defined(_MSC_VER)
|
||||
#define HMM_DEPRECATED(msg) __declspec(deprecated(msg))
|
||||
#else
|
||||
#define HMM_DEPRECATED(msg)
|
||||
#endif
|
||||
|
||||
#if !defined(HANDMADE_MATH_USE_DEGREES) && !defined(HANDMADE_MATH_USE_TURNS) && !defined(HANDMADE_MATH_USE_RADIANS)
|
||||
#define HANDMADE_MATH_USE_RADIANS
|
||||
#endif
|
||||
|
||||
#define HMM_PI 3.14159265358979323846
|
||||
#define HMM_PI32 3.14159265359f
|
||||
#define HMM_DEG180 180.0
|
||||
#define HMM_DEG18032 180.0f
|
||||
#define HMM_TURNHALF 0.5
|
||||
#define HMM_TURNHALF32 0.5f
|
||||
#define HMM_RadToDeg ((float)(HMM_DEG180 / HMM_PI))
|
||||
#define HMM_RadToTurn ((float)(HMM_TURNHALF / HMM_PI))
|
||||
#define HMM_DegToRad ((float)(HMM_PI / HMM_DEG180))
|
||||
#define HMM_DegToTurn ((float)(HMM_TURNHALF / HMM_DEG180))
|
||||
#define HMM_TurnToRad ((float)(HMM_PI / HMM_TURNHALF))
|
||||
#define HMM_TurnToDeg ((float)(HMM_DEG180 / HMM_TURNHALF))
|
||||
|
||||
#if defined(HANDMADE_MATH_USE_RADIANS)
|
||||
#define HMM_AngleRad(a) (a)
|
||||
#define HMM_AngleDeg(a) ((a)*HMM_DegToRad)
|
||||
#define HMM_AngleTurn(a) ((a)*HMM_TurnToRad)
|
||||
#elif defined(HANDMADE_MATH_USE_DEGREES)
|
||||
#define HMM_AngleRad(a) ((a)*HMM_RadToDeg)
|
||||
#define HMM_AngleDeg(a) (a)
|
||||
#define HMM_AngleTurn(a) ((a)*HMM_TurnToDeg)
|
||||
#elif defined(HANDMADE_MATH_USE_TURNS)
|
||||
#define HMM_AngleRad(a) ((a)*HMM_RadToTurn)
|
||||
#define HMM_AngleDeg(a) ((a)*HMM_DegToTurn)
|
||||
#define HMM_AngleTurn(a) (a)
|
||||
#endif
|
||||
|
||||
#if !defined(HANDMADE_MATH_PROVIDE_MATH_FUNCTIONS)
|
||||
#include <math.h>
|
||||
#define HMM_SINF sinf
|
||||
#define HMM_COSF cosf
|
||||
#define HMM_TANF tanf
|
||||
#define HMM_SQRTF sqrtf
|
||||
#define HMM_ACOSF acosf
|
||||
#endif
|
||||
|
||||
#if !defined(HMM_ANGLE_USER_TO_INTERNAL)
|
||||
#define HMM_ANGLE_USER_TO_INTERNAL(a) (HMM_ToRad(a))
|
||||
#endif
|
||||
|
||||
#if !defined(HMM_ANGLE_INTERNAL_TO_USER)
|
||||
#if defined(HANDMADE_MATH_USE_RADIANS)
|
||||
#define HMM_ANGLE_INTERNAL_TO_USER(a) (a)
|
||||
#elif defined(HANDMADE_MATH_USE_DEGREES)
|
||||
#define HMM_ANGLE_INTERNAL_TO_USER(a) ((a)*HMM_RadToDeg)
|
||||
#elif defined(HANDMADE_MATH_USE_TURNS)
|
||||
#define HMM_ANGLE_INTERNAL_TO_USER(a) ((a)*HMM_RadToTurn)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define HMM_MIN(a, b) ((a) > (b) ? (b) : (a))
|
||||
#define HMM_MAX(a, b) ((a) < (b) ? (b) : (a))
|
||||
#define HMM_ABS(a) ((a) > 0 ? (a) : -(a))
|
||||
#define HMM_MOD(a, m) (((a) % (m)) >= 0 ? ((a) % (m)) : (((a) % (m)) + (m)))
|
||||
#define HMM_SQUARE(x) ((x) * (x))
|
||||
|
||||
#define HMMFMT_VEC3 "[%g,%g,%g]"
|
||||
#define HMMPRINT_VEC3(vec) vec.x, vec.y, vec.z
|
||||
|
||||
#define FMT_VEC4 "[%g,%g,%g,%g]"
|
||||
#define PRINT_VEC4(vec) vec.x, vec.y, vec.z, vec.w
|
||||
|
||||
#define FMT_M4 "[%g,%g,%g,%g\n%g,%g,%g,%g\n%g,%g,%g,%g\n%g,%g,%g,%g]"
|
||||
#define PRINT_M4(m) m.e[0][0], m.e[0][1], m.e[0][2], m.e[0][3], m.e[1][0], m.e[1][1], m.e[1][2], m.e[1][3], m.e[2][0], m.e[2][1], m.e[2][2], m.e[2][3], m.e[3][0], m.e[3][1], m.e[3][2], m.e[3][3]
|
||||
|
||||
typedef union HMM_Vec2 {
|
||||
struct
|
||||
{
|
||||
float X, Y;
|
||||
};
|
||||
|
||||
struct {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float U, V;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float Left, Right;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float Width, Height;
|
||||
};
|
||||
|
||||
float Elements[2];
|
||||
float e[2];
|
||||
|
||||
} HMM_Vec2;
|
||||
|
||||
typedef union HMM_Vec3 {
|
||||
struct
|
||||
{
|
||||
float X, Y, Z;
|
||||
};
|
||||
|
||||
struct { float x, y, z; };
|
||||
|
||||
struct
|
||||
{
|
||||
float U, V, W;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float R, G, B;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 XY;
|
||||
float _Ignored0;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 xy;
|
||||
float _Ignored4;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float _Ignored1;
|
||||
HMM_Vec2 YZ;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 UV;
|
||||
float _Ignored2;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float _Ignored3;
|
||||
HMM_Vec2 VW;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 cp;
|
||||
float _Ignored5;
|
||||
};
|
||||
|
||||
float Elements[3];
|
||||
float e[3];
|
||||
|
||||
} HMM_Vec3;
|
||||
|
||||
typedef union HMM_Quat {
|
||||
struct
|
||||
{
|
||||
union {
|
||||
HMM_Vec3 XYZ;
|
||||
struct
|
||||
{
|
||||
float X, Y, Z;
|
||||
};
|
||||
};
|
||||
|
||||
float W;
|
||||
};
|
||||
|
||||
struct {float x, y, z, w;};
|
||||
|
||||
float Elements[4];
|
||||
float e[4];
|
||||
|
||||
#ifdef HANDMADE_MATH__USE_SSE
|
||||
__m128 SSE;
|
||||
#endif
|
||||
} HMM_Quat;
|
||||
|
||||
|
||||
typedef union HMM_Vec4 {
|
||||
struct
|
||||
{
|
||||
union {
|
||||
HMM_Vec3 XYZ;
|
||||
struct
|
||||
{
|
||||
float X, Y, Z;
|
||||
};
|
||||
HMM_Vec3 xyz;
|
||||
};
|
||||
|
||||
float W;
|
||||
};
|
||||
struct
|
||||
{
|
||||
union {
|
||||
HMM_Vec3 RGB;
|
||||
struct
|
||||
{
|
||||
float R, G, B;
|
||||
};
|
||||
};
|
||||
|
||||
float A;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 XY;
|
||||
float _Ignored0;
|
||||
float _Ignored1;
|
||||
};
|
||||
|
||||
struct {
|
||||
HMM_Vec2 xy;
|
||||
float _ig0;
|
||||
float _ig1;
|
||||
};
|
||||
|
||||
struct {
|
||||
HMM_Vec2 _2;
|
||||
float _ig2;
|
||||
float _ig3;
|
||||
};
|
||||
|
||||
struct {
|
||||
HMM_Vec3 _3;
|
||||
float _ig4;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float _Ignored2;
|
||||
HMM_Vec2 YZ;
|
||||
float _Ignored3;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
float _Ignored4;
|
||||
float _Ignored5;
|
||||
HMM_Vec2 ZW;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
HMM_Vec2 cp;
|
||||
HMM_Vec2 wh;
|
||||
};
|
||||
|
||||
HMM_Quat quat;
|
||||
struct {float x, y, z, w; };
|
||||
struct {float r, g, b, a; };
|
||||
struct {float u0, u1, v0, v1;};
|
||||
|
||||
float Elements[4];
|
||||
float e[4];
|
||||
|
||||
#ifdef HANDMADE_MATH__USE_SSE
|
||||
__m128 SSE;
|
||||
#endif
|
||||
#ifdef HANDMADE_MATH__USE_NEON
|
||||
float32x4_t NEON;
|
||||
#endif
|
||||
} HMM_Vec4;
|
||||
|
||||
typedef union HMM_Mat2 {
|
||||
float Elements[2][2];
|
||||
HMM_Vec2 Columns[2];
|
||||
|
||||
} HMM_Mat2;
|
||||
|
||||
typedef union HMM_Mat3 {
|
||||
float Elements[3][3];
|
||||
HMM_Vec3 Columns[3];
|
||||
|
||||
} HMM_Mat3;
|
||||
|
||||
typedef union HMM_Mat4 {
|
||||
float Elements[4][4];
|
||||
HMM_Vec4 Columns[4];
|
||||
HMM_Vec4 col[4];
|
||||
float e[4][4];
|
||||
float em[16];
|
||||
} HMM_Mat4;
|
||||
|
||||
extern const HMM_Vec2 v2zero;
|
||||
extern const HMM_Vec2 v2one;
|
||||
extern const HMM_Vec3 v3zero;
|
||||
extern const HMM_Vec3 v3one;
|
||||
extern const HMM_Vec4 v4zero;
|
||||
|
||||
typedef signed int HMM_Bool;
|
||||
|
||||
extern const HMM_Vec3 vX;
|
||||
extern const HMM_Vec3 vY;
|
||||
extern const HMM_Vec3 vZ;
|
||||
|
||||
extern const HMM_Vec3 vUP;
|
||||
extern const HMM_Vec3 vDOWN;
|
||||
extern const HMM_Vec3 vFWD;
|
||||
extern const HMM_Vec3 vBKWD;
|
||||
extern const HMM_Vec3 vLEFT;
|
||||
extern const HMM_Vec3 vRIGHT;
|
||||
|
||||
extern const HMM_Mat4 MAT1;
|
||||
|
||||
extern const HMM_Quat QUAT1;
|
||||
|
||||
/*
|
||||
* Angle unit conversion functions
|
||||
*/
|
||||
float HMM_ToRad(float Angle);
|
||||
float HMM_ToDeg(float Angle);
|
||||
float HMM_ToTurn(float Angle);
|
||||
|
||||
/*
|
||||
* Floating-point math functions
|
||||
*/
|
||||
float HMM_SinF(float Angle);
|
||||
float HMM_CosF(float Angle);
|
||||
float HMM_TanF(float Angle);
|
||||
float HMM_ACosF(float Arg);
|
||||
float HMM_SqrtF(float Float);
|
||||
float HMM_InvSqrtF(float Float);
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
float HMM_Lerp(float A, float Time, float B);
|
||||
float HMM_Clamp(float Min, float Value, float Max);
|
||||
float frand(float max);
|
||||
|
||||
/*
|
||||
* Vector initialization
|
||||
*/
|
||||
HMM_Vec2 HMM_V2(float X, float Y);
|
||||
HMM_Vec3 HMM_V3(float X, float Y, float Z);
|
||||
HMM_Vec3 HMM_V3i(float i);
|
||||
HMM_Vec4 HMM_V4(float X, float Y, float Z, float W);
|
||||
HMM_Vec4 HMM_V4V(HMM_Vec3 Vector, float W);
|
||||
|
||||
/*
|
||||
* Binary vector operations
|
||||
*/
|
||||
HMM_Vec2 HMM_AddV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Vec3 HMM_AddV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
HMM_Vec4 HMM_AddV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
HMM_Vec2 HMM_SubV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Vec3 HMM_SubV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
HMM_Vec4 HMM_SubV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
HMM_Vec2 HMM_MulV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Vec2 HMM_MulV2F(HMM_Vec2 Left, float Right);
|
||||
HMM_Vec3 HMM_MulV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
HMM_Vec3 HMM_MulV3F(HMM_Vec3 Left, float Right);
|
||||
HMM_Vec4 HMM_MulV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
HMM_Vec4 HMM_MulV4F(HMM_Vec4 Left, float Right);
|
||||
HMM_Vec2 HMM_DivV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Vec2 HMM_DivV2F(HMM_Vec2 Left, float Right);
|
||||
HMM_Vec3 HMM_DivV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
HMM_Vec3 HMM_DivV3F(HMM_Vec3 Left, float Right);
|
||||
HMM_Vec4 HMM_DivV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
HMM_Vec4 HMM_DivV4F(HMM_Vec4 Left, float Right);
|
||||
HMM_Bool HMM_EqV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Bool HMM_EqV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
HMM_Bool HMM_EqV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
float HMM_DotV2(HMM_Vec2 Left, HMM_Vec2 Right);
|
||||
HMM_Vec2 HMM_ProjV2(HMM_Vec2 a, HMM_Vec2 b);
|
||||
float HMM_DotV3(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
float HMM_DotV4(HMM_Vec4 Left, HMM_Vec4 Right);
|
||||
HMM_Vec3 HMM_Cross(HMM_Vec3 Left, HMM_Vec3 Right);
|
||||
|
||||
/*
|
||||
* Unary vector operations
|
||||
*/
|
||||
float HMM_LenSqrV2(HMM_Vec2 A);
|
||||
float HMM_LenSqrV3(HMM_Vec3 A);
|
||||
float HMM_LenSqrV4(HMM_Vec4 A);
|
||||
float HMM_LenV2(HMM_Vec2 A);
|
||||
float HMM_AngleV2(HMM_Vec2 a, HMM_Vec2 b);
|
||||
float HMM_DistV2(HMM_Vec2 a, HMM_Vec2 b);
|
||||
HMM_Vec2 HMM_V2Rotate(HMM_Vec2 v, float angle);
|
||||
float HMM_LenV3(HMM_Vec3 A);
|
||||
float HMM_DistV3(HMM_Vec3 a, HMM_Vec3 b);
|
||||
float HMM_AngleV3(HMM_Vec3 a, HMM_Vec3 b);
|
||||
float HMM_LenV4(HMM_Vec4 A);
|
||||
float HMM_AngleV4(HMM_Vec4 a, HMM_Vec4 b);
|
||||
HMM_Vec2 HMM_NormV2(HMM_Vec2 A);
|
||||
HMM_Vec3 HMM_NormV3(HMM_Vec3 A);
|
||||
HMM_Vec4 HMM_NormV4(HMM_Vec4 A);
|
||||
|
||||
/*
|
||||
* Utility vector functions
|
||||
*/
|
||||
HMM_Vec2 HMM_LerpV2(HMM_Vec2 A, float Time, HMM_Vec2 B);
|
||||
HMM_Vec3 HMM_LerpV3(HMM_Vec3 A, float Time, HMM_Vec3 B);
|
||||
HMM_Vec4 HMM_LerpV4(HMM_Vec4 A, float Time, HMM_Vec4 B);
|
||||
|
||||
/*
|
||||
* SSE stuff
|
||||
*/
|
||||
HMM_Vec4 HMM_LinearCombineV4M4(HMM_Vec4 Left, HMM_Mat4 Right);
|
||||
|
||||
/*
|
||||
* 2x2 Matrices
|
||||
*/
|
||||
HMM_Mat2 HMM_M2(void);
|
||||
HMM_Mat2 HMM_M2D(float Diagonal);
|
||||
HMM_Mat2 HMM_TransposeM2(HMM_Mat2 Matrix);
|
||||
HMM_Mat2 HMM_RotateM2(float angle);
|
||||
HMM_Mat2 HMM_AddM2(HMM_Mat2 Left, HMM_Mat2 Right);
|
||||
HMM_Mat2 HMM_SubM2(HMM_Mat2 Left, HMM_Mat2 Right);
|
||||
HMM_Vec2 HMM_MulM2V2(HMM_Mat2 Matrix, HMM_Vec2 Vector);
|
||||
HMM_Mat2 HMM_MulM2(HMM_Mat2 Left, HMM_Mat2 Right);
|
||||
HMM_Mat2 HMM_MulM2F(HMM_Mat2 Matrix, float Scalar);
|
||||
HMM_Mat2 HMM_DivM2F(HMM_Mat2 Matrix, float Scalar);
|
||||
float HMM_DeterminantM2(HMM_Mat2 Matrix);
|
||||
HMM_Mat2 HMM_InvGeneralM2(HMM_Mat2 Matrix);
|
||||
|
||||
/*
|
||||
* 3x3 Matrices
|
||||
*/
|
||||
HMM_Mat3 HMM_M3(void);
|
||||
HMM_Mat3 HMM_M3D(float Diagonal);
|
||||
HMM_Mat3 HMM_Translate2D(HMM_Vec2 p);
|
||||
HMM_Mat3 HMM_RotateM3(float angle);
|
||||
HMM_Mat3 HMM_ScaleM3(HMM_Vec2 s);
|
||||
HMM_Mat3 HMM_TransposeM3(HMM_Mat3 Matrix);
|
||||
HMM_Mat3 HMM_AddM3(HMM_Mat3 Left, HMM_Mat3 Right);
|
||||
HMM_Mat3 HMM_SubM3(HMM_Mat3 Left, HMM_Mat3 Right);
|
||||
HMM_Vec3 HMM_MulM3V3(HMM_Mat3 Matrix, HMM_Vec3 Vector);
|
||||
HMM_Mat3 HMM_MulM3(HMM_Mat3 Left, HMM_Mat3 Right);
|
||||
HMM_Mat3 HMM_MulM3F(HMM_Mat3 Matrix, float Scalar);
|
||||
HMM_Mat3 HMM_DivM3F(HMM_Mat3 Matrix, float Scalar);
|
||||
HMM_Mat2 HMM_ScaleM2(HMM_Vec2 Scale);
|
||||
float HMM_DeterminantM3(HMM_Mat3 Matrix);
|
||||
HMM_Mat3 HMM_M2Basis(HMM_Mat2 basis);
|
||||
HMM_Mat3 HMM_InvGeneralM3(HMM_Mat3 Matrix);
|
||||
|
||||
/*
|
||||
* 4x4 Matrices
|
||||
*/
|
||||
HMM_Mat4 HMM_M4(void);
|
||||
HMM_Mat4 HMM_M4D(float Diagonal);
|
||||
HMM_Mat4 HMM_TransposeM4(HMM_Mat4 Matrix);
|
||||
HMM_Mat4 HMM_AddM4(HMM_Mat4 Left, HMM_Mat4 Right);
|
||||
HMM_Mat4 HMM_SubM4(HMM_Mat4 Left, HMM_Mat4 Right);
|
||||
HMM_Mat4 HMM_MulM4(HMM_Mat4 Left, HMM_Mat4 Right);
|
||||
HMM_Mat4 HMM_MulM4_P(HMM_Mat4 *Left, HMM_Mat4 *Right);
|
||||
HMM_Mat4 HMM_MulM4F(HMM_Mat4 Matrix, float Scalar);
|
||||
HMM_Vec4 HMM_MulM4V4(HMM_Mat4 Matrix, HMM_Vec4 Vector);
|
||||
HMM_Vec4 HMM_MulM4V4_P(HMM_Mat4 *Matrix, HMM_Vec4 *Vector);
|
||||
HMM_Mat4 HMM_DivM4F(HMM_Mat4 Matrix, float Scalar);
|
||||
float HMM_DeterminantM4(HMM_Mat4 Matrix);
|
||||
|
||||
// Returns a general-purpose inverse of an HMM_Mat4. Note that special-purpose inverses of many transformations
|
||||
// are available and will be more efficient.
|
||||
HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix);
|
||||
|
||||
/*
|
||||
* Common graphics transformations
|
||||
*/
|
||||
|
||||
// Produces a right-handed orthographic projection matrix with Z ranging from -1 to 1 (the GL convention).
|
||||
// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes.
|
||||
// Near and Far specify the distances to the near and far clipping planes.
|
||||
HMM_Mat4 HMM_Orthographic_RH_NO(float Left, float Right, float Bottom, float Top, float Near, float Far);
|
||||
|
||||
// Produces a right-handed orthographic projection matrix with Z ranging from 0 to 1 (the DirectX convention).
|
||||
// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes.
|
||||
// Near and Far specify the distances to the near and far clipping planes.
|
||||
HMM_Mat4 HMM_Orthographic_RH_ZO(float Left, float Right, float Bottom, float Top, float Near, float Far);
|
||||
|
||||
// Produces a left-handed orthographic projection matrix with Z ranging from -1 to 1 (the GL convention).
|
||||
// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes.
|
||||
// Near and Far specify the distances to the near and far clipping planes.
|
||||
HMM_Mat4 HMM_Orthographic_LH_NO(float Left, float Right, float Bottom, float Top, float Near, float Far);
|
||||
|
||||
// Produces a left-handed orthographic projection matrix with Z ranging from 0 to 1 (the DirectX convention).
|
||||
// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes.
|
||||
// Near and Far specify the distances to the near and far clipping planes.
|
||||
HMM_Mat4 HMM_Orthographic_LH_ZO(float Left, float Right, float Bottom, float Top, float Near, float Far);
|
||||
|
||||
// Returns an inverse for the given orthographic projection matrix. Works for all orthographic
|
||||
// projection matrices, regardless of handedness or NDC convention.
|
||||
HMM_Mat4 HMM_InvOrthographic(HMM_Mat4 OrthoMatrix);
|
||||
HMM_Mat4 HMM_Perspective_RH_NO(float FOV, float AspectRatio, float Near, float Far);
|
||||
HMM_Mat4 HMM_Perspective_RH_ZO(float FOV, float AspectRatio, float Near, float Far);
|
||||
HMM_Mat4 HMM_Perspective_LH_NO(float FOV, float AspectRatio, float Near, float Far);
|
||||
HMM_Mat4 HMM_Perspective_LH_ZO(float FOV, float AspectRatio, float Near, float Far);
|
||||
|
||||
HMM_Mat4 HMM_Perspective_Metal(float FOV, float AspectRation, float Near, float Far);
|
||||
HMM_Mat4 HMM_Orthographic_Metal(float l, float r, float b, float t, float near, float far);
|
||||
|
||||
HMM_Mat4 HMM_Orthographic_DX(float l, float r, float b, float t, float near, float far);
|
||||
HMM_Mat4 HMM_Orthographic_GL(float l, float r, float b, float t, float near, float far);
|
||||
|
||||
HMM_Mat4 HMM_InvPerspective_RH(HMM_Mat4 PerspectiveMatrix);
|
||||
HMM_Mat4 HMM_InvPerspective_LH(HMM_Mat4 PerspectiveMatrix);
|
||||
HMM_Mat4 HMM_Translate(HMM_Vec3 Translation);
|
||||
HMM_Mat4 HMM_InvTranslate(HMM_Mat4 TranslationMatrix);
|
||||
HMM_Mat4 HMM_Rotate_RH(float Angle, HMM_Vec3 Axis);
|
||||
HMM_Mat4 HMM_Rotate_LH(float Angle, HMM_Vec3 Axis);
|
||||
HMM_Mat4 HMM_InvRotate(HMM_Mat4 RotationMatrix);
|
||||
HMM_Mat4 HMM_Scale(HMM_Vec3 Scale);
|
||||
HMM_Mat4 HMM_InvScale(HMM_Mat4 ScaleMatrix);
|
||||
HMM_Mat4 _HMM_LookAt(HMM_Vec3 F, HMM_Vec3 S, HMM_Vec3 U, HMM_Vec3 Eye);
|
||||
HMM_Mat4 HMM_LookAt_RH(HMM_Vec3 Eye, HMM_Vec3 Center, HMM_Vec3 Up);
|
||||
HMM_Mat4 HMM_LookAt_LH(HMM_Vec3 Eye, HMM_Vec3 Center, HMM_Vec3 Up);
|
||||
HMM_Mat4 HMM_InvLookAt(HMM_Mat4 Matrix);
|
||||
|
||||
/*
|
||||
* Quaternion operations
|
||||
*/
|
||||
HMM_Vec3 HMM_QVRot(HMM_Vec3 v, HMM_Quat q)
|
||||
;
|
||||
HMM_Quat HMM_Q(float X, float Y, float Z, float W);
|
||||
HMM_Quat HMM_QV4(HMM_Vec4 Vector);
|
||||
HMM_Quat HMM_AddQ(HMM_Quat Left, HMM_Quat Right);
|
||||
HMM_Quat HMM_SubQ(HMM_Quat Left, HMM_Quat Right);
|
||||
HMM_Quat HMM_MulQ(HMM_Quat Left, HMM_Quat Right);
|
||||
HMM_Quat HMM_MulQF(HMM_Quat Left, float Multiplicative);
|
||||
HMM_Quat HMM_DivQF(HMM_Quat Left, float Divnd);
|
||||
float HMM_DotQ(HMM_Quat Left, HMM_Quat Right);
|
||||
HMM_Quat HMM_InvQ(HMM_Quat Left);
|
||||
HMM_Quat HMM_NormQ(HMM_Quat Quat);
|
||||
HMM_Quat _HMM_MixQ(HMM_Quat Left, float MixLeft, HMM_Quat Right, float MixRight);
|
||||
HMM_Quat HMM_NLerp(HMM_Quat Left, float Time, HMM_Quat Right);
|
||||
HMM_Quat HMM_SLerp(HMM_Quat Left, float Time, HMM_Quat Right);
|
||||
HMM_Mat4 HMM_QToM4(HMM_Quat Left);
|
||||
HMM_Mat4 HMM_M4TRS(HMM_Vec3 t, HMM_Quat q, HMM_Vec3 s);
|
||||
HMM_Mat3 HMM_M3TRS(HMM_Vec2 t, float angle, HMM_Vec2 s);
|
||||
HMM_Mat3 HMM_Mat4ToMat3(HMM_Mat4 m);
|
||||
|
||||
HMM_Quat HMM_M4ToQ_RH(HMM_Mat4 M);
|
||||
HMM_Quat HMM_M4ToQ_LH(HMM_Mat4 M);
|
||||
HMM_Quat HMM_QFromAxisAngle_RH(HMM_Vec3 Axis, float AngleOfRotation);
|
||||
HMM_Quat HMM_QFromAxisAngle_LH(HMM_Vec3 Axis, float AngleOfRotation);
|
||||
|
||||
float HMM_Q_Roll(HMM_Quat q);
|
||||
float HMM_Q_Yaw(HMM_Quat q);
|
||||
float HMM_Q_Pitch(HMM_Quat q);
|
||||
|
||||
#endif /* HANDMADE_MATH_H */
|
||||
2
Makefile
Normal file
2
Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
default:
|
||||
cc -fPIC -shared *.c -I../cell/source -Iimgui -lSDL3 -lcell -o prosperon.dylib
|
||||
352
action.cm
Normal file
352
action.cm
Normal file
@@ -0,0 +1,352 @@
|
||||
// Action mapping system that sits at the root of the scene tree
|
||||
// Consumes raw input and reissues as named actions
|
||||
|
||||
var io = use('cellfs')
|
||||
var input = use('input')
|
||||
var json = use('json')
|
||||
|
||||
var action = {}
|
||||
|
||||
var controller_map = {
|
||||
ps3: 'playstation',
|
||||
ps4: 'playstation',
|
||||
ps5: 'playstation',
|
||||
xbox: 'xbox360',
|
||||
xbox: 'xboxone',
|
||||
switch: 'switchpro',
|
||||
xbox: 'standard',
|
||||
gamecube: 'gamecube',
|
||||
switch: 'joyconleft',
|
||||
switch: 'joyconright',
|
||||
switch: 'joyconpair'
|
||||
}
|
||||
|
||||
action.get_icon_for_action = function(action)
|
||||
{
|
||||
var bindings = this.get_bindings_for_device(action)
|
||||
if (!length(bindings)) return null
|
||||
|
||||
var primary_binding = bindings[0]
|
||||
var button = null
|
||||
var key_mapping = {
|
||||
'escape': 'escape',
|
||||
'return': 'return',
|
||||
'space': 'space',
|
||||
'up': 'arrow_up',
|
||||
'down': 'arrow_down',
|
||||
'left': 'arrow_left',
|
||||
'right': 'arrow_right'
|
||||
}
|
||||
var key = null
|
||||
var controller_prefix = null
|
||||
var gamepad_mapping = null
|
||||
var icon_name = null
|
||||
|
||||
if (this.current_device == 'keyboard') {
|
||||
if (starts_with(primary_binding, 'mouse_button_')) {
|
||||
button = replace(primary_binding, 'mouse_button_', '')
|
||||
return 'ui/mouse/mouse_' + button + '.png'
|
||||
} else {
|
||||
// Handle special keyboard keys
|
||||
key = key_mapping[primary_binding] || primary_binding
|
||||
return 'ui/keyboard/keyboard_' + key + '.png'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.current_device == 'gamepad' && this.current_gamepad_type) {
|
||||
controller_prefix = controller_map[this.current_gamepad_type] || 'playstation'
|
||||
|
||||
// Map gamepad inputs to icon names
|
||||
gamepad_mapping = {
|
||||
'gamepad_a': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_cross' : 'xbox_button_a',
|
||||
'gamepad_b': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_circle' : 'xbox_button_b',
|
||||
'gamepad_x': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_square' : 'xbox_button_x',
|
||||
'gamepad_y': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_triangle' : 'xbox_button_y',
|
||||
'gamepad_dpup': controller_prefix + '_dpad_up',
|
||||
'gamepad_dpdown': controller_prefix + '_dpad_down',
|
||||
'gamepad_dpleft': controller_prefix + '_dpad_left',
|
||||
'gamepad_dpright': controller_prefix + '_dpad_right',
|
||||
'gamepad_l1': controller_prefix + '_trigger_l1',
|
||||
'gamepad_r1': controller_prefix + '_trigger_r1',
|
||||
'gamepad_l2': controller_prefix + '_trigger_l2',
|
||||
'gamepad_r2': controller_prefix + '_trigger_r2',
|
||||
'gamepad_start': this.get_start_button_icon()
|
||||
}
|
||||
|
||||
icon_name = gamepad_mapping[primary_binding]
|
||||
if (icon_name) {
|
||||
return 'ui/' + controller_prefix + '/' + icon_name + '.png'
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
action.get_start_button_icon = function() {
|
||||
if (this.current_gamepad_type == 'ps3') {
|
||||
return 'playstation3_button_start'
|
||||
} else if (this.current_gamepad_type == 'ps4') {
|
||||
return 'playstation4_button_options'
|
||||
} else if (this.current_gamepad_type == 'ps5') {
|
||||
return 'playstation5_button_options'
|
||||
} else {
|
||||
return 'xbox_button_start'
|
||||
}
|
||||
}
|
||||
|
||||
var default_action_map = {
|
||||
'move_up': ['w', 'gamepad_dpup', 'swipe_up'],
|
||||
'move_left': ['a', 'gamepad_dpleft', 'swipe_left'],
|
||||
'move_down': ['s', 'gamepad_dpdown', 'swipe_down'],
|
||||
'move_right': ['d', 'gamepad_dpright', 'swipe_right'],
|
||||
'accio': ['space', 'gamepad_x'],
|
||||
'reset': ['r', 'gamepad_y'],
|
||||
'undo': ['z'],
|
||||
'menu': ['escape', 'gamepad_start'],
|
||||
|
||||
'ui_up': ['w', 'up', 'gamepad_dpup'],
|
||||
'ui_down': ['s', 'down', 'gamepad_dpdown'],
|
||||
'ui_left': ['a', 'left', 'gamepad_dpleft'],
|
||||
'ui_right': ['d', 'right', 'gamepad_dpright'],
|
||||
|
||||
'confirm': ['return', 'space', 'mouse_button_left', 'gamepad_b'],
|
||||
'cancel': ['escape', 'gamepad_a'],
|
||||
|
||||
// Editor controls
|
||||
'brush_left': ['gamepad_dpleft', 'left'],
|
||||
'brush_right': ['gamepad_dpright', 'right'],
|
||||
'tool_up': ['gamepad_dpup', 'up'],
|
||||
'tool_down': ['gamepad_dpdown', 'down'],
|
||||
'tool_use': ['gamepad_a', 'mouse_button_left'],
|
||||
'editor_undo': [',', 'gamepad_l1'],
|
||||
'editor_redo': ['.', 'gamepad_r1'],
|
||||
'editor_inspect': ['C-p'],
|
||||
'editor_marquee': ['mouse_button_right', 'gamepad_r2'],
|
||||
'editor_erase': ['x', 'gamepad_b'],
|
||||
'editor_eyedrop': ['i', 'gamepad_x'],
|
||||
'editor_grab': ['mouse_button_middle', 'gamepad_y'],
|
||||
'editor_ccw': ['gamepad_l1', 'q'],
|
||||
'editor_cw': ['gamepad_r1', 'e'],
|
||||
}
|
||||
|
||||
default_action_map.witch_up = 'up'
|
||||
default_action_map.witch_down = 'down'
|
||||
default_action_map.witch_left = 'left'
|
||||
default_action_map.witch_right = 'right'
|
||||
default_action_map.accion = 'y'
|
||||
|
||||
// Utility to detect device from input id
|
||||
function detect_device(input_id) {
|
||||
if (starts_with(input_id, 'gamepad_')) return 'gamepad'
|
||||
if (starts_with(input_id, 'swipe_')) return 'touch'
|
||||
if (starts_with(input_id, 'mouse_button')) return 'keyboard' // Mouse buttons are part of keyboard/mouse controller
|
||||
if (starts_with(input_id, 'touch_')) return 'touch'
|
||||
return 'keyboard'
|
||||
}
|
||||
|
||||
// Display names for actions
|
||||
var action_display_names = {
|
||||
'move_up': 'Move Up',
|
||||
'move_down': 'Move Down',
|
||||
'move_left': 'Move Left',
|
||||
'move_right': 'Move Right',
|
||||
'ui_up': 'UI Up',
|
||||
'ui_down': 'UI Down',
|
||||
'ui_left': 'UI Left',
|
||||
'ui_right': 'UI Right',
|
||||
'menu': 'Menu',
|
||||
'confirm': 'Confirm',
|
||||
'cancel': 'Cancel',
|
||||
'reset': 'Reset',
|
||||
'accio': 'Summon',
|
||||
'brush_left': 'Previous Entity',
|
||||
'brush_right': 'Next Entity',
|
||||
'tool_up': 'Previous Tool',
|
||||
'tool_down': 'Next Tool',
|
||||
'tool_use': 'Use Tool'
|
||||
}
|
||||
|
||||
action.down = {}
|
||||
action.action_map = {}
|
||||
action.display_names = action_display_names
|
||||
action.is_rebinding = false
|
||||
action.rebind_target = null
|
||||
action.current_device = 'keyboard'
|
||||
action.current_gamepad_type = null
|
||||
|
||||
// Copy defaults
|
||||
arrfor(array(default_action_map), function(key) {
|
||||
action.action_map[key] = array(default_action_map[key])
|
||||
})
|
||||
|
||||
// Swipe‐recognizer state & tuning
|
||||
var swipe = { x0:null, y0:null, t0:0 }
|
||||
var SWIPE_MIN_DIST = 30 // pixels
|
||||
var SWIPE_MAX_TIME = 500 // ms
|
||||
action.on_input = function(action_id, evt)
|
||||
{
|
||||
// 1) Detect & store which device user is on (only from raw input, ignore passive inputs)
|
||||
var new_device = null
|
||||
var is_real_kb_mouse = false
|
||||
var gamepad_type = null
|
||||
if (action_id != 'mouse_move' && !starts_with(action_id, 'mouse_pos') && evt.pressed) {
|
||||
new_device = detect_device(action_id)
|
||||
|
||||
// For keyboard/mouse detection, also check if the event has modifier keys or is a mouse button
|
||||
// This helps distinguish real keyboard/mouse events from mapped actions
|
||||
is_real_kb_mouse = (new_device == 'keyboard' &&
|
||||
(evt.ctrl != null || evt.shift != null || evt.alt != null ||
|
||||
starts_with(action_id, 'mouse_button')))
|
||||
|
||||
if (new_device == 'keyboard' && !is_real_kb_mouse) {
|
||||
// This might be a mapped action, not a real keyboard/mouse event
|
||||
return
|
||||
}
|
||||
|
||||
if (new_device != this.current_device) {
|
||||
if (new_device == 'gamepad') {
|
||||
gamepad_type = evt.which != null ? input.gamepad_id_to_type(evt.which) : 'unknown'
|
||||
log.console("Input switched to 'gamepad' (event: " + action_id + ", type: " + gamepad_type + ")")
|
||||
this.current_gamepad_type = gamepad_type
|
||||
} else if (this.current_device == 'gamepad') {
|
||||
log.console("Input switched to 'keyboard/mouse' (event: " + action_id + ")")
|
||||
}
|
||||
this.current_device = new_device
|
||||
}
|
||||
}
|
||||
|
||||
// 2) If we're in rebind mode, grab the raw input
|
||||
if (this.is_rebinding) {
|
||||
if (evt.pressed) {
|
||||
// Only bind if it's from the same device type
|
||||
if (detect_device(action_id) == this.current_device) {
|
||||
this.rebind_action(this.rebind_target, action_id)
|
||||
this.save_bindings()
|
||||
}
|
||||
// Exit rebind mode
|
||||
this.is_rebinding = false
|
||||
this.rebind_target = null
|
||||
}
|
||||
return // Don't also fire the mapped action
|
||||
}
|
||||
|
||||
// 3) Otherwise, find all mapped actions for this input
|
||||
var matched_actions = []
|
||||
arrfor(array(this.action_map), mapped_action => {
|
||||
if (find(this.action_map[mapped_action], action_id) != null) {
|
||||
matched_actions[] = mapped_action
|
||||
|
||||
if (evt.pressed)
|
||||
this.down[mapped_action] = true
|
||||
else if (evt.released)
|
||||
this.down[mapped_action] = false
|
||||
}
|
||||
})
|
||||
|
||||
// Send all matched actions (only if we found mappings - this means it's a raw input)
|
||||
var i = 0
|
||||
if (length(matched_actions) > 0) {
|
||||
for (i = 0; i < length(matched_actions); i++) {
|
||||
// scene.recurse(game.root, 'on_input', [matched_actions[i], evt])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action.start_rebind = function(action_name) {
|
||||
if (!this.action_map[action_name]) return
|
||||
this.is_rebinding = true
|
||||
this.rebind_target = action_name
|
||||
}
|
||||
|
||||
action.rebind_action = function(action_name, new_key) {
|
||||
if (!this.action_map[action_name]) return
|
||||
|
||||
// Remove this key from all other actions
|
||||
arrfor(array(this.action_map), act => {
|
||||
var idx = find(this.action_map[act], new_key)
|
||||
if (idx != null)
|
||||
this.action_map[act] = array(array(this.action_map[act], 0, idx), array(this.action_map[act], idx+1))
|
||||
})
|
||||
|
||||
// Clear existing bindings for the current device from the target action
|
||||
var target_bindings = this.action_map[action_name]
|
||||
var i = length(target_bindings) - 1
|
||||
for (; i >= 0; i--) {
|
||||
if (detect_device(target_bindings[i]) == this.current_device)
|
||||
this.action_map[action_name] = array(array(this.action_map[action_name], 0, i), array(this.action_map[action_name], i+1))
|
||||
}
|
||||
|
||||
// Only insert into the target if it's the right device
|
||||
if (detect_device(new_key) == this.current_device)
|
||||
this.action_map[action_name].unshift(new_key)
|
||||
}
|
||||
|
||||
// Returns bindings for the current device only
|
||||
action.get_bindings_for_device = function(action_name) {
|
||||
var all = this.action_map[action_name] || []
|
||||
var self = this
|
||||
return filter(all, function(id) { return detect_device(id) == self.current_device })
|
||||
}
|
||||
|
||||
// Returns the primary binding for display - prefer keyboard/mouse, then others
|
||||
action.get_primary_binding = function(action_name) {
|
||||
var all = this.action_map[action_name] || []
|
||||
if (!length(all)) return '(unbound)'
|
||||
|
||||
// Prefer keyboard/mouse bindings for display stability (mouse buttons now detect as keyboard)
|
||||
var keyboard = filter(all, function(id) { return detect_device(id) == 'keyboard' })
|
||||
if (length(keyboard)) return keyboard[0]
|
||||
|
||||
// Fall back to any binding
|
||||
return all[0]
|
||||
}
|
||||
|
||||
// Returns the binding for the current device, or "Unbound!" if none
|
||||
action.get_current_device_binding = function(action_name) {
|
||||
var device_bindings = this.get_bindings_for_device(action_name)
|
||||
if (!length(device_bindings)) return 'Unbound!'
|
||||
return device_bindings[0]
|
||||
}
|
||||
|
||||
action.save_bindings = function() {
|
||||
var self = this
|
||||
var _save = function() {
|
||||
io.slurpwrite('keybindings.json', json.encode(self.action_map))
|
||||
} disruption {
|
||||
log.console("Failed to save key bindings")
|
||||
}
|
||||
_save()
|
||||
}
|
||||
|
||||
action.load_bindings = function() {
|
||||
var self = this
|
||||
var _load = function() {
|
||||
var data = null
|
||||
var bindings = null
|
||||
if (io.exists('keybindings.json')) {
|
||||
data = io.slurp('keybindings.json')
|
||||
bindings = object(json.decode(data), self.action_map)
|
||||
}
|
||||
} disruption {
|
||||
log.console("Failed to load key bindings")
|
||||
}
|
||||
_load()
|
||||
}
|
||||
|
||||
action.reset_to_defaults = function() {
|
||||
this.action_map = object(default_action_map)
|
||||
this.save_bindings()
|
||||
}
|
||||
|
||||
return function()
|
||||
{
|
||||
var obj = meme(action)
|
||||
obj.action_map = object(default_action_map)
|
||||
obj.display_names = action_display_names
|
||||
obj.is_rebinding = false
|
||||
obj.rebind_target = null
|
||||
obj.current_device = 'keyboard'
|
||||
obj.current_gamepad_type = null
|
||||
obj.load_bindings()
|
||||
return obj
|
||||
}
|
||||
95
anim2d.cm
Normal file
95
anim2d.cm
Normal file
@@ -0,0 +1,95 @@
|
||||
var film2d = use('film2d')
|
||||
var graphics = use('graphics')
|
||||
|
||||
var anim_proto = {
|
||||
type: 'sprite',
|
||||
|
||||
play: function(name) {
|
||||
var anim = name ? this._anims[name] : this._anims
|
||||
if (!anim || !anim.frames) return this
|
||||
this._anim = anim
|
||||
this._frame = 0
|
||||
this._elapsed = 0
|
||||
this._playing = true
|
||||
this.image = anim.frames[0].image
|
||||
return this
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
this._playing = false
|
||||
return this
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
this._playing = true
|
||||
return this
|
||||
},
|
||||
|
||||
update: function(dt) {
|
||||
if (!this._playing || !this._anim) return
|
||||
var frames = this._anim.frames
|
||||
this._elapsed += dt
|
||||
while (this._elapsed >= frames[this._frame].time) {
|
||||
this._elapsed -= frames[this._frame].time
|
||||
this._frame++
|
||||
if (this._frame >= length(frames)) {
|
||||
if (this._anim.loop) {
|
||||
this._frame = 0
|
||||
} else {
|
||||
this._frame = length(frames) - 1
|
||||
this._playing = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
this.image = frames[this._frame].image
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
}
|
||||
|
||||
return function(props) {
|
||||
var img = props.image || props.anim
|
||||
var anims = graphics.texture(img)
|
||||
|
||||
var defaults = {
|
||||
type: 'sprite',
|
||||
pos: {x: 0, y: 0},
|
||||
width: null,
|
||||
height: null,
|
||||
anchor_x: 0.5,
|
||||
anchor_y: 0.5,
|
||||
rotation: 0,
|
||||
color: {r: 1, g: 1, b: 1, a: 1},
|
||||
opacity: 1,
|
||||
tint: {r: 1, g: 1, b: 1, a: 1},
|
||||
filter: 'nearest',
|
||||
plane: 'default',
|
||||
layer: 0,
|
||||
groups: [],
|
||||
visible: true
|
||||
}
|
||||
|
||||
var data = object(defaults, props)
|
||||
data._anims = anims
|
||||
data._anim = null
|
||||
data._frame = 0
|
||||
data._elapsed = 0
|
||||
data._playing = false
|
||||
data.image = null
|
||||
|
||||
var s = meme(anim_proto, data)
|
||||
|
||||
// Auto-play: if anims is a single animation, start it
|
||||
if (anims && anims.frames) {
|
||||
s._anim = anims
|
||||
s._frame = 0
|
||||
s._playing = true
|
||||
s.image = anims.frames[0].image
|
||||
}
|
||||
|
||||
film2d.register(s)
|
||||
return s
|
||||
}
|
||||
226
camera.cm
226
camera.cm
@@ -1,121 +1,153 @@
|
||||
var backend = use('sdl_gpu')
|
||||
|
||||
var cam = {}
|
||||
|
||||
/*
|
||||
presentation can be one of
|
||||
/*
|
||||
presentation can be one of
|
||||
letterbox
|
||||
overscan
|
||||
stretch
|
||||
... or simply 'null' for no presentation
|
||||
.. or simply 'null' for no presentation
|
||||
*/
|
||||
|
||||
function rect_contains_pt(rect, x, y) {
|
||||
return x >= rect.x && x <= rect.x + rect.width &&
|
||||
y >= rect.y && y <= rect.y + rect.height
|
||||
}
|
||||
|
||||
function place_rect(src, dst, mode) {
|
||||
if (mode == 'stretch')
|
||||
return {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
|
||||
var sx = 0, sy = 0, s = 0, w = 0, h = 0, scale = 0
|
||||
if (mode == 'integer_scale') {
|
||||
sx = floor(dst.width / src.width)
|
||||
sy = floor(dst.height / src.height)
|
||||
s = max(1, min(sx, sy))
|
||||
w = src.width * s
|
||||
h = src.height * s
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
// letterbox (default)
|
||||
scale = min(dst.width / src.width, dst.height / src.height)
|
||||
w = src.width * scale
|
||||
h = src.height * scale
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
var basecam = {
|
||||
pos: [0,0], // where it is
|
||||
pos: {x:0,y:0},
|
||||
ortho:true,
|
||||
width: 100,
|
||||
aspect_ratio: 16/9,
|
||||
width: 1,
|
||||
height: 1,
|
||||
fov:50,
|
||||
near_z:0,
|
||||
far_z:1000,
|
||||
anchor:[0.5,0.5],
|
||||
anchor: {x:0.5,y:0.5},
|
||||
rotation:[0,0,0,1],
|
||||
presentation: "letterbox",
|
||||
background: {r:1,g:1,b:1,a:0},
|
||||
viewport: {x:0,y:0,width:1,height:1},
|
||||
}
|
||||
aspect_ratio() {
|
||||
return this.y/this.x;
|
||||
},
|
||||
|
||||
basecam.draw_rect = function(size)
|
||||
{
|
||||
var mode = this.presentation || "letterbox"
|
||||
var vp = {
|
||||
x:this.viewport.x,
|
||||
y:1-this.viewport.y-this.viewport.height,
|
||||
width:this.viewport.width,
|
||||
height:this.viewport.height
|
||||
}
|
||||
var src_rect = {x:0,y:0,width:this.size.x,height:this.size.y}
|
||||
var dst_rect = {x:vp.x*size.x,y:vp.y*size.y,width:vp.width*size.x,height:vp.height*size.y};
|
||||
return mode_rect(src_rect,dst_rect,mode);
|
||||
}
|
||||
rect() {
|
||||
return {x:0, y:0, width: this.width, height: this.height}
|
||||
},
|
||||
|
||||
basecam.screen2camera = function(pos)
|
||||
{
|
||||
var draw_rect = this.draw_rect(prosperon.window.size);
|
||||
var ret = [pos.x-draw_rect.x, pos.y - draw_rect.y];
|
||||
ret.x /= draw_rect.width;
|
||||
ret.y /= draw_rect.height;
|
||||
ret.y = 1 - ret.y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
basecam.screen2hud = function(pos)
|
||||
{
|
||||
var cam = this.screen2camera(pos);
|
||||
cam.x *= this.size.x;
|
||||
cam.y *= this.size.y;
|
||||
return cam;
|
||||
}
|
||||
|
||||
basecam.screen2world = function(pos)
|
||||
{
|
||||
var hud = this.screen2hud(pos);
|
||||
hud.x += this.transform.pos.x - this.size.x/2;
|
||||
hud.y += this.transform.pos.y - this.size.y/2;
|
||||
return hud;
|
||||
}
|
||||
|
||||
function mode_rect(src,dst,mode = "stretch")
|
||||
{
|
||||
var aspect_src = src.width/src.height;
|
||||
var aspect_dst = dst.width/dst.height;
|
||||
var out = {
|
||||
x:dst.x,
|
||||
y:dst.y,
|
||||
width:dst.width,
|
||||
height:dst.height
|
||||
};
|
||||
if (mode == "stretch") return out;
|
||||
|
||||
if (mode == "letterbox") {
|
||||
if (aspect_src > aspect_dst) {
|
||||
var scaled_h = out.width/aspect_src;
|
||||
var off = (out.height - scaled_h) * 0.5;
|
||||
out.y += off;
|
||||
out.height = scaled_h;
|
||||
} else {
|
||||
var scaled_w =out.height * aspect_src;
|
||||
var off = (out.width - scaled_w) * 0.5;
|
||||
out.x += off;
|
||||
out.width = scaled_w;
|
||||
sensor() {
|
||||
var ws = backend.get_window_size()
|
||||
return {
|
||||
width: ws.width,
|
||||
height: ws.height,
|
||||
}
|
||||
} else if (mode == "overscan"){
|
||||
if (aspect_src > aspect_dst) {
|
||||
var scaled_w = out.height * aspect_src;
|
||||
var off = (out.width - scaled_w) * 0.5;
|
||||
out.x += off;
|
||||
out.width = scaled_w;
|
||||
} else {
|
||||
var scaled_h = out.width / aspect_src;
|
||||
var off = (out.height - scaled_h) * 0.5;
|
||||
out.y += off;
|
||||
out.height = scaled_h;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// The rectangle (in window pixels) where this camera’s target will be drawn
|
||||
screen_rect() {
|
||||
var src = { width: this.width, height: this.height }
|
||||
var ws = backend.get_window_size()
|
||||
var dst = { x: 0, y: 0, width: ws.width, height: ws.height }
|
||||
return place_rect(src, dst, this.presentation)
|
||||
},
|
||||
|
||||
// --- Space converters ---------------------------------------------------
|
||||
// World -> View UV [0..1] independent of pixels/letterbox
|
||||
world_to_view_uv(wx, wy) {
|
||||
var ax = this.anchor.x, ay = this.anchor.y
|
||||
var u = (wx - this.pos.x) / this.width + ax
|
||||
var v = (wy - this.pos.y) / this.height + ay
|
||||
|
||||
// apply viewport (maps camera-local UV into sub-rect)
|
||||
var vp = this.viewport
|
||||
u = vp.x + u * vp.width
|
||||
v = vp.y + v * vp.height
|
||||
return { x: u, y: v }
|
||||
},
|
||||
|
||||
// View UV -> World
|
||||
view_uv_to_world(u, v) {
|
||||
var vp = this.viewport
|
||||
var uu = (u - vp.x) / vp.width
|
||||
var vv = (v - vp.y) / vp.height
|
||||
var ax = this.anchor.x, ay = this.anchor.y
|
||||
var wx = this.pos.x + (uu - ax) * this.width
|
||||
var wy = this.pos.y + (vv - ay) * this.height
|
||||
return { x: wx, y: wy }
|
||||
},
|
||||
|
||||
// World -> Window pixels (what you want for hit-tests)
|
||||
world_to_window(wx, wy) {
|
||||
var uv = this.world_to_view_uv(wx, wy)
|
||||
var sr = this.screen_rect()
|
||||
var sx = sr.x + uv.x * sr.width
|
||||
var sy = sr.y + uv.y * sr.height
|
||||
return { x: sx, y: sy }
|
||||
},
|
||||
|
||||
// Window pixels -> World (mouse picking)
|
||||
window_to_world(sx, sy) {
|
||||
var sr = this.screen_rect()
|
||||
var u = (sx - sr.x) / sr.width
|
||||
var v = 1 - (sy - sr.y) / sr.height
|
||||
return this.view_uv_to_world(u, v)
|
||||
},
|
||||
|
||||
// World -> normalized window [0..1]
|
||||
world_to_screen_norm(wx, wy) {
|
||||
var p = this.world_to_window(wx, wy)
|
||||
var ws = backend.get_window_size()
|
||||
return { x: p.x / ws.width, y: p.y / ws.height }
|
||||
},
|
||||
|
||||
// Normalized window [0..1] -> World
|
||||
screen_norm_to_world(nx, ny) {
|
||||
var ws = backend.get_window_size()
|
||||
var sx = nx * ws.width
|
||||
var sy = ny * ws.height
|
||||
return this.window_to_world(sx, sy)
|
||||
},
|
||||
|
||||
screen_to_world: function(sx, sy) {
|
||||
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
|
||||
var screen_rect = this.screen_rect()
|
||||
var ws = backend.get_window_size()
|
||||
var pixel_x = sx * ws.width
|
||||
var pixel_y = sy * ws.height
|
||||
var rel_x = (pixel_x - screen_rect.x) / screen_rect.width
|
||||
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
|
||||
var ax = this.anchor.x
|
||||
var ay = this.anchor.y
|
||||
var world_x = this.pos.x + (rel_x - ax) * this.width
|
||||
var world_y = this.pos.y + (rel_y - ay) * this.height
|
||||
return {x: world_x, y: world_y}
|
||||
},
|
||||
}
|
||||
|
||||
cam.make = function()
|
||||
{
|
||||
var c = Object.create(basecam)
|
||||
c.transform = new transform
|
||||
c.transform.unit()
|
||||
c.size = [640,360]
|
||||
c.mode = 'keep'
|
||||
c.viewport = {x:0,y:0,width:1,height:1}
|
||||
c.fov = 45
|
||||
c.type = 'ortho'
|
||||
c.ortho = true
|
||||
c.aspect = 16/9
|
||||
return c
|
||||
cam.make = function(config) {
|
||||
return meme(basecam, config || {})
|
||||
}
|
||||
|
||||
return cam
|
||||
|
||||
51
cbuf.h
Normal file
51
cbuf.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef CIRCBUF_H
|
||||
#define CIRCBUF_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static inline unsigned int powof2(unsigned int x)
|
||||
{
|
||||
x = x-1;
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
return x+1;
|
||||
}
|
||||
|
||||
struct rheader
|
||||
{
|
||||
unsigned int read;
|
||||
unsigned int write;
|
||||
int len;
|
||||
};
|
||||
|
||||
#define ringheader(r) ((struct rheader *)r-1)
|
||||
|
||||
static inline void *ringmake(void *ring, size_t elemsize, unsigned int n)
|
||||
{
|
||||
n = powof2(n);
|
||||
if (ring) {
|
||||
struct rheader *h = ringheader(ring);
|
||||
if (n <= h->len) return h+1;
|
||||
h = realloc(h, elemsize*n+sizeof(struct rheader));
|
||||
return h+1;
|
||||
}
|
||||
|
||||
struct rheader *h = malloc(elemsize*n+sizeof(struct rheader));
|
||||
h->len = n; h->read = 0; h->write = 0;
|
||||
return h+1;
|
||||
}
|
||||
|
||||
#define ringnew(r,n) (r = ringmake(r, sizeof(*r),n))
|
||||
#define ringfree(r) ((r) ? free(ringheader(r)) : 0)
|
||||
#define ringmask(r,v) (v & (ringheader(r)->len-1))
|
||||
#define ringpush(r,v) (r[ringmask(r,ringheader(r)->write++)] = v)
|
||||
#define ringshift(r) (r[ringmask(r,ringheader(r)->read++)])
|
||||
#define ringsize(r) ((r) ? ringheader(r)->write - ringheader(r)->read : 0)
|
||||
#define ringfull(r) ((r) ? ringsize(r) == ringheader(r)->len : 0)
|
||||
#define ringempty(r) ((r) ? ringheader(r)->read == ringheader(r)->write : 0)
|
||||
|
||||
#endif
|
||||
12
cell.toml
Normal file
12
cell.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[compilation]
|
||||
LDFLAGS = "-lSDL3 -lstdc++ -lm -lc++"
|
||||
CFLAGS = "-Isrc/imgui"
|
||||
|
||||
[dependencies]
|
||||
rtree = "gitea.pockle.world/john/cell-rtree"
|
||||
dmon = "gitea.pockle.world/john/cell-watch"
|
||||
libsamplerate = "gitea.pockle.world/john/cell-libsamplerate"
|
||||
sdl3 = "gitea.pockle.world/john/cell-sdl3"
|
||||
image = "gitea.pockle.world/john/cell-image"
|
||||
soundwave = "gitea.pockle.world/john/soundwave"
|
||||
|
||||
784
clay.cm
784
clay.cm
@@ -1,410 +1,440 @@
|
||||
// Layout code
|
||||
// Contain is for how it will treat its children. If they should be laid out as a row, or column, or in a flex style, etc.
|
||||
// clay.cm - UI layout engine emitting flat drawables with annotated tree
|
||||
//
|
||||
// - Uses meme/merge for config chains
|
||||
// - Emits flat list of drawables for film2d
|
||||
// - Supports scissor clipping
|
||||
// - Returns annotated tree root with .drawables for clay_input compatibility
|
||||
|
||||
var layout = use('layout')
|
||||
var geometry = use('geometry')
|
||||
var draw = use('prosperon/draw2d')
|
||||
var graphics = use('graphics')
|
||||
var util = use('util')
|
||||
var input = use('input')
|
||||
var prosperon = use('prosperon')
|
||||
|
||||
var CHILDREN = Symbol('children')
|
||||
var PARENT = Symbol('parent')
|
||||
var clay = {}
|
||||
|
||||
function normalizeSpacing(spacing) {
|
||||
if (typeof spacing == 'number') {
|
||||
return {l: spacing, r: spacing, t: spacing, b: spacing}
|
||||
} else if (Array.isArray(spacing)) {
|
||||
if (spacing.length == 2) {
|
||||
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
|
||||
} else if (spacing.length == 4) {
|
||||
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
|
||||
}
|
||||
} else if (typeof spacing == 'object') {
|
||||
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
|
||||
} else {
|
||||
return {l:0, r:0, t:0, b:0}
|
||||
}
|
||||
}
|
||||
// Unique key objects for tree traversal (used by clay_input)
|
||||
var CHILDREN = {}
|
||||
var PARENT = {}
|
||||
clay.CHILDREN = CHILDREN
|
||||
clay.PARENT = PARENT
|
||||
|
||||
var lay_ctx = layout.make_context();
|
||||
// Layout context
|
||||
var lay_ctx = layout.make_context()
|
||||
|
||||
var clay_base = {
|
||||
// Base configuration for UI elements
|
||||
var base_config = {
|
||||
font: null,
|
||||
background_image: null,
|
||||
slice: 0,
|
||||
font: 'smalle.16',
|
||||
font_size: null,
|
||||
color: {r:1,g:1,b:1,a:1},
|
||||
spacing:0,
|
||||
padding:0,
|
||||
margin:0,
|
||||
offset:{x:0, y:0},
|
||||
size:null,
|
||||
font_path: 'fonts/dos', // Default font
|
||||
font_size: 16,
|
||||
color: {r:1, g:1, b:1, a:1},
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
offset: {x:0, y:0},
|
||||
size: null,
|
||||
background_color: null,
|
||||
clipped: false,
|
||||
text_break: 'word',
|
||||
text_align: 'left',
|
||||
max_size: null, // {width: null, height: null}
|
||||
};
|
||||
|
||||
var root_item;
|
||||
var root_config;
|
||||
var tree_root;
|
||||
var clay = {}
|
||||
clay.CHILDREN = CHILDREN
|
||||
clay.PARENT = PARENT
|
||||
|
||||
var focused_textbox = null
|
||||
|
||||
clay.behave = layout.behave;
|
||||
clay.contain = layout.contain;
|
||||
|
||||
clay.draw = function draw(fn, size = [prosperon.camera.width, prosperon.camera.height])
|
||||
{
|
||||
lay_ctx.reset();
|
||||
var root = lay_ctx.item();
|
||||
// Accept both array and object formats
|
||||
if (Array.isArray(size)) {
|
||||
size = {width: size[0], height: size[1]};
|
||||
}
|
||||
lay_ctx.set_size(root,size);
|
||||
lay_ctx.set_contain(root,layout.contain.row);
|
||||
root_item = root;
|
||||
root_config = Object.assign({}, clay_base);
|
||||
tree_root = {
|
||||
id: root,
|
||||
config: root_config,
|
||||
};
|
||||
tree_root[CHILDREN] = [];
|
||||
tree_root[PARENT] = null;
|
||||
fn()
|
||||
lay_ctx.run();
|
||||
|
||||
// Adjust bounding boxes for padding - traverse tree instead of array
|
||||
function adjust_bounding_boxes(node) {
|
||||
node.content = lay_ctx.get_rect(node.id);
|
||||
node.boundingbox = Object.assign({}, node.content);
|
||||
|
||||
var padding = normalizeSpacing(node.config.padding || 0);
|
||||
|
||||
node.boundingbox.x -= padding.l;
|
||||
node.boundingbox.y -= padding.t;
|
||||
node.boundingbox.width += padding.l + padding.r;
|
||||
node.boundingbox.height += padding.t + padding.b;
|
||||
|
||||
node.marginbox = Object.assign({}, node.content);
|
||||
var margin = normalizeSpacing(node.config.margin || 0);
|
||||
node.marginbox.x -= margin.l;
|
||||
node.marginbox.y -= margin.t;
|
||||
node.marginbox.width += margin.l+margin.r;
|
||||
node.marginbox.height += margin.t+margin.b;
|
||||
|
||||
// Apply max_size clamping post-layout
|
||||
if (node.config.max_size) {
|
||||
if (node.config.max_size.width != null) {
|
||||
// Clamp the layout rect size
|
||||
var rect = lay_ctx.get_rect(node.id);
|
||||
rect.width = Math.min(rect.width, node.config.max_size.width);
|
||||
// Also clamp bounding box
|
||||
node.content.width = Math.min(node.content.width, node.config.max_size.width);
|
||||
node.boundingbox.width = Math.min(node.boundingbox.width, node.config.max_size.width + padding.l + padding.r);
|
||||
node.marginbox.width = Math.min(node.marginbox.width, node.config.max_size.width + padding.l + padding.r + margin.l + margin.r);
|
||||
}
|
||||
if (node.config.max_size.height != null) {
|
||||
// Clamp the layout rect size
|
||||
var rect = lay_ctx.get_rect(node.id);
|
||||
rect.height = Math.min(rect.height, node.config.max_size.height);
|
||||
// Also clamp bounding box
|
||||
node.content.height = Math.min(node.content.height, node.config.max_size.height);
|
||||
node.boundingbox.height = Math.min(node.boundingbox.height, node.config.max_size.height + padding.t + padding.b);
|
||||
node.marginbox.height = Math.min(node.marginbox.height, node.config.max_size.height + padding.t + padding.b + margin.t + margin.b);
|
||||
}
|
||||
}
|
||||
|
||||
node.content.y *= -1;
|
||||
node.content.y += size.height;
|
||||
node.boundingbox.y *= -1;
|
||||
node.boundingbox.y += size.height;
|
||||
node.content.anchor_y = 1;
|
||||
node.boundingbox.anchor_y = 1;
|
||||
|
||||
// Recursively adjust children
|
||||
if (node[CHILDREN]) {
|
||||
for (var child of node[CHILDREN]) {
|
||||
adjust_bounding_boxes(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adjust_bounding_boxes(tree_root);
|
||||
|
||||
return tree_root;
|
||||
max_size: null,
|
||||
contain: 0,
|
||||
behave: 0
|
||||
}
|
||||
|
||||
function create_view_fn(base_config)
|
||||
{
|
||||
var base = Object.assign(Object.create(clay_base), base_config);
|
||||
return function view(config = {}, fn) {
|
||||
config.__proto__ = base;
|
||||
var item = add_item(config);
|
||||
function normalize_color(c, fallback) {
|
||||
var fb = fallback || {r:1, g:1, b:1, a:1}
|
||||
if (!c) return {r:fb.r, g:fb.g, b:fb.b, a:fb.a}
|
||||
return {
|
||||
r: c.r != null ? c.r : fb.r,
|
||||
g: c.g != null ? c.g : fb.g,
|
||||
b: c.b != null ? c.b : fb.b,
|
||||
a: c.a != null ? c.a : fb.a
|
||||
}
|
||||
}
|
||||
|
||||
var prev_item = root_item;
|
||||
var prev_config = root_config;
|
||||
var prev_tree_root = tree_root;
|
||||
root_item = item;
|
||||
root_config = config;
|
||||
root_config._childIndex = 0; // Initialize child index
|
||||
// Find the tree node for this item and set it as current tree_root
|
||||
if (prev_tree_root[CHILDREN] && prev_tree_root[CHILDREN].length > 0) {
|
||||
tree_root = prev_tree_root[CHILDREN][prev_tree_root[CHILDREN].length - 1];
|
||||
function normalize_spacing(s) {
|
||||
if (is_number(s)) return {l:s, r:s, t:s, b:s}
|
||||
if (is_array(s)) {
|
||||
if (length(s) == 2) return {l:s[0], r:s[0], t:s[1], b:s[1]}
|
||||
if (length(s) == 4) return {l:s[0], r:s[1], t:s[2], b:s[3]}
|
||||
}
|
||||
if (is_object(s)) return {l:s.l||0, r:s.r||0, t:s.t||0, b:s.b||0}
|
||||
return {l:0, r:0, t:0, b:0}
|
||||
}
|
||||
|
||||
// Tree building state
|
||||
var root_item = null
|
||||
var tree_root = null
|
||||
var config_stack = []
|
||||
|
||||
// Rewriting state management for cleaner recursion
|
||||
var tree_stack = []
|
||||
var _next_id = 0
|
||||
|
||||
function annotate_tree(node, root_height, parent_node) {
|
||||
var rect = lay_ctx.get_rect(node.id)
|
||||
node.boundingbox = {
|
||||
x: rect.x,
|
||||
y: root_height - (rect.y + rect.height),
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
node[CHILDREN] = node.children
|
||||
node[PARENT] = parent_node
|
||||
arrfor(node.children, function(child) {
|
||||
annotate_tree(child, root_height, node)
|
||||
})
|
||||
}
|
||||
|
||||
clay.layout = function(fn, size) {
|
||||
var sz = is_array(size) ? {width: size[0], height: size[1]} : size
|
||||
lay_ctx.reset()
|
||||
_next_id = 0
|
||||
var root_id = lay_ctx.item()
|
||||
|
||||
lay_ctx.set_size(root_id, sz)
|
||||
lay_ctx.set_contain(root_id, layout.contain.row)
|
||||
|
||||
var root_node = {
|
||||
id: root_id,
|
||||
config: meme(base_config, {size: sz}),
|
||||
children: []
|
||||
}
|
||||
|
||||
tree_stack = [root_node]
|
||||
|
||||
fn() // User builds tree
|
||||
|
||||
lay_ctx.run()
|
||||
|
||||
// Annotate tree for clay_input (boundingbox, CHILDREN, PARENT)
|
||||
annotate_tree(root_node, sz.height, null)
|
||||
|
||||
// Build flat drawable list and attach to tree root
|
||||
root_node.drawables = build_drawables(root_node, sz.height)
|
||||
return root_node
|
||||
}
|
||||
|
||||
function build_drawables(node, root_height, parent, parent_scissor) {
|
||||
var p_abs_x = (parent && parent.x) || 0
|
||||
var p_abs_y = (parent && parent.y) || 0
|
||||
var p_layer = (parent && parent.layer) || 0
|
||||
|
||||
var rect = lay_ctx.get_rect(node.id)
|
||||
|
||||
// Calculate absolute world Y for this node (bottom-up layout to top-down render)
|
||||
var abs_y = root_height - (rect.y + rect.height)
|
||||
var abs_x = rect.x
|
||||
|
||||
// IMPORTANT: The offset in config is applied VISUALLY.
|
||||
var vis_x = abs_x + node.config.offset.x
|
||||
var vis_y = abs_y + node.config.offset.y
|
||||
|
||||
var drawables = []
|
||||
|
||||
// Scissor
|
||||
var current_scissor = parent_scissor
|
||||
var sx = vis_x
|
||||
var sy = vis_y
|
||||
var sw = rect.width
|
||||
var sh = rect.height
|
||||
var clip_right = null
|
||||
var clip_bottom = null
|
||||
if (node.config.clipped) {
|
||||
// Intersect with parent
|
||||
if (parent_scissor) {
|
||||
sx = max(sx, parent_scissor.x)
|
||||
sy = max(sy, parent_scissor.y)
|
||||
clip_right = min(vis_x + sw, parent_scissor.x + parent_scissor.width)
|
||||
clip_bottom = min(vis_y + sh, parent_scissor.y + parent_scissor.height)
|
||||
sw = max(0, clip_right - sx)
|
||||
sh = max(0, clip_bottom - sy)
|
||||
}
|
||||
|
||||
current_scissor = {x: sx, y: sy, width: sw, height: sh}
|
||||
}
|
||||
|
||||
// Background
|
||||
if (node.config.background_image) {
|
||||
_next_id = _next_id + 1
|
||||
if (node.config.slice) {
|
||||
drawables[] = {
|
||||
_id: _next_id,
|
||||
type: 'sprite',
|
||||
image: node.config.background_image,
|
||||
pos: {x: vis_x, y: vis_y},
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
slice: node.config.slice,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: p_layer - 0.1,
|
||||
scissor: current_scissor
|
||||
}
|
||||
} else {
|
||||
// If no children yet, this shouldn't happen, but handle gracefully
|
||||
tree_root = prev_tree_root;
|
||||
drawables[] = {
|
||||
_id: _next_id,
|
||||
type: 'sprite',
|
||||
image: node.config.background_image,
|
||||
pos: {x: vis_x, y: vis_y},
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: p_layer - 0.1,
|
||||
scissor: current_scissor
|
||||
}
|
||||
}
|
||||
fn?.();
|
||||
root_item = prev_item;
|
||||
root_config = prev_config;
|
||||
tree_root = prev_tree_root;
|
||||
} else if (node.config.background_color) {
|
||||
_next_id = _next_id + 1
|
||||
drawables[] = {
|
||||
_id: _next_id,
|
||||
type: 'rect',
|
||||
pos: {x: vis_x, y: vis_y},
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color,
|
||||
layer: p_layer - 0.1,
|
||||
scissor: current_scissor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clay.vstack = create_view_fn({
|
||||
contain: layout.contain.column | layout.contain.start,
|
||||
});
|
||||
|
||||
clay.hstack = create_view_fn({
|
||||
contain: layout.contain.row | layout.contain.start,
|
||||
});
|
||||
|
||||
clay.spacer = create_view_fn({
|
||||
behave: layout.behave.hfill | layout.behave.vfill
|
||||
});
|
||||
|
||||
clay.frame = create_view_fn({});
|
||||
|
||||
function image_size(img)
|
||||
{
|
||||
return [img.width * (img.rect?.width || 1), img.height * (img.rect?.height || 1)];
|
||||
}
|
||||
|
||||
function add_item(config)
|
||||
{
|
||||
// Normalize the child's margin
|
||||
var margin = normalizeSpacing(config.margin || 0);
|
||||
var padding = normalizeSpacing(config.padding || 0);
|
||||
var childGap = root_config.child_gap || 0;
|
||||
|
||||
// Adjust for child_gap
|
||||
root_config._childIndex ??= 0
|
||||
if (root_config._childIndex > 0) {
|
||||
var parentContain = root_config.contain || 0;
|
||||
var directionMask = layout.contain.row | layout.contain.column;
|
||||
var direction = parentContain & directionMask;
|
||||
var isVStack = direction == layout.contain.column;
|
||||
var isHStack = direction == layout.contain.row;
|
||||
|
||||
if (isVStack) {
|
||||
margin.t += childGap;
|
||||
} else if (isHStack) {
|
||||
margin.l += childGap;
|
||||
// Content (Image/Text)
|
||||
if (node.config.image) {
|
||||
_next_id = _next_id + 1
|
||||
drawables[] = {
|
||||
_id: _next_id,
|
||||
type: 'sprite',
|
||||
image: node.config.image,
|
||||
pos: {x: vis_x, y: vis_y},
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.color,
|
||||
layer: p_layer,
|
||||
scissor: current_scissor
|
||||
}
|
||||
}
|
||||
|
||||
var use_config = Object.create(config);
|
||||
|
||||
use_config.margin = {
|
||||
t: margin.t+padding.t,
|
||||
b: margin.b+padding.b,
|
||||
r:margin.r+padding.r,
|
||||
l:margin.l+padding.l
|
||||
};
|
||||
|
||||
var item = lay_ctx.item();
|
||||
lay_ctx.set_margins(item, use_config.margin);
|
||||
use_config.size ??= {width:0, height:0}
|
||||
// Convert array to object if needed
|
||||
if (Array.isArray(use_config.size)) {
|
||||
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
|
||||
if (node.config.text) {
|
||||
_next_id = _next_id + 1
|
||||
drawables[] = {
|
||||
_id: _next_id,
|
||||
type: 'text',
|
||||
text: node.config.text,
|
||||
font: node.config.font_path,
|
||||
size: node.config.font_size,
|
||||
color: node.config.color,
|
||||
pos: {x: vis_x, y: vis_y + rect.height},
|
||||
anchor_y: 1.0,
|
||||
layer: p_layer,
|
||||
scissor: current_scissor
|
||||
}
|
||||
}
|
||||
// Apply max_size constraint - only clamp computed size, don't set explicit size pre-layout
|
||||
if (use_config.max_size) {
|
||||
// max_size should not force explicit sizing pre-layout - let children compute natural size,
|
||||
// then clamp the container after layout. For now, just ensure we don't set size to max_size.
|
||||
|
||||
// Children
|
||||
arrfor(node.children, function(child) {
|
||||
drawables = array(drawables, build_drawables(child, root_height, {x: vis_x, y: vis_y, layer: p_layer + 0.01}, current_scissor))
|
||||
})
|
||||
|
||||
return drawables
|
||||
}
|
||||
|
||||
|
||||
// --- Item Creation Helpers ---
|
||||
|
||||
function process_configs(configs) {
|
||||
var cfg = meme(base_config, configs)
|
||||
|
||||
// Parse shorthand font string (e.g. 'blackcastle.64') into font_path and font_size
|
||||
var font_parts = null
|
||||
var parsed_size = null
|
||||
if (cfg.font && is_text(cfg.font)) {
|
||||
font_parts = array(cfg.font, '.')
|
||||
if (length(font_parts) >= 2) {
|
||||
parsed_size = number(font_parts[length(font_parts) - 1])
|
||||
if (parsed_size) {
|
||||
cfg.font_size = parsed_size
|
||||
cfg.font_path = font_parts[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
lay_ctx.set_size(item,use_config.size);
|
||||
lay_ctx.set_contain(item,use_config.contain);
|
||||
lay_ctx.set_behave(item,use_config.behave);
|
||||
var tree_node = {
|
||||
|
||||
cfg.color = normalize_color(cfg.color, base_config.color)
|
||||
if (cfg.background_color) cfg.background_color = normalize_color(cfg.background_color, {r:1,g:1,b:1,a:1})
|
||||
|
||||
if (!cfg.offset) cfg.offset = {x:0, y:0}
|
||||
else cfg.offset = {x: cfg.offset.x || 0, y: cfg.offset.y || 0}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
function push_node(configs, contain_mode) {
|
||||
var config = process_configs(configs)
|
||||
if (contain_mode != null) config.contain = contain_mode
|
||||
|
||||
var item = lay_ctx.item()
|
||||
|
||||
// Apply layout props
|
||||
lay_ctx.set_margins(item, normalize_spacing(config.margin))
|
||||
lay_ctx.set_contain(item, config.contain)
|
||||
lay_ctx.set_behave(item, config.behave)
|
||||
|
||||
var s = config.size
|
||||
if (s) {
|
||||
if (is_array(s)) s = {width: s[0], height: s[1]}
|
||||
lay_ctx.set_size(item, s)
|
||||
}
|
||||
|
||||
var node = {
|
||||
id: item,
|
||||
config: use_config,
|
||||
};
|
||||
tree_node[CHILDREN] = [];
|
||||
tree_node[PARENT] = tree_root;
|
||||
tree_root[CHILDREN].push(tree_node);
|
||||
lay_ctx.insert(root_item,item);
|
||||
|
||||
// Increment the parent's child index
|
||||
root_config._childIndex++;
|
||||
return item;
|
||||
}
|
||||
|
||||
function rectify_configs(config_array)
|
||||
{
|
||||
if (config_array.length == 0)
|
||||
config_array = [{}];
|
||||
|
||||
for (var i = config_array.length-1; i > 0; i--)
|
||||
config_array[i].__proto__ = config_array[i-1];
|
||||
|
||||
config_array[0].__proto__ = clay_base;
|
||||
var cleanobj = Object.create(config_array[config_array.length-1]);
|
||||
|
||||
return cleanobj;
|
||||
}
|
||||
|
||||
clay.image = function image(path, ...configs)
|
||||
{
|
||||
var config = rectify_configs(configs);
|
||||
var image = graphics.texture(path);
|
||||
config.image = path; // Store the path string, not the texture object
|
||||
config.size ??= {width: image.width, height: image.height};
|
||||
add_item(config);
|
||||
}
|
||||
|
||||
clay.text = function text(str, ...configs)
|
||||
{
|
||||
var config = rectify_configs(configs);
|
||||
config.size ??= [0,0]
|
||||
config.font = graphics.get_font(config.font)
|
||||
config.text = str
|
||||
var tsize = config.font.text_size(str, 0, config.size[0], config.text_break, config.text_align);
|
||||
tsize.x = Math.ceil(tsize.x)
|
||||
tsize.y = Math.ceil(tsize.y)
|
||||
config.size = config.size.map((x,i) => Math.max(x, tsize[i]));
|
||||
add_item(config);
|
||||
}
|
||||
|
||||
/*
|
||||
For a given size,
|
||||
the layout engine should "see" size + margin
|
||||
but its interior content should "see" size - padding
|
||||
hence, the layout box should be size-padding, with margin of margin+padding
|
||||
*/
|
||||
|
||||
var button_base = Object.assign(Object.create(clay_base), {
|
||||
padding:0,
|
||||
hovered:{
|
||||
}
|
||||
});
|
||||
clay.button = function button(str, action, config = {})
|
||||
{
|
||||
config.__proto__ = button_base;
|
||||
config.font = graphics.get_font(config.font)
|
||||
config.size = config.font.text_size(str, 0, 0, config.text_break, config.text_align)
|
||||
add_item(config);
|
||||
config.text = str;
|
||||
config.action = action;
|
||||
}
|
||||
|
||||
var point = use('point')
|
||||
|
||||
clay.draw_commands = function draw_commands(tree_root, pos = {x:0,y:0})
|
||||
{
|
||||
function draw_node(node) {
|
||||
var config = node.config
|
||||
var boundingbox = geometry.rect_move(node.boundingbox,point.add(pos,config.offset))
|
||||
var content = geometry.rect_move(node.content,point.add(pos, config.offset))
|
||||
|
||||
if (config.background_image)
|
||||
if (config.slice)
|
||||
draw.slice9(config.background_image, boundingbox, config.slice, config.background_color)
|
||||
else
|
||||
draw.image(config.background_image, boundingbox, 0, config.color)
|
||||
else if (config.background_color)
|
||||
draw.rectangle(boundingbox, null, {color:config.background_color})
|
||||
|
||||
if (config.text) {
|
||||
var baseline_y = content.y + content.height - config.font.ascent
|
||||
draw.text(config.text, {x: content.x, y: baseline_y}, config.font, config.color, content.width)
|
||||
}
|
||||
|
||||
if (config.image)
|
||||
draw.image(config.image, content, 0, config.color)
|
||||
|
||||
if (config.clipped) {
|
||||
draw.scissor(content)
|
||||
}
|
||||
|
||||
// Recursively draw children
|
||||
if (node[CHILDREN]) {
|
||||
for (var child of node[CHILDREN]) {
|
||||
draw_node(child);
|
||||
}
|
||||
}
|
||||
if (config.clipped)
|
||||
draw.scissor(null)
|
||||
}
|
||||
|
||||
draw_node(tree_root);
|
||||
}
|
||||
|
||||
var dbg_colors = {};
|
||||
clay.debug_colors = dbg_colors;
|
||||
dbg_colors.content = [1,0,0,0.1];
|
||||
dbg_colors.boundingbox = [0,1,0,0,0.1];
|
||||
dbg_colors.margin = [0,0,1,0.1];
|
||||
clay.draw_debug = function draw_debug(tree_root, pos = {x:0, y:0})
|
||||
{
|
||||
function draw_debug_node(node) {
|
||||
var boundingbox = geometry.rect_move(node.boundingbox,pos);
|
||||
var content = geometry.rect_move(node.content,pos);
|
||||
draw.rectangle(content, null, {color:dbg_colors.content});
|
||||
draw.rectangle(boundingbox, null, {color:dbg_colors.boundingbox});
|
||||
// draw.rectangle(geometry.rect_move(node.marginbox,pos), dbg_colors.margin);
|
||||
|
||||
// Recursively draw debug for children
|
||||
if (node[CHILDREN]) {
|
||||
for (var child of node[CHILDREN]) {
|
||||
draw_debug_node(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_debug_node(tree_root);
|
||||
}
|
||||
|
||||
clay.print_tree = function print_tree(tree_root, indent = 0) {
|
||||
var indent_str = ' '.repeat(indent)
|
||||
var node_type = 'unknown'
|
||||
|
||||
if (tree_root.config.text) {
|
||||
node_type = 'text'
|
||||
} else if (tree_root.config.image) {
|
||||
node_type = 'image'
|
||||
} else if (tree_root.config.contain) {
|
||||
if (tree_root.config.contain & layout.contain.column) {
|
||||
node_type = 'vstack'
|
||||
} else if (tree_root.config.contain & layout.contain.row) {
|
||||
node_type = 'hstack'
|
||||
} else {
|
||||
node_type = 'container'
|
||||
}
|
||||
} else {
|
||||
node_type = 'node'
|
||||
config: config,
|
||||
children: []
|
||||
}
|
||||
|
||||
log.console(`${indent_str}${node_type} (id: ${tree_root.id})`)
|
||||
// Add to parent
|
||||
var parent = tree_stack[length(tree_stack)-1]
|
||||
parent.children[] = node
|
||||
lay_ctx.insert(parent.id, item)
|
||||
|
||||
if (tree_root[CHILDREN] && tree_root[CHILDREN].length > 0) {
|
||||
log.console(`${indent_str} children: ${tree_root[CHILDREN].length}`)
|
||||
for (var child of tree_root[CHILDREN]) {
|
||||
print_tree(child, indent + 1)
|
||||
}
|
||||
} else {
|
||||
log.console(`${indent_str} (no children)`)
|
||||
}
|
||||
tree_stack[] = node
|
||||
return node
|
||||
}
|
||||
|
||||
function pop_node() {
|
||||
tree_stack[]
|
||||
}
|
||||
|
||||
// Generic container
|
||||
clay.container = function(configs, fn) {
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
push_node(_configs, null)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Stacks
|
||||
clay.vstack = function(configs, fn) {
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.column
|
||||
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.hstack = function(configs, fn) {
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.row
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.zstack = function(configs, fn) {
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.layout
|
||||
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Leaf nodes
|
||||
clay.image = function(path, configs, extra) {
|
||||
var img = graphics.texture(path)
|
||||
var c = [{image: path}]
|
||||
var _configs = configs ? (is_array(configs) ? configs : [configs]) : []
|
||||
if (extra) _configs = array(_configs, is_array(extra) ? extra : [extra])
|
||||
var final_config = process_configs(_configs)
|
||||
if (!final_config.size && !final_config.behave)
|
||||
c[] = {size: {width: img.width, height: img.height}}
|
||||
|
||||
push_node(array(c, _configs), null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.text = function(str, configs, extra) {
|
||||
var c = [{text: str}]
|
||||
var _configs = configs ? (is_array(configs) ? configs : [configs]) : []
|
||||
if (extra) _configs = array(_configs, is_array(extra) ? extra : [extra])
|
||||
var final_config = process_configs(_configs)
|
||||
if (!final_config.size && !final_config.behave) {
|
||||
c[] = {size: {width: 100, height: 20}}
|
||||
}
|
||||
|
||||
push_node(array(c, _configs), null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.rectangle = function(configs) {
|
||||
var _configs = configs ? (is_array(configs) ? configs : [configs]) : [{}]
|
||||
push_node(_configs, null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.button = function(str, action, configs) {
|
||||
var btn_config = [{
|
||||
padding: 10,
|
||||
background_color: {r:0.3, g:0.3, b:0.4, a:1}
|
||||
}]
|
||||
|
||||
var _configs = is_array(configs) ? configs : [configs]
|
||||
var merged = process_configs(_configs)
|
||||
|
||||
clay.zstack(array(btn_config, _configs), function() {
|
||||
clay.text(str, {color: {r:1,g:1,b:1,a:1}, font_path: merged.font_path})
|
||||
})
|
||||
}
|
||||
|
||||
// Spacer — fills available space
|
||||
clay.spacer = function(config) {
|
||||
var cfg = config || {}
|
||||
if (!cfg.behave) cfg.behave = layout.behave.hfill | layout.behave.vfill
|
||||
push_node([cfg], null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Convenience draw wrapper — auto-detects call patterns:
|
||||
// clay.draw(fn) — default 640x360
|
||||
// clay.draw(fn, size_obj) — fn first, size object second
|
||||
// clay.draw(size_array, fn) — size array first, fn second
|
||||
clay.draw = function(arg1, arg2) {
|
||||
var fn = null
|
||||
var size = {width: 640, height: 360}
|
||||
if (is_function(arg1)) {
|
||||
fn = arg1
|
||||
if (arg2) {
|
||||
if (is_array(arg2)) size = {width: arg2[0], height: arg2[1]}
|
||||
else if (is_object(arg2)) size = arg2
|
||||
}
|
||||
} else if (is_array(arg1)) {
|
||||
size = {width: arg1[0], height: arg1[1]}
|
||||
fn = arg2
|
||||
}
|
||||
return clay.layout(fn, size)
|
||||
}
|
||||
|
||||
// Offset all drawables in an array by a position
|
||||
clay.offset_drawables = function(drawables, offset) {
|
||||
var result = []
|
||||
var i = 0
|
||||
var d = null
|
||||
for (i = 0; i < length(drawables); i = i + 1) {
|
||||
d = meme(drawables[i])
|
||||
d.pos = {x: d.pos.x + offset.x, y: d.pos.y + offset.y}
|
||||
result[] = d
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Constants
|
||||
clay.behave = layout.behave
|
||||
clay.contain = layout.contain
|
||||
|
||||
return clay
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Separates input concerns from layout/rendering
|
||||
|
||||
var geometry = use('geometry')
|
||||
var point = use('point')
|
||||
var clay = use('prosperon/clay')
|
||||
var clay = use('clay')
|
||||
|
||||
var clay_input = {}
|
||||
|
||||
@@ -35,13 +34,18 @@ function find_path(node, path, pos) {
|
||||
if (!pointer_enabled(node)) return null
|
||||
if (!rect_contains(node, pos)) return null
|
||||
|
||||
var next_path = path.concat(node)
|
||||
var next_path = array(path)
|
||||
next_path[] = node
|
||||
var i = 0
|
||||
var child = null
|
||||
var child_path = null
|
||||
|
||||
if (node[clay.CHILDREN] && !should_skip_children(node)) {
|
||||
// Children drawn later should be tested first; reverse if your render order differs
|
||||
for (var i = node[clay.CHILDREN].length - 1; i >= 0; i--) {
|
||||
var child = node[clay.CHILDREN][i]
|
||||
var child_path = find_path(child, next_path, pos)
|
||||
i = length(node[clay.CHILDREN]) - 1
|
||||
for (; i >= 0; i--) {
|
||||
child = node[clay.CHILDREN][i]
|
||||
child_path = find_path(child, next_path, pos)
|
||||
if (child_path) return child_path
|
||||
}
|
||||
}
|
||||
@@ -52,7 +56,7 @@ function find_path(node, path, pos) {
|
||||
|
||||
clay_input.deepest = function deepest(tree_root, pos) {
|
||||
var path = find_path(tree_root, [], pos) || []
|
||||
var deepest = path.length ? path[path.length - 1] : null
|
||||
var deepest = length(path) ? path[length(path) - 1] : null
|
||||
return deepest
|
||||
}
|
||||
|
||||
@@ -66,7 +70,8 @@ clay_input.bubble = function bubble(deepest, prop) {
|
||||
return null
|
||||
}
|
||||
|
||||
clay_input.click = function click(tree_root, mousepos, button = 'left') {
|
||||
clay_input.click = function click(tree_root, mousepos, button) {
|
||||
var _button = button || 'left'
|
||||
var deepest = clay_input.deepest(tree_root, mousepos)
|
||||
var action_target = clay_input.bubble(deepest, 'action')
|
||||
if (action_target && action_target.config.action) action_target.config.action()
|
||||
@@ -75,9 +80,9 @@ clay_input.click = function click(tree_root, mousepos, button = 'left') {
|
||||
clay_input.get_actionable = function get_actionable(tree_root) {
|
||||
var actionable = []
|
||||
function walk(node) {
|
||||
if (node.config.action) actionable.push(node)
|
||||
if (node.config.action) actionable[] = node
|
||||
if (node[clay.CHILDREN])
|
||||
for (var child of node[clay.CHILDREN]) walk(child)
|
||||
arrfor(node[clay.CHILDREN], walk)
|
||||
}
|
||||
walk(tree_root)
|
||||
return actionable
|
||||
@@ -86,9 +91,9 @@ clay_input.get_actionable = function get_actionable(tree_root) {
|
||||
clay_input.filter = function filter(tree_root, predicate) {
|
||||
var results = []
|
||||
function rec(node) {
|
||||
if (predicate(node)) results.push(node)
|
||||
if (predicate(node)) results[] = node
|
||||
if (node[clay.CHILDREN])
|
||||
for (var child of node[clay.CHILDREN]) rec(child)
|
||||
arrfor(node[clay.CHILDREN], rec)
|
||||
}
|
||||
rec(tree_root)
|
||||
return results
|
||||
@@ -98,10 +103,10 @@ clay_input.find_by_id = function find_by_id(tree_root, id) {
|
||||
function rec(node) {
|
||||
if (node.id == id) return node
|
||||
if (node[clay.CHILDREN])
|
||||
for (var child of node[clay.CHILDREN]) {
|
||||
arrfor(node[clay.CHILDREN], function(child) {
|
||||
var f = rec(child)
|
||||
if (f) return f
|
||||
}
|
||||
})
|
||||
return null
|
||||
}
|
||||
return rec(tree_root)
|
||||
|
||||
108
collision2d.cm
Normal file
108
collision2d.cm
Normal file
@@ -0,0 +1,108 @@
|
||||
var collision2d = {}
|
||||
|
||||
collision2d.body = function(config) {
|
||||
var c = config || {}
|
||||
return {
|
||||
type: c.type || 'aabb',
|
||||
width: c.width || 0,
|
||||
height: c.height || 0,
|
||||
radius: c.radius || 0,
|
||||
offset: c.offset || {x: 0, y: 0},
|
||||
layer: c.layer || 0,
|
||||
mask: c.mask || 0xFFFF
|
||||
}
|
||||
}
|
||||
|
||||
function body_center(body, pos) {
|
||||
return {
|
||||
x: pos.x + body.offset.x,
|
||||
y: pos.y + body.offset.y
|
||||
}
|
||||
}
|
||||
|
||||
function test_aabb_aabb(a, ap, b, bp) {
|
||||
var ac = body_center(a, ap)
|
||||
var bc = body_center(b, bp)
|
||||
var ahw = a.width * 0.5
|
||||
var ahh = a.height * 0.5
|
||||
var bhw = b.width * 0.5
|
||||
var bhh = b.height * 0.5
|
||||
return abs(ac.x - bc.x) < ahw + bhw && abs(ac.y - bc.y) < ahh + bhh
|
||||
}
|
||||
|
||||
function test_circle_circle(a, ap, b, bp) {
|
||||
var ac = body_center(a, ap)
|
||||
var bc = body_center(b, bp)
|
||||
var dx = ac.x - bc.x
|
||||
var dy = ac.y - bc.y
|
||||
var r = a.radius + b.radius
|
||||
return dx * dx + dy * dy < r * r
|
||||
}
|
||||
|
||||
function test_aabb_circle(aabb, ap, circ, cp) {
|
||||
var ac = body_center(aabb, ap)
|
||||
var cc = body_center(circ, cp)
|
||||
var hw = aabb.width * 0.5
|
||||
var hh = aabb.height * 0.5
|
||||
var cx = max(ac.x - hw, min(cc.x, ac.x + hw))
|
||||
var cy = max(ac.y - hh, min(cc.y, ac.y + hh))
|
||||
var dx = cc.x - cx
|
||||
var dy = cc.y - cy
|
||||
return dx * dx + dy * dy < circ.radius * circ.radius
|
||||
}
|
||||
|
||||
collision2d.test = function(a, a_pos, b, b_pos) {
|
||||
if (a.layer & b.mask == 0 || b.layer & a.mask == 0)
|
||||
return false
|
||||
|
||||
if (a.type == 'aabb' && b.type == 'aabb')
|
||||
return test_aabb_aabb(a, a_pos, b, b_pos)
|
||||
if (a.type == 'circle' && b.type == 'circle')
|
||||
return test_circle_circle(a, a_pos, b, b_pos)
|
||||
if (a.type == 'aabb' && b.type == 'circle')
|
||||
return test_aabb_circle(a, a_pos, b, b_pos)
|
||||
if (a.type == 'circle' && b.type == 'aabb')
|
||||
return test_aabb_circle(b, b_pos, a, a_pos)
|
||||
return false
|
||||
}
|
||||
|
||||
collision2d.overlap = function(body, pos, others) {
|
||||
var results = []
|
||||
var i = 0
|
||||
var other = null
|
||||
for (i = 0; i < length(others); i++) {
|
||||
other = others[i]
|
||||
if (collision2d.test(body, pos, other.body, other.pos))
|
||||
results[] = other
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
collision2d.overlap_point = function(point, bodies) {
|
||||
var results = []
|
||||
var i = 0
|
||||
var b = null
|
||||
var c = null
|
||||
var hw = 0
|
||||
var hh = 0
|
||||
var dx = 0
|
||||
var dy = 0
|
||||
for (i = 0; i < length(bodies); i++) {
|
||||
b = bodies[i]
|
||||
c = body_center(b.body, b.pos)
|
||||
if (b.body.type == 'aabb') {
|
||||
hw = b.body.width * 0.5
|
||||
hh = b.body.height * 0.5
|
||||
if (abs(point.x - c.x) < hw && abs(point.y - c.y) < hh)
|
||||
results[] = b
|
||||
} else if (b.body.type == 'circle') {
|
||||
dx = point.x - c.x
|
||||
dy = point.y - c.y
|
||||
if (dx * dx + dy * dy < b.body.radius * b.body.radius)
|
||||
results[] = b
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
return collision2d
|
||||
79
color.cm
79
color.cm
@@ -1,7 +1,7 @@
|
||||
function tohex(n) {
|
||||
var s = Math.floor(n).toString(16);
|
||||
if (s.length == 1) s = "0" + s;
|
||||
return s.toUpperCase();
|
||||
var s = text(floor(n), 16)
|
||||
if (length(s) == 1) s = "0" + s;
|
||||
return upper(s);
|
||||
};
|
||||
|
||||
var Color = {
|
||||
@@ -22,19 +22,15 @@ Color.editor = {};
|
||||
Color.editor.ur = Color.green;
|
||||
|
||||
Color.tohtml = function (v) {
|
||||
var html = v.map(function (n) {
|
||||
return tohex(n * 255);
|
||||
});
|
||||
return "#" + html.join("");
|
||||
var html = array(v, n => tohex(n * 255));
|
||||
return "#" + text(html);
|
||||
};
|
||||
|
||||
var esc = {};
|
||||
esc.reset = "\x1b[0";
|
||||
esc.color = function (v) {
|
||||
var c = v.map(function (n) {
|
||||
return Math.floor(n * 255);
|
||||
});
|
||||
var truecolor = "\x1b[38;2;" + c.join(";") + ";";
|
||||
var c = array(v, n => floor(n * 255));
|
||||
var truecolor = "\x1b[38;2;" + text(c, ";") + ";";
|
||||
return truecolor;
|
||||
};
|
||||
|
||||
@@ -92,50 +88,46 @@ Color.Editor = {
|
||||
/* Detects the format of all colors and munges them into a floating point format */
|
||||
Color.normalize = function (c) {
|
||||
var add_a = function (a) {
|
||||
var n = this.slice();
|
||||
var n = array(this);
|
||||
n[3] = a;
|
||||
return n;
|
||||
};
|
||||
|
||||
for (var p of Object.keys(c)) {
|
||||
if (typeof c[p] != "object") continue;
|
||||
if (!Array.isArray(c[p])) {
|
||||
arrfor(array(c), function(p) {
|
||||
if (!is_object(c[p])) return;
|
||||
if (!is_array(c[p])) {
|
||||
Color.normalize(c[p]);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add alpha channel if not present
|
||||
if (c[p].length == 3) {
|
||||
if (length(c[p]) == 3) {
|
||||
c[p][3] = 1;
|
||||
}
|
||||
|
||||
// Check if any values are > 1 (meaning they're in 0-255 format)
|
||||
var needs_conversion = false;
|
||||
for (var color of c[p]) {
|
||||
arrfor(c[p], function(color) {
|
||||
if (color > 1) {
|
||||
needs_conversion = true;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Convert from 0-255 to 0-1 if needed
|
||||
if (needs_conversion) {
|
||||
c[p] = c[p].map(function (x) {
|
||||
return x / 255;
|
||||
});
|
||||
c[p] = array(c[p], x => x / 255);
|
||||
}
|
||||
|
||||
c[p].alpha = add_a;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Color.normalize(Color);
|
||||
|
||||
var ColorMap = {};
|
||||
ColorMap.makemap = function (map) {
|
||||
var newmap = Object.create(ColorMap);
|
||||
Object.assign(newmap, map);
|
||||
return newmap;
|
||||
return meme(ColorMap, map)
|
||||
};
|
||||
ColorMap.Jet = ColorMap.makemap({
|
||||
0: [0, 0, 0.514],
|
||||
@@ -189,22 +181,31 @@ ColorMap.Viridis = ColorMap.makemap({
|
||||
|
||||
Color.normalize(ColorMap);
|
||||
|
||||
ColorMap.sample = function (t, map = this) {
|
||||
if (t < 0) return map[0];
|
||||
if (t > 1) return map[1];
|
||||
ColorMap.sample = function(t, map) {
|
||||
var local_map = map || this
|
||||
if (t < 0) return local_map[0]
|
||||
if (t > 1) return local_map[1]
|
||||
|
||||
var lastkey = 0;
|
||||
for (var key of Object.keys(map).sort()) {
|
||||
var keys = sorted(array(local_map))
|
||||
var lastkey = 0
|
||||
var i = 0
|
||||
var key = null
|
||||
var b = null
|
||||
var a = null
|
||||
var tt = 0
|
||||
|
||||
for (i = 0; i < length(keys); i++) {
|
||||
key = keys[i]
|
||||
if (t < key) {
|
||||
var b = map[key];
|
||||
var a = map[lastkey];
|
||||
var tt = (key - lastkey) * t;
|
||||
return a.lerp(b, tt);
|
||||
b = local_map[key]
|
||||
a = local_map[lastkey]
|
||||
tt = (t - lastkey) / (key - lastkey)
|
||||
return a.lerp(b, tt)
|
||||
}
|
||||
lastkey = key;
|
||||
lastkey = key
|
||||
}
|
||||
return map[1];
|
||||
};
|
||||
return local_map[1]
|
||||
}
|
||||
|
||||
ColorMap.doc = {
|
||||
sample: "Sample a given colormap at the given percentage (0 to 1).",
|
||||
|
||||
448
compositor.cm
Normal file
448
compositor.cm
Normal file
@@ -0,0 +1,448 @@
|
||||
var effects_mod = use('effects')
|
||||
var backend = use('sdl_gpu')
|
||||
var film2d = use('film2d')
|
||||
|
||||
var compositor = {}
|
||||
|
||||
// Compile compositor config into render plan
|
||||
compositor.compile = function(config) {
|
||||
var ctx = {
|
||||
passes: [],
|
||||
targets: {},
|
||||
counter: 0,
|
||||
screen_size: backend.get_window_size ? backend.get_window_size() : {width: 1280, height: 720},
|
||||
|
||||
alloc: function(w, h, hint) {
|
||||
var key = (hint || 't') + '_' + text(this.counter++)
|
||||
this.targets[key] = {width: w, height: h, key: key}
|
||||
return {type: 'target', key: key, width: w, height: h}
|
||||
}
|
||||
}
|
||||
|
||||
var group_effects = config.group_effects || {}
|
||||
|
||||
// Clear screen
|
||||
if (config.clear)
|
||||
ctx.passes[] = {type: 'clear', target: 'screen', color: config.clear}
|
||||
|
||||
// Process each plane (supports both 'planes' and legacy 'layers' key)
|
||||
var planes = config.planes || config.layers || []
|
||||
var i = 0
|
||||
var plane = null
|
||||
var type = null
|
||||
for (i = 0; i < length(planes); i++) {
|
||||
plane = planes[i]
|
||||
type = plane.type || 'film2d'
|
||||
if (type == 'imgui') {
|
||||
compile_imgui_layer(plane, ctx)
|
||||
} else {
|
||||
compile_plane(plane, ctx, group_effects)
|
||||
}
|
||||
}
|
||||
|
||||
return {passes: ctx.passes, targets: ctx.targets, screen_size: ctx.screen_size}
|
||||
}
|
||||
|
||||
function compile_imgui_layer(layer, ctx) {
|
||||
ctx.passes[] = {
|
||||
type: 'imgui',
|
||||
target: 'screen',
|
||||
draw: layer.draw
|
||||
}
|
||||
}
|
||||
|
||||
function compile_plane(plane_config, ctx, group_effects) {
|
||||
var plane_name = plane_config.plane || plane_config.name
|
||||
var res = plane_config.resolution || ctx.screen_size
|
||||
var camera = plane_config.camera
|
||||
var layer_sort = plane_config.layer_sort || {} // layer -> 'y' or 'explicit'
|
||||
|
||||
// Build set of groups used as masks (these should not be drawn directly)
|
||||
var mask_groups = {}
|
||||
arrfor(array(group_effects), gname => {
|
||||
var effects = group_effects[gname].effects || []
|
||||
var e = 0
|
||||
for (e = 0; e < length(effects); e++) {
|
||||
if (effects[e].type == 'mask' && effects[e].mask_group)
|
||||
mask_groups[effects[e].mask_group] = true
|
||||
}
|
||||
})
|
||||
|
||||
// Get all sprites in this plane
|
||||
var all_sprites = film2d.query({plane: plane_name})
|
||||
|
||||
// Add manual drawables
|
||||
var di = 0
|
||||
if (plane_config.drawables) {
|
||||
for (di = 0; di < length(plane_config.drawables); di++)
|
||||
all_sprites[] = plane_config.drawables[di]
|
||||
}
|
||||
|
||||
// Find which sprites belong to groups with effects
|
||||
var effect_groups = {} // group_name -> {sprites: [], effects: []}
|
||||
var base_sprites = []
|
||||
|
||||
var si = 0
|
||||
var s = null
|
||||
var sprite_groups = null
|
||||
var assigned = false
|
||||
var is_mask_only = false
|
||||
var g = 0
|
||||
var gname = null
|
||||
for (si = 0; si < length(all_sprites); si++) {
|
||||
s = all_sprites[si]
|
||||
sprite_groups = s.groups || []
|
||||
assigned = false
|
||||
is_mask_only = length(sprite_groups) > 0
|
||||
|
||||
// First pass: check if sprite has any non-mask group
|
||||
for (g = 0; g < length(sprite_groups); g++) {
|
||||
gname = sprite_groups[g]
|
||||
if (!mask_groups[gname]) {
|
||||
is_mask_only = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: assign to effect groups
|
||||
for (g = 0; g < length(sprite_groups); g++) {
|
||||
gname = sprite_groups[g]
|
||||
if (group_effects[gname]) {
|
||||
if (!effect_groups[gname])
|
||||
effect_groups[gname] = {sprites: [], effects: group_effects[gname].effects}
|
||||
effect_groups[gname].sprites[] = s
|
||||
assigned = true
|
||||
break // Only assign to first matching effect group
|
||||
}
|
||||
}
|
||||
|
||||
// Add to base sprites if not assigned to effect group and not mask-only
|
||||
if (!assigned && !is_mask_only) base_sprites[] = s
|
||||
}
|
||||
|
||||
// Allocate plane target
|
||||
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
|
||||
|
||||
// Always clear plane target to prevent stale data between frames
|
||||
ctx.passes[] = {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}}
|
||||
|
||||
// Render each effect group to temp target, apply effects, composite back
|
||||
arrfor(array(effect_groups), gname => {
|
||||
var eg = effect_groups[gname]
|
||||
if (length(eg.sprites) == 0) return
|
||||
|
||||
var group_target = ctx.alloc(res.width, res.height, gname + '_content')
|
||||
|
||||
// Render group content
|
||||
ctx.passes[] = {
|
||||
type: 'render',
|
||||
renderer: 'film2d',
|
||||
drawables: eg.sprites,
|
||||
camera: camera,
|
||||
target: group_target,
|
||||
target_size: res,
|
||||
layer_sort: layer_sort,
|
||||
clear: {r: 0, g: 0, b: 0, a: 0}
|
||||
}
|
||||
|
||||
// Apply effects
|
||||
var current = group_target
|
||||
var e = 0
|
||||
var effect = null
|
||||
for (e = 0; e < length(eg.effects); e++) {
|
||||
effect = eg.effects[e]
|
||||
current = apply_effect(ctx, effect, current, {size: res, camera: camera, hint: gname, current_plane: plane_name, group_effects: group_effects})
|
||||
}
|
||||
|
||||
// Composite result to plane
|
||||
ctx.passes[] = {
|
||||
type: 'composite',
|
||||
source: current,
|
||||
dest: plane_target,
|
||||
source_size: res,
|
||||
dest_size: res,
|
||||
blend: 'over'
|
||||
}
|
||||
})
|
||||
|
||||
// Render base sprites (no effects)
|
||||
if (length(base_sprites) > 0) {
|
||||
ctx.passes[] = {
|
||||
type: 'render',
|
||||
renderer: 'film2d',
|
||||
drawables: base_sprites,
|
||||
camera: camera,
|
||||
target: plane_target,
|
||||
target_size: res,
|
||||
layer_sort: layer_sort,
|
||||
clear: null // Don't clear, blend on top
|
||||
}
|
||||
}
|
||||
|
||||
// Composite plane to screen
|
||||
ctx.passes[] = {
|
||||
type: 'blit_to_screen',
|
||||
source: plane_target,
|
||||
source_size: res,
|
||||
dest_size: ctx.screen_size,
|
||||
presentation: plane_config.presentation || 'stretch'
|
||||
}
|
||||
}
|
||||
|
||||
function apply_effect(ctx, effect, input, params) {
|
||||
var size = params.size
|
||||
var camera = params.camera
|
||||
var hint = params.hint
|
||||
var current_plane = params.current_plane
|
||||
var group_effects = params.group_effects
|
||||
var output = ctx.alloc(size.width, size.height, hint + '_' + effect.type)
|
||||
|
||||
var bright = null
|
||||
var blur1 = null
|
||||
var blur2 = null
|
||||
var blur_passes = null
|
||||
var blur_in = null
|
||||
var p = 0
|
||||
var mask_group = null
|
||||
var mask_sprites = null
|
||||
var mask_target = null
|
||||
if (effect.type == 'bloom') {
|
||||
bright = ctx.alloc(size.width, size.height, hint + '_bright')
|
||||
blur1 = ctx.alloc(size.width, size.height, hint + '_blur1')
|
||||
blur2 = ctx.alloc(size.width, size.height, hint + '_blur2')
|
||||
|
||||
// Threshold
|
||||
ctx.passes[] = {
|
||||
type: 'shader_pass',
|
||||
shader: 'threshold',
|
||||
input: input,
|
||||
output: bright,
|
||||
uniforms: {threshold: effect.threshold || 0.8, intensity: effect.intensity || 1}
|
||||
}
|
||||
|
||||
// Blur passes
|
||||
blur_passes = effect.blur_passes != null ? effect.blur_passes : 2
|
||||
blur_in = bright
|
||||
for (p = 0; p < blur_passes; p++) {
|
||||
ctx.passes[] = {type: 'shader_pass', shader: 'blur', input: blur_in, output: blur1, uniforms: {direction: {x: 1, y: 0}, texel_size: {x: 1/size.width, y: 1/size.height}}}
|
||||
ctx.passes[] = {type: 'shader_pass', shader: 'blur', input: blur1, output: blur2, uniforms: {direction: {x: 0, y: 1}, texel_size: {x: 1/size.width, y: 1/size.height}}}
|
||||
blur_in = blur2
|
||||
}
|
||||
|
||||
// Composite bloom
|
||||
ctx.passes[] = {type: 'composite_textures', base: input, overlay: blur_in, output: output, mode: 'add'}
|
||||
|
||||
} else if (effect.type == 'mask') {
|
||||
mask_group = effect.mask_group
|
||||
// Query masks within the same plane to avoid cross-plane mask issues
|
||||
mask_sprites = film2d.query({group: mask_group, plane: current_plane})
|
||||
log.compositor("mask effect: group=" + mask_group + " plane=" + current_plane + " sprites=" + text(length(mask_sprites)))
|
||||
|
||||
if (length(mask_sprites) > 0) {
|
||||
mask_target = ctx.alloc(size.width, size.height, hint + '_mask')
|
||||
|
||||
// Render mask
|
||||
ctx.passes[] = {
|
||||
type: 'render',
|
||||
renderer: 'film2d',
|
||||
drawables: mask_sprites,
|
||||
camera: camera,
|
||||
target: mask_target,
|
||||
target_size: size,
|
||||
clear: {r: 0, g: 0, b: 0, a: 0}
|
||||
}
|
||||
|
||||
// Apply mask
|
||||
ctx.passes[] = {
|
||||
type: 'apply_mask',
|
||||
content: input,
|
||||
mask: mask_target,
|
||||
output: output,
|
||||
mode: effect.channel || 'alpha',
|
||||
invert: effect.invert || false
|
||||
}
|
||||
} else {
|
||||
// No mask sprites, pass through
|
||||
ctx.passes[] = {type: 'blit', source: input, dest: output}
|
||||
}
|
||||
} else {
|
||||
// Unknown effect, pass through
|
||||
ctx.passes[] = {type: 'blit', source: input, dest: output}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Execute compiled plan
|
||||
compositor.execute = function(plan) {
|
||||
var cache = {}
|
||||
|
||||
arrfor(array(plan.targets), key => {
|
||||
var spec = plan.targets[key]
|
||||
cache[key] = backend.get_or_create_target(spec.width, spec.height, key)
|
||||
})
|
||||
|
||||
function resolve(t) {
|
||||
if (t == 'screen') return 'screen'
|
||||
if (t && t.type == 'target') return cache[t.key]
|
||||
return t
|
||||
}
|
||||
|
||||
var commands = []
|
||||
|
||||
var i = 0
|
||||
var pass = null
|
||||
var target = null
|
||||
var result = null
|
||||
var c = 0
|
||||
var rect = null
|
||||
var src = null
|
||||
var dst = null
|
||||
for (i = 0; i < length(plan.passes); i++) {
|
||||
pass = plan.passes[i]
|
||||
|
||||
if (pass.type == 'clear') {
|
||||
target = resolve(pass.target)
|
||||
commands[] = {cmd: 'begin_render', target: target, clear: pass.color}
|
||||
commands[] = {cmd: 'end_render'}
|
||||
|
||||
} else if (pass.type == 'render') {
|
||||
result = film2d.render({
|
||||
drawables: pass.drawables,
|
||||
camera: pass.camera,
|
||||
target: resolve(pass.target),
|
||||
target_size: pass.target_size,
|
||||
layer_sort: pass.layer_sort || {},
|
||||
clear: pass.clear
|
||||
}, backend)
|
||||
for (c = 0; c < length(result.commands); c++)
|
||||
commands[] = result.commands[c]
|
||||
|
||||
} else if (pass.type == 'shader_pass') {
|
||||
commands[] = {
|
||||
cmd: 'shader_pass',
|
||||
shader: pass.shader,
|
||||
input: resolve(pass.input),
|
||||
output: resolve(pass.output),
|
||||
uniforms: pass.uniforms
|
||||
}
|
||||
|
||||
} else if (pass.type == 'composite_textures') {
|
||||
commands[] = {
|
||||
cmd: 'composite_textures',
|
||||
base: resolve(pass.base),
|
||||
overlay: resolve(pass.overlay),
|
||||
output: resolve(pass.output),
|
||||
mode: pass.mode
|
||||
}
|
||||
|
||||
} else if (pass.type == 'apply_mask') {
|
||||
commands[] = {
|
||||
cmd: 'apply_mask',
|
||||
content_texture: resolve(pass.content),
|
||||
mask_texture: resolve(pass.mask),
|
||||
output: resolve(pass.output),
|
||||
mode: pass.mode,
|
||||
invert: pass.invert
|
||||
}
|
||||
|
||||
} else if (pass.type == 'composite') {
|
||||
commands[] = {
|
||||
cmd: 'blit',
|
||||
texture: resolve(pass.source),
|
||||
target: resolve(pass.dest),
|
||||
dst_rect: {x: 0, y: 0, width: pass.dest_size.width, height: pass.dest_size.height}
|
||||
}
|
||||
|
||||
} else if (pass.type == 'blit_to_screen') {
|
||||
rect = _calc_presentation(pass.source_size, pass.dest_size, pass.presentation)
|
||||
commands[] = {
|
||||
cmd: 'blit',
|
||||
texture: resolve(pass.source),
|
||||
target: 'screen',
|
||||
dst_rect: rect,
|
||||
filter: pass.presentation == 'integer_scale' ? 'nearest' : 'linear'
|
||||
}
|
||||
} else if (pass.type == 'blit') {
|
||||
src = resolve(pass.source)
|
||||
dst = resolve(pass.dest)
|
||||
commands[] = {
|
||||
cmd: 'blit',
|
||||
texture: src,
|
||||
target: dst,
|
||||
dst_rect: {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
}
|
||||
} else if (pass.type == 'imgui') {
|
||||
commands[] = {
|
||||
cmd: 'imgui',
|
||||
target: resolve(pass.target),
|
||||
draw: pass.draw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {commands: commands, plan: plan}
|
||||
}
|
||||
|
||||
function _calc_presentation(src, dst, mode) {
|
||||
if (mode == 'stretch')
|
||||
return {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
|
||||
var sx = 0
|
||||
var sy = 0
|
||||
var s = 0
|
||||
var w = 0
|
||||
var h = 0
|
||||
var scale = 0
|
||||
if (mode == 'integer_scale') {
|
||||
sx = floor(dst.width / src.width)
|
||||
sy = floor(dst.height / src.height)
|
||||
s = max(1, min(sx, sy))
|
||||
w = src.width * s
|
||||
h = src.height * s
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
// letterbox
|
||||
scale = min(dst.width / src.width, dst.height / src.height)
|
||||
w = src.width * scale
|
||||
h = src.height * scale
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
var _last_plan = null
|
||||
|
||||
var _orig_compile = compositor.compile
|
||||
compositor.compile = function(config) {
|
||||
_last_plan = _orig_compile(config)
|
||||
return _last_plan
|
||||
}
|
||||
|
||||
compositor.snapshot = function() {
|
||||
if (!_last_plan) return null
|
||||
var planes = []
|
||||
var i = 0
|
||||
var pass = null
|
||||
for (i = 0; i < length(_last_plan.passes); i++) {
|
||||
pass = _last_plan.passes[i]
|
||||
if (pass.type == 'render') {
|
||||
planes[] = {
|
||||
drawable_count: length(pass.drawables),
|
||||
camera: pass.camera ? {
|
||||
pos: pass.camera.pos,
|
||||
width: pass.camera.width,
|
||||
height: pass.camera.height
|
||||
} : null,
|
||||
target_size: pass.target_size
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
pass_count: length(_last_plan.passes),
|
||||
target_count: length(array(_last_plan.targets)),
|
||||
screen_size: _last_plan.screen_size,
|
||||
planes: planes
|
||||
}
|
||||
}
|
||||
|
||||
return compositor
|
||||
345
controller.cm
345
controller.cm
@@ -1,345 +0,0 @@
|
||||
var input = use('input')
|
||||
return {}
|
||||
|
||||
var downkeys = {};
|
||||
|
||||
function keyname(key)
|
||||
{
|
||||
var str = input.keyname(key);
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
||||
function modstr(mod = input.keymod()) {
|
||||
var s = "";
|
||||
if (mod.ctrl) s += "C-";
|
||||
if (mod.alt) s += "M-";
|
||||
if (mod.super) s += "S-";
|
||||
return s;
|
||||
}
|
||||
|
||||
prosperon.on('key_down', function key_down(e) {
|
||||
downkeys[e.key] = true;
|
||||
var emacs = modstr(e.mod) + keyname(e.key);
|
||||
if (e.repeat) player[0].raw_input(emacs, "rep");
|
||||
else player[0].raw_input(emacs, "pressed");
|
||||
})
|
||||
|
||||
prosperon.on('quit', function() {
|
||||
os.exit(0);
|
||||
})
|
||||
|
||||
prosperon.on('key_up', function key_up(e) {
|
||||
delete downkeys[e.key];
|
||||
var emacs = modstr(e.mod) + keyname(e.key);
|
||||
player[0].raw_input(emacs, "released");
|
||||
})
|
||||
|
||||
prosperon.on('drop_file', function (path) {
|
||||
player[0].raw_input("drop", "pressed", path);
|
||||
})
|
||||
|
||||
var mousepos = [0, 0];
|
||||
|
||||
prosperon.on('text_input', function (e) {
|
||||
player[0].raw_input("char", "pressed", e.text);
|
||||
})
|
||||
|
||||
prosperon.on('mouse_motion', function (e)
|
||||
{
|
||||
mousepos = e.pos;
|
||||
player[0].mouse_input("move", e.pos, e.d_pos);
|
||||
})
|
||||
|
||||
prosperon.on('mouse_wheel', function mousescroll(e) {
|
||||
player[0].mouse_input(modstr() + "scroll", e.scroll);
|
||||
})
|
||||
|
||||
prosperon.on('mouse_button_down', function(e) {
|
||||
player[0].mouse_input(modstr() + e.button, "pressed");
|
||||
input.mouse.buttons[e.button] = true
|
||||
})
|
||||
|
||||
prosperon.on('mouse_button_up', function(e) {
|
||||
player[0].mouse_input(modstr() + e.button, "released");
|
||||
input.mouse.buttons[e.button] = false
|
||||
})
|
||||
|
||||
input.mouse = {};
|
||||
input.mouse.screenpos = function mouse_screenpos() {
|
||||
return mousepos.slice();
|
||||
};
|
||||
input.mouse.worldpos = function mouse_worldpos() {
|
||||
return prosperon.camera.screen2world(mousepos);
|
||||
};
|
||||
input.mouse.viewpos = function mouse_viewpos()
|
||||
{
|
||||
var world = input.mouse.worldpos();
|
||||
|
||||
return mousepos.slice();
|
||||
}
|
||||
input.mouse.disabled = function mouse_disabled() {
|
||||
input.mouse_mode(1);
|
||||
};
|
||||
input.mouse.normal = function mouse_normal() {
|
||||
input.mouse_mode(0);
|
||||
};
|
||||
input.mouse.mode = function mouse_mode(m) {
|
||||
if (input.mouse.custom[m]) input.cursor_img(input.mouse.custom[m]);
|
||||
else input.mouse_cursor(m);
|
||||
};
|
||||
input.mouse.buttons = {
|
||||
0:false,
|
||||
1:false,
|
||||
2:false
|
||||
}
|
||||
|
||||
input.mouse.set_custom_cursor = function mouse_cursor(img, mode = input.mouse.cursor.default) {
|
||||
if (!img) delete input.mouse.custom[mode];
|
||||
else {
|
||||
input.cursor_img(img);
|
||||
input.mouse.custom[mode] = img;
|
||||
}
|
||||
};
|
||||
input.mouse.doc = {};
|
||||
input.mouse.doc.pos = "The screen position of the mouse.";
|
||||
input.mouse.doc.worldpos = "The position in the game world of the mouse.";
|
||||
input.mouse.disabled.doc = "Set the mouse to hidden. This locks it to the game and hides it, but still provides movement and click events.";
|
||||
input.mouse.normal.doc = "Set the mouse to show again after hiding.";
|
||||
|
||||
input.keyboard = {};
|
||||
input.keyboard.down = function (code) {
|
||||
if (typeof code == "number") return downkeys[code];
|
||||
if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
||||
return null;
|
||||
};
|
||||
|
||||
input.print_pawn_kbm = function (pawn) {
|
||||
if (!("inputs" in pawn)) return;
|
||||
var str = "";
|
||||
for (var key in pawn.inputs) {
|
||||
if (!pawn.inputs[key].doc) continue;
|
||||
str += `${key} | ${pawn.inputs[key].doc}\n`;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
var joysticks = {};
|
||||
|
||||
joysticks["wasd"] = {
|
||||
uy: "w",
|
||||
dy: "s",
|
||||
ux: "d",
|
||||
dx: "a",
|
||||
};
|
||||
|
||||
input.procdown = function procdown() {
|
||||
for (var k in downkeys) player[0].raw_input(keyname(k), "down");
|
||||
|
||||
for (var i in joysticks) {
|
||||
var joy = joysticks[i];
|
||||
var x = joy.ux - joy.dx;
|
||||
var y = joy.uy - joy.dy;
|
||||
player[0].joy_input(i, joysticks[i]);
|
||||
}
|
||||
};
|
||||
|
||||
input.print_md_kbm = function print_md_kbm(pawn) {
|
||||
if (!("inputs" in pawn)) return;
|
||||
|
||||
var str = "";
|
||||
str += "|control|description|\n|---|---|\n";
|
||||
|
||||
for (var key in pawn.inputs) {
|
||||
str += `|${key}|${pawn.inputs[key].doc}|`;
|
||||
str += "\n";
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
input.has_bind = function (pawn, bind) {
|
||||
return typeof pawn.inputs?.[bind] == "function";
|
||||
};
|
||||
|
||||
input.action = {
|
||||
add_new(name) {
|
||||
var action = Object.create(input.action);
|
||||
action.name = name;
|
||||
action.inputs = [];
|
||||
this.actions.push(action);
|
||||
|
||||
return action;
|
||||
},
|
||||
actions: [],
|
||||
};
|
||||
|
||||
input.tabcomplete = function tabcomplete(val, list) {
|
||||
if (!val) return val;
|
||||
list = filter(x => x.startsWith(val))
|
||||
|
||||
if (list.length == 1) {
|
||||
return list[0];
|
||||
}
|
||||
|
||||
var ret = null;
|
||||
var i = val.length;
|
||||
while (!ret && list.length != 0) {
|
||||
var char = list[0][i];
|
||||
if (
|
||||
!list.every(function (x) {
|
||||
return x[i] == char;
|
||||
})
|
||||
)
|
||||
ret = list[0].slice(0, i);
|
||||
else {
|
||||
i++;
|
||||
list = list.filter(x => x.length-1 > i)
|
||||
}
|
||||
}
|
||||
|
||||
return ret ? ret : val;
|
||||
};
|
||||
|
||||
/* May be a human player; may be an AI player */
|
||||
|
||||
/*
|
||||
'block' on a pawn's input blocks any input from reaching below for the
|
||||
*/
|
||||
|
||||
var Player = {
|
||||
players: [],
|
||||
input(fn, ...args) {
|
||||
this.pawns.forEach(x => x[fn]?.(...args));
|
||||
},
|
||||
|
||||
mouse_input(type, ...args) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
if (typeof pawn.inputs?.mouse?.[type] == "function") {
|
||||
pawn.inputs.mouse[type].call(pawn, ...args);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
char_input(c) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
if (typeof pawn.inputs?.char == "function") {
|
||||
pawn.inputs.char.call(pawn, c);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
joy_input(name, joystick) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
if (!pawn.inputs) return;
|
||||
if (!pawn.inputs.joystick) return;
|
||||
if (!pawn.inputs.joystick[name]) return;
|
||||
|
||||
var x = 0;
|
||||
if (input.keyboard.down(joystick.ux)) x++;
|
||||
if (input.keyboard.down(joystick.dx)) x--;
|
||||
var y = 0;
|
||||
if (input.keyboard.down(joystick.uy)) y++;
|
||||
if (input.keyboard.down(joystick.dy)) y--;
|
||||
|
||||
pawn.inputs.joystick[name](x, y);
|
||||
}
|
||||
},
|
||||
|
||||
raw_input(cmd, state, ...args) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
var inputs = pawn.inputs;
|
||||
|
||||
if (!inputs[cmd]) {
|
||||
if (inputs.block) return;
|
||||
continue;
|
||||
}
|
||||
|
||||
var fn = null;
|
||||
|
||||
switch (state) {
|
||||
case "pressed":
|
||||
fn = inputs[cmd];
|
||||
break;
|
||||
case "rep":
|
||||
fn = inputs[cmd].rep ? inputs[cmd] : null;
|
||||
break;
|
||||
case "released":
|
||||
fn = inputs[cmd].released;
|
||||
break;
|
||||
case "down":
|
||||
if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
|
||||
else if (inputs[cmd].down) fn = inputs[cmd];
|
||||
}
|
||||
|
||||
var consumed = false;
|
||||
if (typeof fn == "function") {
|
||||
fn.call(pawn, ...args);
|
||||
consumed = true;
|
||||
}
|
||||
if (state == "released") inputs.release_post?.call(pawn);
|
||||
if (inputs.block) return;
|
||||
if (consumed) return;
|
||||
}
|
||||
},
|
||||
|
||||
obj_controlled(obj) {
|
||||
for (var p in Player.players) {
|
||||
if (p.pawns.has(obj)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
print_pawns() {
|
||||
[...this.pawns].reverse().forEach(x => log.console(x))
|
||||
},
|
||||
|
||||
create() {
|
||||
var n = Object.create(this);
|
||||
n.pawns = new Set()
|
||||
n.gamepads = [];
|
||||
this.players.push(n);
|
||||
this[this.players.length - 1] = n;
|
||||
return n;
|
||||
},
|
||||
|
||||
control(pawn) {
|
||||
if (!pawn)
|
||||
return
|
||||
|
||||
if (!pawn.inputs)
|
||||
throw new Error("attempted to control a pawn without any input object.");
|
||||
|
||||
this.pawns.add(pawn);
|
||||
},
|
||||
|
||||
uncontrol(pawn) {
|
||||
this.pawns.delete(pawn)
|
||||
},
|
||||
};
|
||||
|
||||
input.do_uncontrol = function input_do_uncontrol(pawn) {
|
||||
if (!pawn.inputs) return;
|
||||
Player.players.forEach(function (p) {
|
||||
p.pawns.delete(pawn)
|
||||
});
|
||||
};
|
||||
|
||||
//for (var i = 0; i < 4; i++)
|
||||
Player.create();
|
||||
|
||||
Player.control.doc = "Control a provided object, if the object has an 'inputs' object.";
|
||||
Player.uncontrol.doc = "Uncontrol a previously controlled object.";
|
||||
Player.print_pawns.doc = "Print out a list of the current pawn control stack.";
|
||||
Player.doc = {};
|
||||
Player.doc.players = "A list of current players.";
|
||||
|
||||
var player = Player;
|
||||
|
||||
input.player = Player
|
||||
|
||||
return input
|
||||
316
core.cm
Normal file
316
core.cm
Normal file
@@ -0,0 +1,316 @@
|
||||
// core.cm - Minimal entry point for prosperon rendering
|
||||
//
|
||||
// Usage:
|
||||
// var core = use('prosperon/core')
|
||||
// core.start({
|
||||
// width: 1280,
|
||||
// height: 720,
|
||||
// title: "My Game",
|
||||
// update: function(dt) { ... },
|
||||
// render: function() { return graph }
|
||||
// })
|
||||
//
|
||||
// Headless mode (no window, no input, no render, no audio):
|
||||
// core.start({ headless: true, update: function(dt) { ... } })
|
||||
|
||||
var io = use('cellfs')
|
||||
io.mount('/Users/john/work/prosperon')
|
||||
|
||||
var time_mod = use('time')
|
||||
|
||||
var core = {}
|
||||
|
||||
// Private state
|
||||
var _running = false
|
||||
var _config = null
|
||||
var _backend = null
|
||||
var _window = null
|
||||
var _last_time = 0
|
||||
var _framerate = 60
|
||||
|
||||
// Lazy-loaded modules (only in non-headless mode)
|
||||
var video = null
|
||||
var events = null
|
||||
var imgui = null
|
||||
var debug_imgui = null
|
||||
|
||||
// Start the application
|
||||
core.start = function(config) {
|
||||
_config = config
|
||||
_framerate = config.framerate || 60
|
||||
_running = true
|
||||
_last_time = time_mod.number()
|
||||
|
||||
if (config.probe) _register_probes()
|
||||
|
||||
if (config.headless) {
|
||||
_headless_loop()
|
||||
return true
|
||||
}
|
||||
|
||||
// Load SDL modules
|
||||
video = use('sdl3/video')
|
||||
events = use('sdl3/input')
|
||||
imgui = use('imgui')
|
||||
debug_imgui = use('debug_imgui')
|
||||
|
||||
// Initialize SDL GPU backend
|
||||
var sdl_gpu = use('sdl_gpu')
|
||||
_backend = sdl_gpu
|
||||
|
||||
var init_result = _backend.init({
|
||||
width: config.width || 1280,
|
||||
height: config.height || 720,
|
||||
title: config.title || "Prosperon"
|
||||
})
|
||||
|
||||
if (!init_result) {
|
||||
log.console("core: Failed to initialize backend")
|
||||
return false
|
||||
}
|
||||
|
||||
_window = _backend.get_window()
|
||||
|
||||
if ((config.imgui || config.editor) && imgui.init) {
|
||||
imgui.init(_window, _backend.get_device())
|
||||
}
|
||||
|
||||
// Start main loop
|
||||
_main_loop()
|
||||
|
||||
// Call start callback after the first frame and main loop are established
|
||||
if (config.start) config.start()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Stop the application
|
||||
core.stop = function() {
|
||||
_running = false
|
||||
}
|
||||
|
||||
// Get window size
|
||||
core.window_size = function() {
|
||||
return _backend.get_window_size()
|
||||
}
|
||||
|
||||
// Get backend for direct access
|
||||
core.backend = function() {
|
||||
return _backend
|
||||
}
|
||||
// FPS tracking
|
||||
var _fps_samples = []
|
||||
var _fps_sample_count = 120
|
||||
var _fps_sample_sum = 0
|
||||
var _fps_sample_pos = 0
|
||||
|
||||
function fps_add_sample(sample) {
|
||||
var n = length(_fps_samples)
|
||||
var old = null
|
||||
if (n < _fps_sample_count) {
|
||||
_fps_samples[] = sample
|
||||
_fps_sample_sum += sample
|
||||
} else {
|
||||
old = _fps_samples[_fps_sample_pos]
|
||||
_fps_samples[_fps_sample_pos] = sample
|
||||
_fps_sample_sum += sample - old
|
||||
_fps_sample_pos++
|
||||
if (_fps_sample_pos >= _fps_sample_count) _fps_sample_pos = 0
|
||||
}
|
||||
}
|
||||
|
||||
function fps_get_avg() {
|
||||
var n = length(_fps_samples)
|
||||
return n ? _fps_sample_sum / n : 0
|
||||
}
|
||||
|
||||
var _current_fps = 0
|
||||
var _frame_time_ms = 0
|
||||
|
||||
// Headless loop — update only, no window/input/render/audio
|
||||
function _headless_loop() {
|
||||
if (!_running) return
|
||||
|
||||
var now = time_mod.number()
|
||||
var dt = now - _last_time
|
||||
_last_time = now
|
||||
|
||||
if (_config.update) _config.update(dt)
|
||||
|
||||
var frame_time = 1 / _framerate
|
||||
var elapsed = time_mod.number() - now
|
||||
var delay = frame_time - elapsed
|
||||
if (delay < 0) delay = 0
|
||||
|
||||
$delay(_headless_loop, delay)
|
||||
}
|
||||
|
||||
// Main loop
|
||||
function _main_loop() {
|
||||
var frame_start = time_mod.number()
|
||||
|
||||
if (!_running) return
|
||||
|
||||
var now = frame_start
|
||||
var dt = now - _last_time
|
||||
_last_time = now
|
||||
|
||||
// Process events
|
||||
var evts = events.get_events()
|
||||
|
||||
var win_size = _backend.get_window_size()
|
||||
|
||||
// Get input module for auto-ingestion
|
||||
var input_mod = use('input')
|
||||
|
||||
arrfor(evts, function(ev) {
|
||||
if (_config.imgui || _config.editor) {
|
||||
imgui.process_event(ev)
|
||||
}
|
||||
|
||||
var ev_obj = events.objectify(ev)
|
||||
if (ev_obj.type == 'quit') {
|
||||
_running = false
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
if (ev_obj.type == 'window_pixel_size_changed') {
|
||||
win_size.width = ev_obj.width
|
||||
win_size.height = ev_obj.height
|
||||
if (_backend.set_window_size) {
|
||||
_backend.set_window_size(ev_obj.width, ev_obj.height)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-ingest through input system
|
||||
input_mod.ingest(ev_obj)
|
||||
|
||||
// Optional raw hook for debugging
|
||||
if (_config.input) {
|
||||
_config.input(ev_obj)
|
||||
}
|
||||
})
|
||||
|
||||
// Update
|
||||
if (_config.update) {
|
||||
_config.update(dt)
|
||||
}
|
||||
|
||||
var imgui_mod = use('imgui')
|
||||
var debug_imgui = use('debug_imgui')
|
||||
|
||||
// ImGui Frame
|
||||
if (_config.imgui || _config.editor) {
|
||||
imgui_mod.newframe()
|
||||
}
|
||||
|
||||
// Render
|
||||
var render_result = null
|
||||
var dbg = false
|
||||
var stats = null
|
||||
if (_config.render) {
|
||||
render_result = _config.render()
|
||||
if (render_result) {
|
||||
if (_config.debug == 'graph') {
|
||||
log.console(render_result)
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
dbg = _config.debug == 'cmd'
|
||||
|
||||
// Build stats for debug_imgui
|
||||
stats = {
|
||||
fps: _current_fps,
|
||||
frame_time_ms: _frame_time_ms
|
||||
}
|
||||
|
||||
// Handle both compositor result ({commands: [...]}) and fx_graph (graph object)
|
||||
if (render_result.commands) {
|
||||
if (_config.imgui || _config.editor) {
|
||||
render_result.commands[] = {
|
||||
cmd: 'imgui',
|
||||
draw: function(ui) {
|
||||
if (_config.imgui) _config.imgui(ui)
|
||||
if (is_function(_config.editor)) {
|
||||
debug_imgui.render(ui, null, render_result.plan, stats)
|
||||
_config.editor(ui)
|
||||
}
|
||||
},
|
||||
target: 'screen'
|
||||
}
|
||||
}
|
||||
// Compositor result - execute commands directly
|
||||
_backend.execute_commands(render_result.commands, win_size, dbg)
|
||||
} else {
|
||||
// fx_graph result - execute graph
|
||||
_backend.execute_graph(render_result, win_size, dbg)
|
||||
}
|
||||
|
||||
if (dbg) {
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var frame_end = time_mod.number()
|
||||
var actual_frame_time = frame_end - frame_start
|
||||
|
||||
_frame_time_ms = actual_frame_time * 1000
|
||||
fps_add_sample(actual_frame_time)
|
||||
|
||||
var avg_frame_time = fps_get_avg()
|
||||
_current_fps = avg_frame_time > 0 ? 1 / avg_frame_time : 0
|
||||
|
||||
// Schedule next frame
|
||||
var frame_time = 1 / _framerate
|
||||
var elapsed = frame_end - frame_start
|
||||
var delay = frame_time - elapsed
|
||||
if (delay < 0) delay = 0
|
||||
|
||||
$delay(_main_loop, delay)
|
||||
}
|
||||
|
||||
function _register_probes() {
|
||||
var probe = use('probe')
|
||||
var film2d = use('film2d')
|
||||
var world = use('world')
|
||||
var comp = use('compositor')
|
||||
var input_mod = use('input')
|
||||
var tween_mod = use('tween')
|
||||
var graphics_mod = use('graphics')
|
||||
|
||||
probe.register("drawables", {
|
||||
all: function(args) { return film2d.snapshot() }
|
||||
})
|
||||
|
||||
probe.register("world", {
|
||||
all: function(args) { return world.snapshot() },
|
||||
count: function(args) { return world.count() }
|
||||
})
|
||||
|
||||
probe.register("compositor", {
|
||||
all: function(args) { return comp.snapshot() }
|
||||
})
|
||||
|
||||
probe.register("input", {
|
||||
all: function(args) { return input_mod.snapshot() }
|
||||
})
|
||||
|
||||
probe.register("tweens", {
|
||||
all: function(args) { return tween_mod.snapshot() }
|
||||
})
|
||||
|
||||
probe.register("assets", {
|
||||
all: function(args) { return graphics_mod.snapshot() }
|
||||
})
|
||||
|
||||
probe.register("core", {
|
||||
fps: function(args) {
|
||||
return {fps: _current_fps, frame_time_ms: _frame_time_ms}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return core
|
||||
118
datastream.c
Normal file
118
datastream.c
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "cell.h"
|
||||
|
||||
#define PL_MPEG_IMPLEMENTATION
|
||||
#include "pl_mpeg.h"
|
||||
|
||||
struct datastream {
|
||||
plm_t *plm;
|
||||
JSValue callback;
|
||||
JSContext *js;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
typedef struct datastream datastream;
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
#include "limits.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cbuf.h"
|
||||
|
||||
void datastream_free(JSRuntime *rt,datastream *ds)
|
||||
{
|
||||
plm_destroy(ds->plm);
|
||||
free(ds);
|
||||
}
|
||||
|
||||
QJSCLASS(datastream,)
|
||||
|
||||
struct datastream *ds_openvideo(void *raw, size_t rawlen)
|
||||
{
|
||||
struct datastream *ds = malloc(sizeof(*ds));
|
||||
void *newraw = malloc(rawlen);
|
||||
memcpy(newraw,raw,rawlen);
|
||||
ds->plm = plm_create_with_memory(newraw, rawlen, 1);
|
||||
|
||||
if (!ds->plm) {
|
||||
free(ds);
|
||||
free(newraw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
void ds_advance(struct datastream *ds, double s) {
|
||||
plm_decode(ds->plm, s);
|
||||
}
|
||||
|
||||
void ds_seek(struct datastream *ds, double time) {
|
||||
plm_seek(ds->plm, time, false);
|
||||
}
|
||||
|
||||
double ds_length(struct datastream *ds) {
|
||||
return plm_get_duration(ds->plm);
|
||||
}
|
||||
|
||||
JSC_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); )
|
||||
JSC_CCALL(datastream_seek, ds_seek(js2datastream(js,self), js2number(js,argv[0])))
|
||||
JSC_CCALL(datastream_advance, ds_advance(js2datastream(js,self), js2number(js,argv[0])))
|
||||
JSC_CCALL(datastream_duration, return number2js(js,ds_length(js2datastream(js,self))))
|
||||
JSC_CCALL(datastream_framerate, return number2js(js,plm_get_framerate(js2datastream(js,self)->plm)))
|
||||
|
||||
static const JSCFunctionListEntry js_datastream_funcs[] = {
|
||||
MIST_FUNC_DEF(datastream, time, 0),
|
||||
MIST_FUNC_DEF(datastream, seek, 1),
|
||||
MIST_FUNC_DEF(datastream, advance, 1),
|
||||
MIST_FUNC_DEF(datastream, duration, 0),
|
||||
MIST_FUNC_DEF(datastream, framerate, 0),
|
||||
};
|
||||
|
||||
static void render_frame(plm_t *mpeg, plm_frame_t *frame, void *d) {
|
||||
datastream *ds = d;
|
||||
if (JS_IsNull(ds->callback)) return;
|
||||
uint8_t *rgb = malloc(frame->height*frame->width*4);
|
||||
memset(rgb,255,frame->height*frame->width*4);
|
||||
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
||||
|
||||
// Create surface data object instead of SDL_Surface
|
||||
JS_FRAME(ds->js);
|
||||
JS_ROOT(surfData, JS_NewObject(ds->js));
|
||||
JS_SetPropertyStr(ds->js, surfData.val, "width", JS_NewInt32(ds->js, frame->width));
|
||||
JS_SetPropertyStr(ds->js, surfData.val, "height", JS_NewInt32(ds->js, frame->height));
|
||||
JSValue tmp = JS_NewString(ds->js, "rgba32");
|
||||
JS_SetPropertyStr(ds->js, surfData.val, "format", tmp);
|
||||
JS_SetPropertyStr(ds->js, surfData.val, "pitch", JS_NewInt32(ds->js, frame->width*4));
|
||||
tmp = js_new_blob_stoned_copy(ds->js, rgb, frame->height*frame->width*4);
|
||||
JS_SetPropertyStr(ds->js, surfData.val, "pixels", tmp);
|
||||
|
||||
JSValue s[1];
|
||||
s[0] = surfData.val;
|
||||
JSValue cb = ds->callback;
|
||||
JSValue cret = JS_Call(ds->js, cb, JS_NULL, 1, s);
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
|
||||
free(rgb);
|
||||
// TODO: raise exception
|
||||
}
|
||||
|
||||
JSC_CCALL(os_make_video,
|
||||
size_t len;
|
||||
void *data = js_get_blob_data(js,&len,argv[0]);
|
||||
if (data == -1 || !data) return JS_EXCEPTION;
|
||||
datastream *ds = ds_openvideo(data, len);
|
||||
if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid.");
|
||||
ds->js = js;
|
||||
ds->callback = JS_NULL;
|
||||
plm_set_video_decode_callback(ds->plm, render_frame, ds);
|
||||
return datastream2js(js,ds);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_video_funcs[] = {
|
||||
MIST_FUNC_DEF(os, make_video, 1),
|
||||
};
|
||||
|
||||
CELL_USE_FUNCS(js_video_funcs)
|
||||
498
debug_imgui.cm
Normal file
498
debug_imgui.cm
Normal file
@@ -0,0 +1,498 @@
|
||||
// debug_imgui.cm - ImGui Debug Windows for Render Architecture
|
||||
//
|
||||
// Provides debug windows for inspecting:
|
||||
// - Scene tree and node properties
|
||||
// - Render graph and pass connections
|
||||
// - Effect parameters
|
||||
// - Performance statistics
|
||||
|
||||
var debug_imgui = {}
|
||||
|
||||
var json = use('json')
|
||||
|
||||
// State
|
||||
var _show_scene_tree = false
|
||||
var _show_render_graph = false
|
||||
var _show_effects = false
|
||||
var _show_stats = true
|
||||
var _show_targets = false
|
||||
|
||||
var _selected_node = null
|
||||
var _selected_pass = null
|
||||
var _expanded_nodes = {}
|
||||
|
||||
// Toggle windows
|
||||
debug_imgui.toggle_scene_tree = function() { _show_scene_tree = !_show_scene_tree }
|
||||
debug_imgui.toggle_render_graph = function() { _show_render_graph = !_show_render_graph }
|
||||
debug_imgui.toggle_effects = function() { _show_effects = !_show_effects }
|
||||
debug_imgui.toggle_stats = function() { _show_stats = !_show_stats }
|
||||
debug_imgui.toggle_targets = function() { _show_targets = !_show_targets }
|
||||
|
||||
// Main render function - call from imgui callback
|
||||
debug_imgui.render = function(imgui, scene_graph, render_plan, stats) {
|
||||
// Menu bar for toggling windows
|
||||
_render_menu(imgui)
|
||||
|
||||
if (_show_scene_tree && scene_graph) {
|
||||
_render_scene_tree(imgui, scene_graph)
|
||||
}
|
||||
|
||||
if (_show_render_graph && render_plan) {
|
||||
_render_graph_view(imgui, render_plan)
|
||||
}
|
||||
|
||||
if (_show_effects) {
|
||||
_render_effects_panel(imgui)
|
||||
}
|
||||
|
||||
if (_show_stats && stats) {
|
||||
_render_stats(imgui, stats)
|
||||
}
|
||||
|
||||
if (_show_targets && render_plan) {
|
||||
_render_targets(imgui, render_plan)
|
||||
}
|
||||
|
||||
if (_selected_node) {
|
||||
_render_node_inspector(imgui, _selected_node)
|
||||
}
|
||||
|
||||
if (_selected_pass) {
|
||||
_render_pass_inspector(imgui, _selected_pass)
|
||||
}
|
||||
}
|
||||
|
||||
// Render debug menu
|
||||
function _render_menu(imgui) {
|
||||
imgui.mainmenubar(function() {
|
||||
imgui.menu("Debug", function() {
|
||||
if (imgui.menuitem("Scene Tree", null, function(){}, _show_scene_tree)) {
|
||||
_show_scene_tree = !_show_scene_tree
|
||||
}
|
||||
if (imgui.menuitem("Render Graph", null, function(){}, _show_render_graph)) {
|
||||
_show_render_graph = !_show_render_graph
|
||||
}
|
||||
if (imgui.menuitem("Effects", null, function(){}, _show_effects)) {
|
||||
_show_effects = !_show_effects
|
||||
}
|
||||
if (imgui.menuitem("Statistics", null, function(){}, _show_stats)) {
|
||||
_show_stats = !_show_stats
|
||||
}
|
||||
if (imgui.menuitem("Render Targets", null, function(){}, _show_targets)) {
|
||||
_show_targets = !_show_targets
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Render scene tree window
|
||||
function _render_scene_tree(imgui, scene_graph) {
|
||||
imgui.window("Scene Tree", function() {
|
||||
if (!scene_graph.root) {
|
||||
imgui.text("No scene root")
|
||||
return
|
||||
}
|
||||
|
||||
imgui.text("Total nodes: " + text(scene_graph.stats.total_nodes))
|
||||
imgui.text("Dirty this frame: " + text(scene_graph.stats.dirty_this_frame))
|
||||
imgui.text("Geometry rebuilds: " + text(scene_graph.stats.geometry_rebuilds))
|
||||
|
||||
imgui.text("---")
|
||||
|
||||
_render_node_tree(imgui, scene_graph.root, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Render a node and its children recursively
|
||||
function _render_node_tree(imgui, node, depth) {
|
||||
if (!node) return
|
||||
|
||||
var id = node.id || 'unknown'
|
||||
var type = node.type || 'node'
|
||||
var label = type + " [" + id + "]"
|
||||
|
||||
// Add dirty indicator
|
||||
if (is_number(node.dirty) && node.dirty > 0) {
|
||||
label += " *"
|
||||
}
|
||||
|
||||
var has_children = node.children && length(node.children) > 0
|
||||
|
||||
if (has_children) {
|
||||
imgui.tree(label, function() {
|
||||
// Show node summary
|
||||
_render_node_summary(imgui, node)
|
||||
|
||||
// Recurse children
|
||||
var i = 0
|
||||
for (i = 0; i < length(node.children); i++) {
|
||||
_render_node_tree(imgui, node.children[i], depth + 1)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Leaf node - show as selectable
|
||||
if (imgui.button(label)) {
|
||||
_selected_node = node
|
||||
}
|
||||
imgui.sameline(0)
|
||||
_render_node_summary(imgui, node)
|
||||
}
|
||||
}
|
||||
|
||||
// Render node summary inline
|
||||
function _render_node_summary(imgui, node) {
|
||||
var info = []
|
||||
|
||||
if (node.pos) {
|
||||
info[] = "pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")"
|
||||
}
|
||||
|
||||
if (node.width && node.height) {
|
||||
info[] = "size:" + text(node.width) + "x" + text(node.height)
|
||||
}
|
||||
|
||||
if (node.image) {
|
||||
info[] = "img:" + node.image
|
||||
}
|
||||
|
||||
var t = null
|
||||
if (node.text) {
|
||||
t = node.text
|
||||
if (length(t) > 20) t = text(t, 0, 17) + "..."
|
||||
info[] = "\"" + t + "\""
|
||||
}
|
||||
|
||||
var fx = []
|
||||
var j = 0
|
||||
if (node.effects && length(node.effects) > 0) {
|
||||
for (j = 0; j < length(node.effects); j++) {
|
||||
fx[] = node.effects[j].type
|
||||
}
|
||||
info[] = "fx:[" + text(fx, ",") + "]"
|
||||
}
|
||||
|
||||
if (length(info) > 0) {
|
||||
imgui.text(" " + text(info, " "))
|
||||
}
|
||||
}
|
||||
|
||||
// Render node inspector window
|
||||
function _render_node_inspector(imgui, node) {
|
||||
imgui.window("Node Inspector", function() {
|
||||
if (imgui.button("Close")) {
|
||||
_selected_node = null
|
||||
return
|
||||
}
|
||||
|
||||
imgui.text("ID: " + (node.id || 'none'))
|
||||
imgui.text("Type: " + (node.type || 'unknown'))
|
||||
imgui.text("Layer: " + text(node.layer || 0))
|
||||
imgui.text("Dirty: " + text(node.dirty || 0))
|
||||
|
||||
imgui.text("---")
|
||||
|
||||
// Position
|
||||
var pos = null
|
||||
if (node.pos) {
|
||||
imgui.text("Position")
|
||||
pos = imgui.slider("X", node.pos.x, -1000, 1000)
|
||||
if (pos != node.pos.x) node.pos.x = pos
|
||||
pos = imgui.slider("Y", node.pos.y, -1000, 1000)
|
||||
if (pos != node.pos.y) node.pos.y = pos
|
||||
}
|
||||
|
||||
// Size
|
||||
if (node.width != null) {
|
||||
imgui.text("Size")
|
||||
node.width = imgui.slider("Width", node.width, 0, 1000)
|
||||
node.height = imgui.slider("Height", node.height, 0, 1000)
|
||||
}
|
||||
|
||||
// Opacity
|
||||
if (node.opacity != null) {
|
||||
node.opacity = imgui.slider("Opacity", node.opacity, 0, 1)
|
||||
}
|
||||
|
||||
// Color
|
||||
if (node.color) {
|
||||
imgui.text("Color")
|
||||
node.color.r = imgui.slider("R", node.color.r, 0, 1)
|
||||
node.color.g = imgui.slider("G", node.color.g, 0, 1)
|
||||
node.color.b = imgui.slider("B", node.color.b, 0, 1)
|
||||
node.color.a = imgui.slider("A", node.color.a, 0, 1)
|
||||
}
|
||||
|
||||
// World transform (read-only)
|
||||
if (node.world_pos) {
|
||||
imgui.text("---")
|
||||
imgui.text("World Position: (" + text(round(node.world_pos.x * 100) / 100) + ", " + text(round(node.world_pos.y * 100) / 100) + ")")
|
||||
}
|
||||
if (node.world_opacity != null) {
|
||||
imgui.text("World Opacity: " + text(round(node.world_opacity * 100) / 100))
|
||||
}
|
||||
|
||||
// Image
|
||||
if (node.image) {
|
||||
imgui.text("---")
|
||||
imgui.text("Image: " + node.image)
|
||||
}
|
||||
|
||||
// Text
|
||||
if (node.text != null) {
|
||||
imgui.text("---")
|
||||
imgui.text("Text: " + node.text)
|
||||
if (node.font) imgui.text("Font: " + node.font)
|
||||
if (node.size) imgui.text("Size: " + text(node.size))
|
||||
}
|
||||
|
||||
// Effects
|
||||
var ei = 0
|
||||
var fx = null
|
||||
if (node.effects && length(node.effects) > 0) {
|
||||
imgui.text("---")
|
||||
imgui.text("Effects:")
|
||||
for (ei = 0; ei < length(node.effects); ei++) {
|
||||
fx = node.effects[ei]
|
||||
imgui.tree(fx.type, function() {
|
||||
arrfor(array(fx), k => {
|
||||
var v = fx[k]
|
||||
if (k != 'type' && k != 'source') {
|
||||
if (is_number(v)) {
|
||||
fx[k] = imgui.slider(k, v, 0, 10)
|
||||
} else {
|
||||
imgui.text(k + ": " + text(v))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Geometry cache info
|
||||
if (node.geom_cache) {
|
||||
imgui.text("---")
|
||||
imgui.text("Geometry Cache:")
|
||||
imgui.text(" Vertices: " + text(node.geom_cache.vert_count || 0))
|
||||
imgui.text(" Indices: " + text(node.geom_cache.index_count || 0))
|
||||
if (node.geom_cache.texture_key) {
|
||||
imgui.text(" Texture: " + node.geom_cache.texture_key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Render graph view window
|
||||
function _render_graph_view(imgui, plan) {
|
||||
imgui.window("Render Graph", function() {
|
||||
if (!plan || !plan.passes) {
|
||||
imgui.text("No render plan")
|
||||
return
|
||||
}
|
||||
|
||||
imgui.text("Passes: " + text(length(plan.passes)))
|
||||
imgui.text("Targets: " + text(length(array(plan.targets || {}))))
|
||||
imgui.text("Persistent: " + text(length(array(plan.persistent_targets || {}))))
|
||||
|
||||
imgui.text("---")
|
||||
|
||||
var i = 0
|
||||
var pass = null
|
||||
var label = null
|
||||
var target_info = null
|
||||
for (i = 0; i < length(plan.passes); i++) {
|
||||
pass = plan.passes[i]
|
||||
label = text(i) + ": " + pass.type
|
||||
|
||||
if (pass.shader) label += " [" + pass.shader + "]"
|
||||
if (pass.renderer) label += " [" + pass.renderer + "]"
|
||||
|
||||
if (imgui.button(label)) {
|
||||
_selected_pass = pass
|
||||
}
|
||||
|
||||
// Show target info
|
||||
imgui.sameline(0)
|
||||
target_info = ""
|
||||
if (pass.target) {
|
||||
if (pass.target == 'screen') {
|
||||
target_info = "-> screen"
|
||||
} else if (pass.target.key) {
|
||||
target_info = "-> " + pass.target.key
|
||||
}
|
||||
}
|
||||
if (pass.output) {
|
||||
if (pass.output.key) {
|
||||
target_info = "-> " + pass.output.key
|
||||
}
|
||||
}
|
||||
if (target_info) imgui.text(target_info)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Render pass inspector
|
||||
function _render_pass_inspector(imgui, pass) {
|
||||
imgui.window("Pass Inspector", function() {
|
||||
if (imgui.button("Close")) {
|
||||
_selected_pass = null
|
||||
return
|
||||
}
|
||||
|
||||
imgui.text("Type: " + pass.type)
|
||||
|
||||
if (pass.shader) imgui.text("Shader: " + pass.shader)
|
||||
if (pass.renderer) imgui.text("Renderer: " + pass.renderer)
|
||||
if (pass.blend) imgui.text("Blend: " + pass.blend)
|
||||
if (pass.presentation) imgui.text("Presentation: " + pass.presentation)
|
||||
|
||||
// Target info
|
||||
imgui.text("---")
|
||||
if (pass.target) {
|
||||
if (pass.target == 'screen') {
|
||||
imgui.text("Target: screen")
|
||||
} else if (pass.target.key) {
|
||||
imgui.text("Target: " + pass.target.key)
|
||||
if (pass.target.w) imgui.text(" Size: " + text(pass.target.w) + "x" + text(pass.target.h))
|
||||
}
|
||||
}
|
||||
|
||||
if (pass.input) {
|
||||
imgui.text("Input: " + (pass.input.key || 'unknown'))
|
||||
}
|
||||
|
||||
if (pass.output) {
|
||||
imgui.text("Output: " + (pass.output.key || 'unknown'))
|
||||
}
|
||||
|
||||
// Uniforms
|
||||
if (pass.uniforms) {
|
||||
imgui.text("---")
|
||||
imgui.text("Uniforms:")
|
||||
arrfor(array(pass.uniforms), k => {
|
||||
var v = pass.uniforms[k]
|
||||
if (is_array(v)) {
|
||||
imgui.text(" " + k + ": [" + text(v, ", ") + "]")
|
||||
} else {
|
||||
imgui.text(" " + k + ": " + text(v))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clear color
|
||||
if (pass.color) {
|
||||
imgui.text("---")
|
||||
imgui.text("Clear: rgba(" +
|
||||
text(round(pass.color.r * 255)) + "," +
|
||||
text(round(pass.color.g * 255)) + "," +
|
||||
text(round(pass.color.b * 255)) + "," +
|
||||
text(round(pass.color.a * 100) / 100) + ")")
|
||||
}
|
||||
|
||||
// Source size
|
||||
if (pass.source_size) {
|
||||
imgui.text("Source size: " + text(pass.source_size.w || pass.source_size.width) + "x" + text(pass.source_size.h || pass.source_size.height))
|
||||
}
|
||||
if (pass.dest_size) {
|
||||
imgui.text("Dest size: " + text(pass.dest_size.w || pass.dest_size.width) + "x" + text(pass.dest_size.h || pass.dest_size.height))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Render effects panel
|
||||
function _render_effects_panel(imgui) {
|
||||
var effects_mod = use('effects')
|
||||
|
||||
imgui.window("Effects Registry", function() {
|
||||
var effect_list = effects_mod.list()
|
||||
imgui.text("Registered effects: " + text(length(effect_list)))
|
||||
imgui.text("---")
|
||||
|
||||
var i = 0
|
||||
var name = null
|
||||
var deff = null
|
||||
for (i = 0; i < length(effect_list); i++) {
|
||||
name = effect_list[i]
|
||||
deff = effects_mod.get(name)
|
||||
|
||||
imgui.tree(name, function() {
|
||||
imgui.text("Type: " + (deff.type || 'unknown'))
|
||||
imgui.text("Requires target: " + (deff.requires_target ? "yes" : "no"))
|
||||
|
||||
if (deff.params) {
|
||||
imgui.text("Parameters:")
|
||||
arrfor(array(deff.params), k => {
|
||||
var p = deff.params[k]
|
||||
var info = k
|
||||
if (p.default != null) info += " = " + text(p.default)
|
||||
if (p.type) info += " (" + p.type + ")"
|
||||
if (p.required) info += " [required]"
|
||||
imgui.text(" " + info)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Render statistics
|
||||
function _render_stats(imgui, stats) {
|
||||
imgui.window("Render Statistics", function() {
|
||||
if (stats.scene) {
|
||||
imgui.text("Scene:")
|
||||
imgui.text(" Total nodes: " + text(stats.scene.total_nodes || 0))
|
||||
imgui.text(" Dirty nodes: " + text(stats.scene.dirty_this_frame || 0))
|
||||
imgui.text(" Geometry rebuilds: " + text(stats.scene.geometry_rebuilds || 0))
|
||||
}
|
||||
|
||||
if (stats.render) {
|
||||
imgui.text("---")
|
||||
imgui.text("Render:")
|
||||
imgui.text(" Draw calls: " + text(stats.render.draw_calls || 0))
|
||||
imgui.text(" Triangles: " + text(stats.render.triangles || 0))
|
||||
imgui.text(" Batches: " + text(stats.render.batches || 0))
|
||||
}
|
||||
|
||||
if (stats.targets) {
|
||||
imgui.text("---")
|
||||
imgui.text("Targets:")
|
||||
imgui.text(" Active: " + text(stats.targets.active || 0))
|
||||
imgui.text(" Pooled: " + text(stats.targets.pooled || 0))
|
||||
imgui.text(" Memory: " + text(stats.targets.memory_mb || 0) + " MB")
|
||||
}
|
||||
|
||||
if (stats.fps) {
|
||||
imgui.text("---")
|
||||
imgui.text("FPS: " + text(round(stats.fps)))
|
||||
imgui.text("Frame time: " + text(round(stats.frame_time_ms * 100) / 100) + " ms")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Render targets view
|
||||
function _render_targets(imgui, plan) {
|
||||
imgui.window("Render Targets", function() {
|
||||
if (!plan) {
|
||||
imgui.text("No render plan")
|
||||
return
|
||||
}
|
||||
|
||||
imgui.text("Temporary Targets:")
|
||||
if (plan.targets) {
|
||||
arrfor(array(plan.targets), key => {
|
||||
var t = plan.targets[key]
|
||||
imgui.text(" " + key + ": " + text(t.width) + "x" + text(t.height))
|
||||
})
|
||||
}
|
||||
|
||||
imgui.text("---")
|
||||
imgui.text("Persistent Targets:")
|
||||
if (plan.persistent_targets) {
|
||||
arrfor(array(plan.persistent_targets), key => {
|
||||
var t = plan.persistent_targets[key]
|
||||
imgui.text(" " + key + ": " + text(t.width) + "x" + text(t.height ))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return debug_imgui
|
||||
221
docs/3pfollow.md
Normal file
221
docs/3pfollow.md
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
draft: true
|
||||
---
|
||||
|
||||
This was a good camera system for third person games. Recreate it eventually in cell.
|
||||
|
||||
// void ThirdPersonFollow::_ready() {
|
||||
// Target = dynamic_cast<Spatial*>(get_node(TargetPath));
|
||||
// ExternalFrame = dynamic_cast<Spatial*>(get_node(ExternalFramePath));
|
||||
// }
|
||||
|
||||
// void ThirdPersonFollow::_process(float delta_time) {
|
||||
|
||||
// if (Target != nullptr) {
|
||||
// // Get the frame of reference
|
||||
// if (has_node(ExternalFramePath) && get_node(ExternalFramePath) != this)
|
||||
// TFOR = dynamic_cast<Spatial*>(get_node(ExternalFramePath))->get_global_transform();
|
||||
// else
|
||||
// TFOR = Transform(Basis(glm::vec3::RIGHT(), glm::vec3::UP(), glm::vec3::FORWARD()), glm::vec3::ZERO());
|
||||
|
||||
// CalculateTargetOffset();
|
||||
// TargetPosition = CalculatePosition();
|
||||
|
||||
// translate(to_local(FrameBasedVectorLerp(get_global_translation(), TargetPosition, PositionSpeeds, delta_time)));
|
||||
|
||||
// set_rotation_degrees(get_rotation_degrees().linear_interpolate(RotationOffset, RotationSpeed * delta_time));
|
||||
// }
|
||||
|
||||
// // For rotation
|
||||
// // TODO: Add rotation offset in properly
|
||||
// //this->SetActorRotation(FMath::Lerp(this->GetActorRotation(), TargetRotation, RotationSpeed * DeltaTime));
|
||||
// //this->set_rotation(glm::quat(this->get_rotation().slerp(RotationOffset, RotationSpeed * DeltaTime)).get_euler());
|
||||
|
||||
// //set_global_rotation_degrees(get_global_rotation_degrees().linear_interpolate(RotationOffset, RotationSpeed * delta_time));
|
||||
|
||||
// // TODO: Figure out how to translate rotation to a global rotation
|
||||
// }
|
||||
|
||||
/*
|
||||
|
||||
glm::vec3 ThirdPersonFollow::CalculatePosition()
|
||||
{
|
||||
glm::vec3 p1 = CalculateCenter();
|
||||
|
||||
glm::vec3 p2 =
|
||||
XDirPosts ? GetPostsOffset(TFOR.Right(),
|
||||
FloatWidths.
|
||||
x) : GetExtentsOffset(TFOR.Right(),
|
||||
FloatWidths.x,
|
||||
TargetOffset.x,
|
||||
AnchorWidths.x);
|
||||
glm::vec3 p3 =
|
||||
YDirPosts ? GetPostsOffset(TFOR.Up(),
|
||||
FloatWidths.
|
||||
y) : GetExtentsOffset(TFOR.Up(),
|
||||
FloatWidths.y,
|
||||
TargetOffset.y,
|
||||
AnchorWidths.y);
|
||||
glm::vec3 p4 =
|
||||
ZDirPosts ? GetPostsOffset(TFOR.Back(),
|
||||
FloatWidths.
|
||||
z) : GetExtentsOffset(TFOR.Back(),
|
||||
FloatWidths.z,
|
||||
TargetOffset.z,
|
||||
AnchorWidths.z);
|
||||
|
||||
return p1 + p2 + p3 + p4;
|
||||
}
|
||||
|
||||
glm::vec3 ThirdPersonFollow::CalculateCenter()
|
||||
{
|
||||
return Target->get_global_translation() +
|
||||
TFOR.TransformDirection(Offset) + (mytransform->Back() * Distance);
|
||||
}
|
||||
|
||||
glm::vec3 ThirdPersonFollow::
|
||||
GetPostsOffset(const glm::vec3 & DirectionVector, float AnchorWidth)
|
||||
{
|
||||
float dot = glm::dot(Target->Forward(), DirectionVector);
|
||||
|
||||
return DirectionVector * (dot >= 0 ? AnchorWidth : AnchorWidth * -1);
|
||||
}
|
||||
|
||||
glm::vec3 ThirdPersonFollow::
|
||||
GetExtentsOffset(const glm::vec3 & DirectionVector, float AnchorWidth,
|
||||
float TOffset, float Width)
|
||||
{
|
||||
|
||||
float negated_offset_sign = ((0 <= TOffset) - (TOffset < 0)) * -1.f;
|
||||
float TotalWidth = AnchorWidth + Width;
|
||||
|
||||
if (glm::abs(TOffset) > TotalWidth
|
||||
&& !glm::epsilonEqual(glm::abs(TOffset), TotalWidth, 0.5f))
|
||||
return DirectionVector * TotalWidth * negated_offset_sign;
|
||||
else {
|
||||
if (glm::abs(TOffset) >= AnchorWidth)
|
||||
return DirectionVector * AnchorWidth * negated_offset_sign;
|
||||
else
|
||||
return DirectionVector * TOffset * -1.f;
|
||||
}
|
||||
|
||||
return glm::vec3(0.f);
|
||||
}
|
||||
|
||||
glm::vec3 ThirdPersonFollow::FrameBasedVectorLerp(const glm::vec3 & From,
|
||||
const glm::vec3 & To,
|
||||
const glm::vec3 & Speeds,
|
||||
float Tick)
|
||||
{
|
||||
// Previously "FORTransform.TransformVector(Speeds)
|
||||
glm::vec3 TSpeed = glm::abs(TFOR.TransformDirection(Speeds));
|
||||
glm::vec3 TOffset = glm::abs(TFOR.TransformDirection(TargetOffset));
|
||||
glm::vec3 TAnchorWidths =
|
||||
glm::abs(TFOR.TransformDirection(AnchorWidths));
|
||||
glm::vec3 TFloatWidths =
|
||||
glm::abs(TFOR.TransformDirection(FloatWidths));
|
||||
|
||||
|
||||
|
||||
// If these are true, that means to use the anchor speed instead of the normal speed.
|
||||
// True if the offset is beyond the anchor plus the width
|
||||
bool bUseX = GetLerpParam(TOffset.x, TAnchorWidths.x, TFloatWidths.x);
|
||||
bool bUseY = GetLerpParam(TOffset.y, TAnchorWidths.y, TFloatWidths.y);
|
||||
bool bUseZ = GetLerpParam(TOffset.z, TAnchorWidths.z, TFloatWidths.z);
|
||||
|
||||
|
||||
float xAlpha =
|
||||
glm::clamp((bUseX ? AnchorSpeed : TSpeed.x) * Tick, 0.f, 1.f);
|
||||
float yAlpha =
|
||||
glm::clamp((bUseY ? AnchorSpeed : TSpeed.y) * Tick, 0.f, 1.f);
|
||||
float zAlpha =
|
||||
glm::clamp((bUseZ ? AnchorSpeed : TSpeed.z) * Tick, 0.f, 1.f);
|
||||
|
||||
return VectorLerpPiecewise(From, To,
|
||||
glm::vec3(xAlpha, yAlpha, zAlpha));
|
||||
}
|
||||
|
||||
int float_epsilon(mfloat_t a, mfloat_t b, mfloat_t e)
|
||||
{
|
||||
return fabs(a - b) < e;
|
||||
}
|
||||
|
||||
int lerpparam(float offset, float anchorwidth, float floatwidth)
|
||||
{
|
||||
return (offset > (anchorwidth + floatwidth)) && !float_epsilon(anchorwidth + floatwidth, offset, 0.5f);
|
||||
}
|
||||
|
||||
mfloat_t *vec3lerp(mfloat_t from[3], mfloat_t to[3], mfloat_t a[3])
|
||||
{
|
||||
from[0] = from[0] + (to[0] - from[0]) * a[0];
|
||||
from[1] = from[1] + (to[1] - from[1]) * a[1];
|
||||
from[2] = from[2] + (to[2] - from[2]) * a[2];
|
||||
}
|
||||
|
||||
void follow_calctargets()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ThirdPersonFollow::CalculateTargets()
|
||||
{
|
||||
// For translation
|
||||
TargetPosition = CalculatePosition();
|
||||
|
||||
|
||||
// For rotation
|
||||
// TODO: Check of this implementation is the same as UKismetMath FindLookAtRotation
|
||||
TargetRotation =
|
||||
RemoveLockedRotation(glm::quat
|
||||
(mytransform->
|
||||
get_global_transform()->get_origin() -
|
||||
Target->
|
||||
get_global_transform()->get_origin()));
|
||||
}
|
||||
|
||||
follow_removelockedrot()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::quat ThirdPersonFollow::
|
||||
RemoveLockedRotation(const glm::quat & CurrentRotation)
|
||||
{
|
||||
glm::vec3 NewRotator;
|
||||
glm::vec3 CurrentRotator = glm::eulerAngles(CurrentRotation);
|
||||
//
|
||||
|
||||
NewRotator.x =
|
||||
LockRoll ? mytransform->get_rotation().x : CurrentRotator.x;
|
||||
NewRotator.y =
|
||||
LockPitch ? mytransform->get_rotation().y : CurrentRotator.y;
|
||||
NewRotator.z =
|
||||
LockYaw ? mytransform->get_rotation().z : CurrentRotator.z;
|
||||
|
||||
return glm::quat(NewRotator);
|
||||
}
|
||||
|
||||
// The target offset is the value of where the target is compared to where he "should" be based on where
|
||||
// the camera is. It is what the camera needs to move to be centered on the target
|
||||
void ThirdPersonFollow::CalculateTargetOffset()
|
||||
{
|
||||
glm::vec3 p1 =
|
||||
(mytransform->Forward() * Distance) +
|
||||
TFOR.TransformDirection(Offset) +
|
||||
mytransform->get_global_translation();
|
||||
glm::vec3 p2 =
|
||||
TFOR.InverseTransformDirection(Target->get_global_translation());
|
||||
glm::vec3 p3 = TFOR.InverseTransformDirection(p1);
|
||||
|
||||
TargetOffset = p2 - p3;
|
||||
}
|
||||
|
||||
void follow_targetoffset()
|
||||
{
|
||||
mfloat_t p1[3], p2[3], p3[3] = {0};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
16
docs/api/actor.md
Normal file
16
docs/api/actor.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "actor"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# actor
|
||||
|
||||
### toString() <sub>function</sub>
|
||||
|
||||
### spawn(script, config, callback) <sub>function</sub>
|
||||
|
||||
### clear() <sub>function</sub>
|
||||
|
||||
### kill() <sub>function</sub>
|
||||
|
||||
### delay(fn, seconds) <sub>function</sub>
|
||||
42
docs/api/console.md
Normal file
42
docs/api/console.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "console"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# console
|
||||
|
||||
The console object provides various logging, debugging, and output methods.
|
||||
|
||||
### print() <sub>function</sub>
|
||||
|
||||
### spam(msg) <sub>function</sub>
|
||||
|
||||
Output a spam-level message for very verbose logging.
|
||||
|
||||
### debug(msg) <sub>function</sub>
|
||||
|
||||
Output a debug-level message.
|
||||
|
||||
### info(msg) <sub>function</sub>
|
||||
|
||||
Output info level message.
|
||||
|
||||
### warn(msg) <sub>function</sub>
|
||||
|
||||
Output warn level message.
|
||||
|
||||
### log(msg) <sub>function</sub>
|
||||
|
||||
Output directly to in game console.
|
||||
|
||||
### error(e) <sub>function</sub>
|
||||
|
||||
Output error level message, and print stacktrace.
|
||||
|
||||
### panic(e) <sub>function</sub>
|
||||
|
||||
Output a panic-level message and exit the program.
|
||||
|
||||
### assert(op, str = `assertion failed [value '${op}']`) <sub>function</sub>
|
||||
|
||||
If the condition is false, print an error and panic.
|
||||
62
docs/api/index.md
Normal file
62
docs/api/index.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: "API Reference"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
Prosperon's API is a set of modules imported with `use()`. There is no global engine object — import only what you need.
|
||||
|
||||
## Game-Facing Modules
|
||||
|
||||
These are the modules you use to build games:
|
||||
|
||||
| Module | Import | Purpose |
|
||||
|--------|--------|---------|
|
||||
| **core** | `use('core')` | Main loop, window, GPU initialization |
|
||||
| **sprite** | `use('sprite')` | Create and manage sprites |
|
||||
| **compositor** | `use('compositor')` | Build render plans from scene configs |
|
||||
| **camera** | `use('camera')` | Viewport into the world |
|
||||
| **input** | `use('input')` | Action mapping, device routing, control stacks |
|
||||
| **sound** | `use('sound')` | Audio playback and mixing |
|
||||
| **world** | `use('world')` | Entity management |
|
||||
| **text2d** | `use('text2d')` | Text rendering |
|
||||
| **shape2d** | `use('shape2d')` | SDF-rendered shapes (rect, circle, ellipse, pill) |
|
||||
| **tilemap2d** | `use('tilemap2d')` | Grid-based tile rendering |
|
||||
| **line2d** | `use('line2d')` | Line rendering |
|
||||
| **particles2d** | `use('particles2d')` | Particle emitters |
|
||||
| **tween** | `use('tween')` | Value interpolation over time |
|
||||
| **ease** | `use('ease')` | Easing functions |
|
||||
| **color** | `use('color')` | Color utilities |
|
||||
| **resources** | `use('resources')` | Asset path resolution |
|
||||
| **action** | `use('action')` | Action definitions |
|
||||
| **draw2d** | `use('draw2d')` | Immediate-mode 2D drawing |
|
||||
| **clay** | `use('clay')` | UI layout |
|
||||
| **point** | `use('point')` | Point/vector utilities |
|
||||
|
||||
## Actor Primitives
|
||||
|
||||
These are built into the Pit language runtime and available in all `.ce` programs:
|
||||
|
||||
| Primitive | Purpose |
|
||||
|-----------|---------|
|
||||
| `$start(callback, path)` | Spawn a child actor from a program |
|
||||
| `$receiver(callback)` | Register a message handler |
|
||||
| `send(target, message)` | Send a message to an actor |
|
||||
| `$delay(callback, seconds)` | Schedule a one-shot callback |
|
||||
| `$clock(callback, interval)` | Schedule a repeating callback |
|
||||
| `$stop()` | Terminate the current actor |
|
||||
|
||||
## Globals
|
||||
|
||||
| Name | Purpose |
|
||||
|------|---------|
|
||||
| `use(path)` | Import a module |
|
||||
| `log.console(msg)` | Print to console |
|
||||
| `meme(proto, data)` | Create object with prototype and overrides |
|
||||
|
||||
## Internal Modules
|
||||
|
||||
These are used by the engine internally and not intended for direct game use:
|
||||
|
||||
`film2d`, `effects`, `sdl_gpu`, `graphics`, `rasterize`, `fx_graph`, `debug_imgui`, `device`, `input/backends/*`, `input/bindings`, `input/devices`, `input/router`
|
||||
92
docs/api/modules/actor.md
Normal file
92
docs/api/modules/actor.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: "actor"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# actor
|
||||
|
||||
|
||||
A set of utilities for iterating over a hierarchy of actor-like objects, as well
|
||||
as managing tag-based lookups. Objects are assumed to have a "objects" property,
|
||||
pointing to children or sub-objects, forming a tree.
|
||||
|
||||
|
||||
### all_objects(fn, startobj) <sub>function</sub>
|
||||
|
||||
|
||||
Iterate over each object (and its sub-objects) in the hierarchy, calling fn for each one.
|
||||
|
||||
|
||||
**fn**: A callback function that receives each object. If it returns a truthy value, iteration stops and that value is returned.
|
||||
|
||||
**startobj**: The root object at which iteration begins, default is the global "world".
|
||||
|
||||
|
||||
**Returns**: The first truthy value returned by fn, or undefined if none.
|
||||
|
||||
|
||||
### find_object(fn, startobj) <sub>function</sub>
|
||||
|
||||
|
||||
Intended to find a matching object within the hierarchy.
|
||||
|
||||
|
||||
**fn**: A callback or criteria to locate a particular object.
|
||||
|
||||
**startobj**: The root object at which search begins, default "world".
|
||||
|
||||
|
||||
**Returns**: Not yet implemented.
|
||||
|
||||
|
||||
### tag_add(tag, obj) <sub>function</sub>
|
||||
|
||||
|
||||
Associate the given object with the specified tag. Creates a new tag set if it does not exist.
|
||||
|
||||
|
||||
**tag**: A string tag to associate with the object.
|
||||
|
||||
**obj**: The object to add under this tag.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### tag_rm(tag, obj) <sub>function</sub>
|
||||
|
||||
|
||||
Remove the given object from the specified tag’s set, if it exists.
|
||||
|
||||
|
||||
**tag**: The tag to remove the object from.
|
||||
|
||||
**obj**: The object to remove from the tag set.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### tag_clear_guid(obj) <sub>function</sub>
|
||||
|
||||
|
||||
Remove the object from all tag sets.
|
||||
|
||||
|
||||
**obj**: The object whose tags should be cleared.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### objects_with_tag(tag) <sub>function</sub>
|
||||
|
||||
|
||||
Retrieve all objects currently tagged with the specified tag.
|
||||
|
||||
|
||||
**tag**: A string tag to look up.
|
||||
|
||||
|
||||
**Returns**: An array of objects associated with the given tag.
|
||||
|
||||
51
docs/api/modules/camera.md
Normal file
51
docs/api/modules/camera.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "camera"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# camera
|
||||
|
||||
### list() <sub>function</sub>
|
||||
|
||||
Return an array of available camera device IDs.
|
||||
|
||||
|
||||
|
||||
**Returns**: An array of camera IDs, or undefined if no cameras are available.
|
||||
|
||||
|
||||
### open(id) <sub>function</sub>
|
||||
|
||||
Open a camera device with the given ID.
|
||||
|
||||
|
||||
|
||||
**id**: The camera ID to open.
|
||||
|
||||
|
||||
**Returns**: A camera object on success, or throws an error if the camera cannot be opened.
|
||||
|
||||
|
||||
### name(id) <sub>function</sub>
|
||||
|
||||
Return the name of the camera with the given ID.
|
||||
|
||||
|
||||
|
||||
**id**: The camera ID to query.
|
||||
|
||||
|
||||
**Returns**: A string with the camera's name, or throws an error if the name cannot be retrieved.
|
||||
|
||||
|
||||
### position(id) <sub>function</sub>
|
||||
|
||||
Return the physical position of the camera with the given ID.
|
||||
|
||||
|
||||
|
||||
**id**: The camera ID to query.
|
||||
|
||||
|
||||
**Returns**: A string indicating the camera position ("unknown", "front", or "back").
|
||||
|
||||
12
docs/api/modules/cmd.md
Normal file
12
docs/api/modules/cmd.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "cmd"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# cmd
|
||||
|
||||
### length <sub>number</sub>
|
||||
|
||||
### name <sub>string</sub>
|
||||
|
||||
### prototype <sub>object</sub>
|
||||
12
docs/api/modules/color.md
Normal file
12
docs/api/modules/color.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "color"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# color
|
||||
|
||||
### Color <sub>object</sub>
|
||||
|
||||
### esc <sub>object</sub>
|
||||
|
||||
### ColorMap <sub>object</sub>
|
||||
81
docs/api/modules/debug.md
Normal file
81
docs/api/modules/debug.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: "debug"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# debug
|
||||
|
||||
### stack_depth() <sub>function</sub>
|
||||
|
||||
Return the current stack depth.
|
||||
|
||||
|
||||
|
||||
**Returns**: A number representing the stack depth.
|
||||
|
||||
|
||||
### build_backtrace() <sub>function</sub>
|
||||
|
||||
Build and return a backtrace of the current call stack.
|
||||
|
||||
|
||||
|
||||
**Returns**: An object representing the call stack backtrace.
|
||||
|
||||
|
||||
### closure_vars(fn) <sub>function</sub>
|
||||
|
||||
Return the closure variables for a given function.
|
||||
|
||||
|
||||
|
||||
**fn**: The function object to inspect.
|
||||
|
||||
|
||||
**Returns**: An object containing the closure variables.
|
||||
|
||||
|
||||
### local_vars(depth) <sub>function</sub>
|
||||
|
||||
Return the local variables for a specific stack frame.
|
||||
|
||||
|
||||
|
||||
**depth**: The stack frame depth to inspect.
|
||||
|
||||
|
||||
**Returns**: An object containing the local variables at the specified depth.
|
||||
|
||||
|
||||
### fn_info(fn) <sub>function</sub>
|
||||
|
||||
Return metadata about a given function.
|
||||
|
||||
|
||||
|
||||
**fn**: The function object to inspect.
|
||||
|
||||
|
||||
**Returns**: An object with metadata about the function.
|
||||
|
||||
|
||||
### backtrace_fns() <sub>function</sub>
|
||||
|
||||
Return an array of functions in the current backtrace.
|
||||
|
||||
|
||||
|
||||
**Returns**: An array of function objects from the call stack.
|
||||
|
||||
|
||||
### dump_obj(obj) <sub>function</sub>
|
||||
|
||||
Return a string representation of a given object.
|
||||
|
||||
|
||||
|
||||
**obj**: The object to dump.
|
||||
|
||||
|
||||
**Returns**: A string describing the object's contents.
|
||||
|
||||
44
docs/api/modules/dmon.md
Normal file
44
docs/api/modules/dmon.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "dmon"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# dmon
|
||||
|
||||
### watch() <sub>function</sub>
|
||||
|
||||
Start watching the root directory, recursively.
|
||||
|
||||
This function begins monitoring the specified directory and its subdirectories recursively for events such as file creation, deletion, modification, or movement. Events are queued and can be retrieved by calling poll.
|
||||
|
||||
:throws: An error if dmon is already watching.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### unwatch() <sub>function</sub>
|
||||
|
||||
Stop watching the currently monitored directory.
|
||||
|
||||
This function halts filesystem monitoring for the directory previously set by watch. It clears the watch state, allowing a new watch to be started.
|
||||
|
||||
:throws: An error if no directory is currently being watched.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### poll(callback) <sub>function</sub>
|
||||
|
||||
Retrieve and process queued filesystem events.
|
||||
|
||||
This function dequeues all pending filesystem events and invokes the provided callback for each one. The callback receives an event object with properties: 'action' (string: "create", "delete", "modify", or "move"), 'root' (string: watched directory), 'file' (string: affected file path), and 'old' (string: previous file path for move events, empty if not applicable).
|
||||
|
||||
|
||||
|
||||
**callback**: A function to call for each event, receiving an event object as its argument.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
43
docs/api/modules/doc.md
Normal file
43
docs/api/modules/doc.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "doc"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# doc
|
||||
|
||||
|
||||
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
|
||||
|
||||
Docstrings are set to the symbol `cell.DOC`
|
||||
|
||||
```js
|
||||
// Suppose we have a module that returns a function
|
||||
function greet(name) { log.console("Hello, " + name) }
|
||||
|
||||
// We can attach a docstring
|
||||
greet.doc = `
|
||||
Greets the user by name.
|
||||
`
|
||||
|
||||
// A single function is a valid return!
|
||||
return greet
|
||||
```
|
||||
|
||||
```js
|
||||
// Another way is to add a docstring object to an object
|
||||
var greet = {
|
||||
hello() { log.console('hello!') }
|
||||
}
|
||||
|
||||
greet[cell.DOC] = {}
|
||||
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
|
||||
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
|
||||
```
|
||||
|
||||
|
||||
**name**: The name of the person to greet.
|
||||
|
||||
|
||||
### writeDocFile(obj, title) <sub>function</sub>
|
||||
|
||||
Return a markdown string for a given obj, with an optional title.
|
||||
95
docs/api/modules/draw2d.md
Normal file
95
docs/api/modules/draw2d.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: "draw2d"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# draw2d
|
||||
|
||||
A collection of retained-mode 2D drawing factories. Each factory creates an object that auto-registers with `film2d` and renders via the compositor. Destroy objects when no longer needed.
|
||||
|
||||
```javascript
|
||||
var draw = use('draw2d')
|
||||
```
|
||||
|
||||
## Factories
|
||||
|
||||
### draw.sprite(props)
|
||||
|
||||
Create a sprite from an image. Auto-registers with `film2d`.
|
||||
|
||||
```javascript
|
||||
var s = draw.sprite({
|
||||
image: "hero.png",
|
||||
pos: {x: 100, y: 200},
|
||||
width: 32, height: 32,
|
||||
plane: 'game', layer: 0
|
||||
})
|
||||
s.destroy() // remove from renderer
|
||||
```
|
||||
|
||||
See `sprite.cm` for full property list: `pos`, `image`, `width`, `height`, `anchor_x`, `anchor_y`, `rotation`, `color`, `opacity`, `tint`, `filter`, `plane`, `layer`, `groups`, `visible`, `flip`, `fit`, `uv`.
|
||||
|
||||
### draw.shape.rect(props) / circle(props) / ellipse(props) / pill(props)
|
||||
|
||||
Create SDF shapes. Supports fill, stroke, rounded corners, dashing, feathering, and texture fill.
|
||||
|
||||
```javascript
|
||||
var box = draw.shape.rect({
|
||||
pos: {x: 50, y: 50}, width: 100, height: 60,
|
||||
fill: {r: 1, g: 0, b: 0, a: 1},
|
||||
stroke: {r: 1, g: 1, b: 1, a: 1}, stroke_thickness: 2,
|
||||
radius: 8,
|
||||
plane: 'game'
|
||||
})
|
||||
|
||||
var ball = draw.shape.circle({
|
||||
pos: {x: 200, y: 200}, radius: 16,
|
||||
fill: {r: 0, g: 1, b: 0, a: 1},
|
||||
plane: 'game', groups: ['glow']
|
||||
})
|
||||
```
|
||||
|
||||
Properties: `shape_type`, `pos`, `width`, `height`, `radius`, `corner_style`, `feather`, `stroke_thickness`, `stroke_align`, `dash_len`, `gap_len`, `dash_offset`, `cap`, `join`, `fill`, `stroke`, `blend`, `opacity`, `fill_tex`, `uv`, `plane`, `layer`, `groups`, `visible`.
|
||||
|
||||
### draw.text(props)
|
||||
|
||||
Create a text label. Updates live when you change `.text`.
|
||||
|
||||
```javascript
|
||||
var label = draw.text({
|
||||
text: "Score: 0",
|
||||
pos: {x: 10, y: 500},
|
||||
font: "fonts/dos", size: 16,
|
||||
color: {r: 1, g: 1, b: 1, a: 1},
|
||||
plane: 'hud'
|
||||
})
|
||||
label.text = "Score: 42" // updates on next frame
|
||||
```
|
||||
|
||||
### draw.tilemap(props)
|
||||
|
||||
Create a tile-based map. See `tilemap2d.cm`.
|
||||
|
||||
### draw.anim(props)
|
||||
|
||||
Create an animated sprite from an aseprite/gif file. Auto-plays if the image has frames.
|
||||
|
||||
```javascript
|
||||
var anim = draw.anim({
|
||||
image: "hero.aseprite",
|
||||
pos: {x: 100, y: 100},
|
||||
plane: 'game'
|
||||
})
|
||||
anim.play("walk") // play named animation
|
||||
anim.stop() // pause
|
||||
anim.resume() // resume
|
||||
anim.update(dt) // advance frame (call from update loop)
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
All draw2d objects register with `film2d` on creation. Call `.destroy()` to unregister and remove from rendering. Set `.visible = false` to hide without destroying.
|
||||
|
||||
## Planes and Groups
|
||||
|
||||
Every drawable has a `plane` (string) and optional `groups` (array of strings). The compositor renders drawables per-plane. Groups route drawables through effects (bloom, mask, etc.).
|
||||
50
docs/api/modules/enet.md
Normal file
50
docs/api/modules/enet.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "enet"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# enet
|
||||
|
||||
### initialize() <sub>function</sub>
|
||||
|
||||
|
||||
Initialize the ENet library. Must be called before using any ENet functionality.
|
||||
Throws an error if initialization fails.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### deinitialize() <sub>function</sub>
|
||||
|
||||
|
||||
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
|
||||
need any ENet functionality.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### create_host(address) <sub>function</sub>
|
||||
|
||||
|
||||
Create an ENet host for either a client-like unbound host or a server bound to a specific
|
||||
address and port:
|
||||
|
||||
- If no argument is provided, creates an unbound "client-like" host with default settings
|
||||
(maximum 32 peers, 2 channels, unlimited bandwidth).
|
||||
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
|
||||
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
|
||||
|
||||
Throws an error if host creation fails for any reason.
|
||||
|
||||
omit to create an unbound client-like host.
|
||||
|
||||
|
||||
**address**: (optional) A string in 'ip:port' format to bind the host (server), or
|
||||
|
||||
|
||||
**Returns**: An ENetHost object.
|
||||
|
||||
30
docs/api/modules/event.md
Normal file
30
docs/api/modules/event.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "event"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# event
|
||||
|
||||
### push_event(event) <sub>function</sub>
|
||||
|
||||
Push a custom user event into SDL's queue, passing a callback function.
|
||||
|
||||
|
||||
|
||||
**event**: A function to call when this event is consumed.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### engine_input(callback) <sub>function</sub>
|
||||
|
||||
Poll all system events (keyboard, mouse, etc.) and call the given function with each event object.
|
||||
|
||||
|
||||
|
||||
**callback**: A function that executes on each event consumed from the poll.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
226
docs/api/modules/geometry.md
Normal file
226
docs/api/modules/geometry.md
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: "geometry"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# geometry
|
||||
|
||||
|
||||
A collection of geometry-related functions for circles, spheres, boxes, polygons,
|
||||
and rectangle utilities. Some functionality is implemented in C and exposed here.
|
||||
|
||||
|
||||
### rect_intersection(a, b) <sub>function</sub>
|
||||
|
||||
|
||||
Return the intersection of two rectangles. The result may be empty if no intersection.
|
||||
|
||||
|
||||
**a**: The first rectangle as {x, y, w, h}.
|
||||
|
||||
**b**: The second rectangle as {x, y, w, h}.
|
||||
|
||||
|
||||
**Returns**: A rectangle that is the intersection of the two. May have zero width/height if no overlap.
|
||||
|
||||
|
||||
### rect_intersects(a, b) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**a**: Rectangle {x,y,w,h}.
|
||||
|
||||
**b**: Rectangle {x,y,w,h}.
|
||||
|
||||
|
||||
**Returns**: A boolean indicating whether the two rectangles overlap.
|
||||
|
||||
|
||||
### rect_expand(a, b) <sub>function</sub>
|
||||
|
||||
|
||||
Merge or combine two rectangles, returning their bounding rectangle.
|
||||
|
||||
|
||||
**a**: Rectangle {x,y,w,h}.
|
||||
|
||||
**b**: Rectangle {x,y,w,h}.
|
||||
|
||||
|
||||
**Returns**: A new rectangle that covers the bounds of both input rectangles.
|
||||
|
||||
|
||||
### rect_inside(inner, outer) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**inner**: A rectangle to test.
|
||||
|
||||
**outer**: A rectangle that may contain 'inner'.
|
||||
|
||||
|
||||
**Returns**: True if 'inner' is completely inside 'outer', otherwise false.
|
||||
|
||||
|
||||
### rect_random(rect) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle {x,y,w,h}.
|
||||
|
||||
|
||||
**Returns**: A random point within the rectangle (uniform distribution).
|
||||
|
||||
|
||||
### cwh2rect(center, wh) <sub>function</sub>
|
||||
|
||||
|
||||
Helper: convert a center point and width/height vector to a rect object.
|
||||
|
||||
|
||||
**center**: A 2D point [cx, cy].
|
||||
|
||||
**wh**: A 2D size [width, height].
|
||||
|
||||
|
||||
**Returns**: A rectangle {x, y, w, h} with x,y set to center and w,h set to the given size.
|
||||
|
||||
|
||||
### rect_point_inside(rect, point) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle {x,y,w,h}.
|
||||
|
||||
**point**: A 2D point [px, py].
|
||||
|
||||
|
||||
**Returns**: True if the point lies inside the rectangle, otherwise false.
|
||||
|
||||
|
||||
### rect_pos(rect) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle {x,y,w,h}.
|
||||
|
||||
|
||||
**Returns**: A 2D vector [x,y] giving the rectangle's position.
|
||||
|
||||
|
||||
### rect_move(rect, offset) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle {x,y,w,h}.
|
||||
|
||||
**offset**: A 2D vector to add to the rectangle's position.
|
||||
|
||||
|
||||
**Returns**: A new rectangle with updated x,y offset.
|
||||
|
||||
|
||||
### box(w, h) <sub>function</sub>
|
||||
|
||||
|
||||
Construct a box centered at the origin with the given width and height. This overrides the box object above.
|
||||
|
||||
|
||||
**w**: The width of the box.
|
||||
|
||||
**h**: The height of the box.
|
||||
|
||||
|
||||
**Returns**: An array of four 2D points representing the corners of a rectangle centered at [0,0].
|
||||
|
||||
|
||||
### sphere <sub>object</sub>
|
||||
|
||||
|
||||
Sphere-related geometry functions:
|
||||
- volume(r): Return the volume of a sphere with radius r.
|
||||
- random(r, theta, phi): Return a random point on or inside a sphere.
|
||||
|
||||
|
||||
### circle <sub>object</sub>
|
||||
|
||||
|
||||
Circle-related geometry functions:
|
||||
- area(r): Return the area of a circle with radius r.
|
||||
- random(r, theta): Return a random 2D point on a circle; uses sphere.random internally and extracts x,z.
|
||||
|
||||
|
||||
### ngon(radius, n) <sub>function</sub>
|
||||
|
||||
|
||||
Generates a regular n-gon by calling geometry.arc with full 360 degrees.
|
||||
|
||||
|
||||
**radius**: The radius of the n-gon from center to each vertex.
|
||||
|
||||
**n**: Number of sides/vertices.
|
||||
|
||||
|
||||
**Returns**: An array of 2D points forming a regular n-gon.
|
||||
|
||||
|
||||
### arc(radius, angle, n, start) <sub>function</sub>
|
||||
|
||||
|
||||
Generate an arc (or partial circle) of n points, each angle spread equally over 'angle' degrees from 'start'.
|
||||
|
||||
|
||||
**radius**: The distance from center to the arc points.
|
||||
|
||||
**angle**: The total angle (in degrees) over which points are generated, capped at 360.
|
||||
|
||||
**n**: Number of segments (if <=1, empty array is returned).
|
||||
|
||||
**start**: Starting angle (in degrees), default 0.
|
||||
|
||||
|
||||
**Returns**: An array of 2D points along the arc.
|
||||
|
||||
|
||||
### corners2points(ll, ur) <sub>function</sub>
|
||||
|
||||
|
||||
Similar to box.points, but calculates differently.
|
||||
|
||||
|
||||
**ll**: Lower-left 2D coordinate.
|
||||
|
||||
**ur**: Upper-right 2D coordinate (relative offset in x,y).
|
||||
|
||||
|
||||
**Returns**: A four-point array of corners [ll, lower-right, upper-right, upper-left].
|
||||
|
||||
|
||||
### sortpointsccw(points) <sub>function</sub>
|
||||
|
||||
|
||||
Sort an array of points in CCW order based on their angles from the centroid.
|
||||
|
||||
|
||||
**points**: An array of 2D points.
|
||||
|
||||
|
||||
**Returns**: A new array of the same points, sorted counterclockwise around their centroid.
|
||||
|
||||
|
||||
### points2cm(points) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**points**: An array of 2D points.
|
||||
|
||||
|
||||
**Returns**: The centroid (average x,y) of the given points.
|
||||
|
||||
283
docs/api/modules/graphics.md
Normal file
283
docs/api/modules/graphics.md
Normal file
@@ -0,0 +1,283 @@
|
||||
---
|
||||
title: "graphics"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# graphics
|
||||
|
||||
|
||||
Provides functionality for loading and managing images, fonts, textures, and sprite meshes.
|
||||
Includes both JavaScript and C-implemented routines for creating geometry buffers, performing
|
||||
rectangle packing, etc.
|
||||
|
||||
|
||||
### make_sprite_mesh(sprites) <sub>function</sub>
|
||||
|
||||
|
||||
:param oldMesh (optional): An existing mesh object to reuse/resize if possible.
|
||||
Given an array of sprites, build a single geometry mesh for rendering them.
|
||||
|
||||
|
||||
**sprites**: An array of sprite objects, each containing .rect (or transform), .src (UV region), .color, etc.
|
||||
|
||||
|
||||
**Returns**: A GPU mesh object with pos, uv, color, and indices buffers for all sprites.
|
||||
|
||||
|
||||
### make_sprite_queue(sprites, camera, pipeline, sort) <sub>function</sub>
|
||||
|
||||
|
||||
Given an array of sprites, optionally sort them, then build a queue of pipeline commands.
|
||||
Each group with a shared image becomes one command.
|
||||
|
||||
|
||||
**sprites**: An array of sprite objects.
|
||||
|
||||
**camera**: (unused in the C code example) Typically a camera or transform for sorting?
|
||||
|
||||
**pipeline**: A pipeline object for rendering.
|
||||
|
||||
**sort**: An integer or boolean for whether to sort sprites; if truthy, sorts by layer & texture.
|
||||
|
||||
|
||||
**Returns**: An array of pipeline commands: geometry with mesh references, grouped by image.
|
||||
|
||||
|
||||
### make_text_buffer(text, rect, angle, color, wrap, font) <sub>function</sub>
|
||||
|
||||
|
||||
Generate a GPU buffer mesh of text quads for rendering with a font, etc.
|
||||
|
||||
|
||||
**text**: The string to render.
|
||||
|
||||
**rect**: A rectangle specifying position and possibly wrapping.
|
||||
|
||||
**angle**: Rotation angle (unused or optional).
|
||||
|
||||
**color**: A color for the text (could be a vec4).
|
||||
|
||||
**wrap**: The width in pixels to wrap text, or 0 for no wrap.
|
||||
|
||||
**font**: A font object created by graphics.make_font or graphics.get_font.
|
||||
|
||||
|
||||
**Returns**: A geometry buffer mesh (pos, uv, color, indices) for rendering text.
|
||||
|
||||
|
||||
### rectpack(width, height, sizes) <sub>function</sub>
|
||||
|
||||
|
||||
Perform a rectangle packing using the stbrp library. Return positions for each rect.
|
||||
|
||||
|
||||
**width**: The width of the area to pack into.
|
||||
|
||||
**height**: The height of the area to pack into.
|
||||
|
||||
**sizes**: An array of [w,h] pairs for the rectangles to pack.
|
||||
|
||||
|
||||
**Returns**: An array of [x,y] coordinates placing each rect, or null if they don't fit.
|
||||
|
||||
|
||||
### make_rtree() <sub>function</sub>
|
||||
|
||||
|
||||
Create a new R-Tree for geometry queries.
|
||||
|
||||
|
||||
**Returns**: An R-Tree object for quickly querying many rectangles or sprite bounds.
|
||||
|
||||
|
||||
### make_texture(data) <sub>function</sub>
|
||||
|
||||
|
||||
Convert raw image bytes into an SDL_Surface object.
|
||||
|
||||
|
||||
**data**: Raw image bytes (PNG, JPG, etc.) as an ArrayBuffer.
|
||||
|
||||
|
||||
**Returns**: An SDL_Surface object representing the decoded image in RAM, for use with GPU or software rendering.
|
||||
|
||||
|
||||
### make_gif(data) <sub>function</sub>
|
||||
|
||||
|
||||
Load a GIF, returning its frames. If it's a single-frame GIF, the result may have .surface only.
|
||||
|
||||
|
||||
**data**: An ArrayBuffer containing GIF data.
|
||||
|
||||
|
||||
**Returns**: An object with frames[], each frame having its own .surface. Some also have a .texture for GPU use.
|
||||
|
||||
|
||||
### make_aseprite(data) <sub>function</sub>
|
||||
|
||||
|
||||
Load an Aseprite/ASE file from an array of bytes, returning frames or animations.
|
||||
|
||||
|
||||
**data**: An ArrayBuffer containing Aseprite (ASE) file data.
|
||||
|
||||
|
||||
**Returns**: An object containing frames or animations, each with .surface. May also have top-level .surface for a single-layer case.
|
||||
|
||||
|
||||
### cull_sprites(sprites, camera) <sub>function</sub>
|
||||
|
||||
|
||||
Filter an array of sprites to only those visible in the provided camera’s view.
|
||||
|
||||
|
||||
**sprites**: An array of sprite objects (each has rect or transform).
|
||||
|
||||
**camera**: A camera or bounding rectangle defining the view area.
|
||||
|
||||
|
||||
**Returns**: A new array of sprites that are visible in the camera's view.
|
||||
|
||||
|
||||
### rects_to_sprites(rects, image) <sub>function</sub>
|
||||
|
||||
|
||||
Convert an array of rect coords into sprite objects referencing a single image.
|
||||
|
||||
|
||||
**rects**: An array of rect coords or objects.
|
||||
|
||||
**image**: An image object (with .texture).
|
||||
|
||||
|
||||
**Returns**: An array of sprite objects referencing the 'image' and each rect for UV or position.
|
||||
|
||||
|
||||
### make_surface(dimensions) <sub>function</sub>
|
||||
|
||||
|
||||
Create a blank surface in RAM.
|
||||
|
||||
|
||||
**dimensions**: The size object {width, height}, or an array [w,h].
|
||||
|
||||
|
||||
**Returns**: A blank RGBA surface with the given dimensions, typically for software rendering or icons.
|
||||
|
||||
|
||||
### make_cursor(opts) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**opts**: An object with {surface, hotx, hoty} or similar.
|
||||
|
||||
|
||||
**Returns**: An SDL_Cursor object referencing the given surface for a custom mouse cursor.
|
||||
|
||||
|
||||
### make_font(data, size) <sub>function</sub>
|
||||
|
||||
|
||||
Load a font from TTF/OTF data at the given size.
|
||||
|
||||
|
||||
**data**: TTF/OTF file data as an ArrayBuffer.
|
||||
|
||||
**size**: Pixel size for rendering glyphs.
|
||||
|
||||
|
||||
**Returns**: A font object with surface, texture, and glyph data, for text rendering with make_text_buffer.
|
||||
|
||||
|
||||
### make_sprite() <sub>function</sub>
|
||||
|
||||
|
||||
Create a new sprite object, storing default properties.
|
||||
|
||||
|
||||
**Returns**: A new sprite object, which typically has .rect, .color, .layer, .image, etc.
|
||||
|
||||
|
||||
### make_line_prim(points, thickness, startCap, endCap, color) <sub>function</sub>
|
||||
|
||||
|
||||
Build a GPU mesh representing a thick polyline from an array of points, using parsl or a similar library under the hood.
|
||||
|
||||
|
||||
**points**: An array of [x,y] points forming the line.
|
||||
|
||||
**thickness**: The thickness (width) of the polyline.
|
||||
|
||||
**startCap**: (Unused) Possibly the type of cap for the start.
|
||||
|
||||
**endCap**: (Unused) Possibly the type of cap for the end.
|
||||
|
||||
**color**: A color to apply to the line.
|
||||
|
||||
|
||||
**Returns**: A geometry mesh object suitable for rendering the line via a pipeline command.
|
||||
|
||||
|
||||
### is_image(obj) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
**obj**: An object to check.
|
||||
|
||||
|
||||
**Returns**: True if 'obj' has a .texture and a .rect property, indicating it's an image object.
|
||||
|
||||
|
||||
### texture(path) <sub>function</sub>
|
||||
|
||||
|
||||
Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, it’s returned directly.
|
||||
|
||||
|
||||
**path**: A string path to an image file or an already-loaded image object.
|
||||
|
||||
|
||||
**Returns**: An image object with {surface, texture, frames?, etc.} depending on the format.
|
||||
|
||||
|
||||
### tex_hotreload(file) <sub>function</sub>
|
||||
|
||||
|
||||
Reload the image for the given file, updating the cached copy in memory and GPU.
|
||||
|
||||
|
||||
**file**: The file path that was changed on disk.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### get_font(path, size) <sub>function</sub>
|
||||
|
||||
|
||||
Load a font from file if not cached, or retrieve from cache if already loaded.
|
||||
|
||||
|
||||
**path**: A string path to a font file, optionally with ".size" appended.
|
||||
|
||||
**size**: Pixel size of the font, if not included in 'path'.
|
||||
|
||||
|
||||
**Returns**: A font object with .surface and .texture for rendering text.
|
||||
|
||||
|
||||
### queue_sprite_mesh(queue) <sub>function</sub>
|
||||
|
||||
|
||||
Builds a single geometry mesh for all sprite-type commands in the queue, storing first_index/num_indices
|
||||
so they can be rendered in one draw call.
|
||||
|
||||
|
||||
**queue**: An array of draw commands, some of which are {type:'sprite'} objects.
|
||||
|
||||
|
||||
**Returns**: An array of references to GPU buffers [pos,uv,color,indices].
|
||||
|
||||
1249
docs/api/modules/imgui.md
Normal file
1249
docs/api/modules/imgui.md
Normal file
File diff suppressed because it is too large
Load Diff
73
docs/api/modules/input.md
Normal file
73
docs/api/modules/input.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "input"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# input
|
||||
|
||||
### mouse_show(show) <sub>function</sub>
|
||||
|
||||
Show or hide the mouse cursor. Pass true to show, false to hide.
|
||||
|
||||
|
||||
|
||||
**show**: Boolean. True to show, false to hide.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### mouse_lock(lock) <sub>function</sub>
|
||||
|
||||
Capture or release the mouse, confining it within the window if locked.
|
||||
|
||||
|
||||
|
||||
**lock**: Boolean. True to lock, false to unlock.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### cursor_set(cursor) <sub>function</sub>
|
||||
|
||||
Set the given cursor (created by os.make_cursor) as the active mouse cursor.
|
||||
|
||||
|
||||
|
||||
**cursor**: The cursor to set.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### keyname(keycode) <sub>function</sub>
|
||||
|
||||
Given a numeric keycode, return the corresponding key name (e.g., from SDL).
|
||||
|
||||
|
||||
|
||||
**keycode**: A numeric SDL keycode.
|
||||
|
||||
|
||||
**Returns**: A string with the key name.
|
||||
|
||||
|
||||
### keymod() <sub>function</sub>
|
||||
|
||||
Return an object describing the current modifier keys, e.g. {shift:true, ctrl:true}.
|
||||
|
||||
|
||||
|
||||
**Returns**: An object with boolean fields for each modifier key.
|
||||
|
||||
|
||||
### mousestate() <sub>function</sub>
|
||||
|
||||
Return an object describing the current mouse state, including x,y coordinates
|
||||
and booleans for pressed buttons (left, middle, right, x1, x2).
|
||||
|
||||
|
||||
|
||||
**Returns**: Object { x, y, left, middle, right, x1, x2 }
|
||||
|
||||
248
docs/api/modules/io.md
Normal file
248
docs/api/modules/io.md
Normal file
@@ -0,0 +1,248 @@
|
||||
---
|
||||
title: "io"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# io
|
||||
|
||||
### rm(path) <sub>function</sub>
|
||||
|
||||
Remove the file or empty directory at the given path.
|
||||
|
||||
|
||||
|
||||
**path**: The file or empty directory to remove. Must be empty if a directory.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### mkdir(path) <sub>function</sub>
|
||||
|
||||
Create a directory at the given path.
|
||||
|
||||
|
||||
|
||||
**path**: The directory path to create.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### stat(path) <sub>function</sub>
|
||||
|
||||
Return an object describing file metadata for the given path. The object includes
|
||||
filesize, modtime, createtime, and accesstime. Throw an error if the path does not exist.
|
||||
|
||||
|
||||
|
||||
**path**: The file or directory to retrieve metadata for.
|
||||
|
||||
|
||||
**Returns**: An object with metadata (filesize, modtime, createtime, accesstime).
|
||||
|
||||
|
||||
### globfs(patterns) <sub>function</sub>
|
||||
|
||||
Return an array of files that do not match any of the provided glob patterns. It
|
||||
recursively enumerates the filesystem within PHYSFS. Each pattern is treated as an
|
||||
"ignore" rule, similar to .gitignore usage.
|
||||
|
||||
|
||||
|
||||
**patterns**: An array of glob patterns to ignore. Any file matching one of these is skipped.
|
||||
|
||||
|
||||
**Returns**: An array of matching file paths.
|
||||
|
||||
|
||||
### match(pattern, string) <sub>function</sub>
|
||||
|
||||
Return boolean indicating whether the given wildcard pattern matches the provided
|
||||
string. Dots must match dots. Case is not ignored.
|
||||
|
||||
Patterns can incorporate:
|
||||
'?' - Matches exactly one character (except leading dots or slashes).
|
||||
'*' - Matches zero or more characters (excluding path separators).
|
||||
'**' - Matches zero or more characters, including path separators.
|
||||
'[abc]' - A bracket expression; matches any single character from the set. Ranges like [a-z], [0-9] also work.
|
||||
'[[:alpha:]]' - POSIX character classes can be used inside brackets.
|
||||
'\' - Backslash escapes the next character.
|
||||
'!' - If placed immediately inside brackets (like [!abc]), it negates the set.
|
||||
|
||||
|
||||
|
||||
**pattern**: The wildcard pattern to compare.
|
||||
|
||||
**string**: The string to test against the wildcard pattern.
|
||||
|
||||
|
||||
**Returns**: True if matched, otherwise false.
|
||||
|
||||
|
||||
### exists(path) <sub>function</sub>
|
||||
|
||||
Return a boolean indicating whether the file or directory at the given path exists.
|
||||
|
||||
|
||||
|
||||
**path**: The file or directory path to check.
|
||||
|
||||
|
||||
**Returns**: True if the path exists, otherwise false.
|
||||
|
||||
|
||||
### mount(archiveOrDir, mountPoint) <sub>function</sub>
|
||||
|
||||
Mount a directory or archive at the specified mount point. An undefined mount
|
||||
point mounts to '/'. Throw on error.
|
||||
|
||||
|
||||
|
||||
**archiveOrDir**: The directory or archive to mount.
|
||||
|
||||
**mountPoint**: The path at which to mount. If omitted or undefined, '/' is used.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### unmount(path) <sub>function</sub>
|
||||
|
||||
Unmount a previously mounted directory or archive. Throw on error.
|
||||
|
||||
|
||||
|
||||
**path**: The directory or archive mount point to unmount.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### slurp(path) <sub>function</sub>
|
||||
|
||||
Read the entire file at the given path as a string. Throw on error.
|
||||
|
||||
|
||||
|
||||
**path**: The file path to read from.
|
||||
|
||||
|
||||
**Returns**: A string with the file’s contents.
|
||||
|
||||
|
||||
### slurpbytes(path) <sub>function</sub>
|
||||
|
||||
Read the entire file at the given path as a raw ArrayBuffer. Throw on error.
|
||||
|
||||
|
||||
|
||||
**path**: The file path to read from.
|
||||
|
||||
|
||||
**Returns**: An ArrayBuffer containing the file’s raw bytes.
|
||||
|
||||
|
||||
### slurpwrite(data, path) <sub>function</sub>
|
||||
|
||||
Write data (string or ArrayBuffer) to the given file path. Overwrite if it exists.
|
||||
Throw on error.
|
||||
|
||||
|
||||
|
||||
**data**: The data to write (string or ArrayBuffer).
|
||||
|
||||
**path**: The file path to write to.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### writepath(path) <sub>function</sub>
|
||||
|
||||
Set the write directory. Subsequent writes will go here by default. Throw on error.
|
||||
|
||||
|
||||
|
||||
**path**: The directory path to set as writable.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### basedir() <sub>function</sub>
|
||||
|
||||
Return the application's base directory (where the executable is located).
|
||||
|
||||
|
||||
|
||||
**Returns**: A string with the base directory path.
|
||||
|
||||
|
||||
### prefdir(org, app) <sub>function</sub>
|
||||
|
||||
Get the user-and-app-specific path where files can be written.
|
||||
|
||||
|
||||
|
||||
**org**: The name of your organization.
|
||||
|
||||
**app**: The name of your application.
|
||||
|
||||
|
||||
**Returns**: A string with the user's directory path.
|
||||
|
||||
|
||||
### realdir(path) <sub>function</sub>
|
||||
|
||||
Return the actual, real directory (on the host filesystem) that contains the given
|
||||
file path. Return undefined if not found.
|
||||
|
||||
|
||||
|
||||
**path**: The file path whose real directory is requested.
|
||||
|
||||
|
||||
**Returns**: A string with the real directory path, or undefined.
|
||||
|
||||
|
||||
### open(path) <sub>function</sub>
|
||||
|
||||
Open a file for writing, returning a file object that can be used for further
|
||||
operations. Throw on error.
|
||||
|
||||
|
||||
|
||||
**path**: The file path to open for writing.
|
||||
|
||||
|
||||
**Returns**: A file object for subsequent write operations.
|
||||
|
||||
|
||||
### searchpath() <sub>function</sub>
|
||||
|
||||
Return an array of all directories in the current paths.
|
||||
|
||||
|
||||
|
||||
**Returns**: An array of directory paths in the search path.
|
||||
|
||||
|
||||
### enumerate(path, recurse) <sub>function</sub>
|
||||
|
||||
Return an array of files within the given directory, optionally recursing into
|
||||
subdirectories.
|
||||
|
||||
|
||||
|
||||
**path**: The directory to list.
|
||||
|
||||
**recurse**: Whether to recursively include subdirectories (true or false).
|
||||
|
||||
|
||||
**Returns**: An array of file (and directory) paths found.
|
||||
|
||||
|
||||
### mount_core() <sub>function</sub>
|
||||
|
||||
### is_directory() <sub>function</sub>
|
||||
180
docs/api/modules/js.md
Normal file
180
docs/api/modules/js.md
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: "js"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# js
|
||||
|
||||
|
||||
Provides functions for introspecting and configuring the QuickJS runtime engine.
|
||||
Includes debug info, memory usage, GC controls, code evaluation, etc.
|
||||
|
||||
|
||||
### cycle_hook(callback) <sub>function</sub>
|
||||
|
||||
|
||||
or undefined to remove the callback.
|
||||
|
||||
Register or remove a hook function that QuickJS calls once per execution cycle. If the callback
|
||||
is set, it receives a single argument (an optional object/value describing the cycle). If callback
|
||||
is undefined, the hook is removed.
|
||||
|
||||
|
||||
**callback**: A function to call each time QuickJS completes a "cycle" (internal VM loop),
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### dump_shapes() <sub>function</sub>
|
||||
|
||||
|
||||
Use this for internal debugging of object shapes.
|
||||
|
||||
|
||||
**Returns**: A debug string describing the internal shape hierarchy used by QuickJS.
|
||||
|
||||
|
||||
### dump_atoms() <sub>function</sub>
|
||||
|
||||
|
||||
known by QuickJS. Helpful for diagnosing memory usage or potential key collisions.
|
||||
|
||||
|
||||
**Returns**: A debug string listing all currently registered atoms (internal property keys/symbols)
|
||||
|
||||
|
||||
### dump_class() <sub>function</sub>
|
||||
|
||||
|
||||
Shows how many objects of each class exist, useful for advanced memory or performance profiling.
|
||||
|
||||
|
||||
**Returns**: A debug string describing the distribution of JS object classes in the QuickJS runtime.
|
||||
|
||||
|
||||
### dump_objects() <sub>function</sub>
|
||||
|
||||
|
||||
useful for debugging memory leaks or object lifetimes.
|
||||
|
||||
|
||||
**Returns**: A debug string listing certain internal QuickJS objects and their references,
|
||||
|
||||
|
||||
### dump_type_overheads() <sub>function</sub>
|
||||
|
||||
|
||||
Displays memory usage breakdown for different internal object types.
|
||||
|
||||
|
||||
**Returns**: A debug string describing the overheads for various JS object types in QuickJS.
|
||||
|
||||
|
||||
### stack_info() <sub>function</sub>
|
||||
|
||||
|
||||
Internal debugging utility to examine call stack details.
|
||||
|
||||
|
||||
**Returns**: An object or string describing the runtime's current stack usage and capacity.
|
||||
|
||||
|
||||
### calc_mem(value) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Compute the approximate size of a single JS value in memory. This is a best-effort estimate.
|
||||
|
||||
|
||||
**value**: A JavaScript value to analyze.
|
||||
|
||||
|
||||
**Returns**: Approximate memory usage (in bytes) of that single value.
|
||||
|
||||
|
||||
### mem() <sub>function</sub>
|
||||
|
||||
|
||||
including total allocated bytes, object counts, and more.
|
||||
|
||||
Retrieve an overview of the runtime’s memory usage.
|
||||
|
||||
|
||||
**Returns**: An object containing a comprehensive snapshot of memory usage for the current QuickJS runtime,
|
||||
|
||||
|
||||
### mem_limit(bytes) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Set the upper memory limit for the QuickJS runtime. Exceeding this limit may cause operations to
|
||||
fail or throw errors.
|
||||
|
||||
|
||||
**bytes**: The maximum memory (in bytes) QuickJS is allowed to use.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### gc_threshold(bytes) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Set the threshold (in bytes) for QuickJS to perform an automatic GC pass when memory usage surpasses it.
|
||||
|
||||
|
||||
**bytes**: The threshold (in bytes) at which the engine triggers automatic garbage collection.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### max_stacksize(bytes) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Set the maximum stack size for QuickJS. If exceeded, the runtime may throw a stack overflow error.
|
||||
|
||||
|
||||
**bytes**: The maximum allowed stack size (in bytes) for QuickJS.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### memstate() <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Gives a quick overview of the memory usage, including malloc size and other allocations.
|
||||
|
||||
|
||||
**Returns**: A simpler memory usage object (malloc sizes, etc.) for the QuickJS runtime.
|
||||
|
||||
|
||||
### gc() <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Force an immediate, full garbage collection pass, reclaiming unreachable memory.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### eval(src, filename) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Execute a string of JavaScript code in the current QuickJS context.
|
||||
|
||||
|
||||
**src**: A string of JavaScript source code to evaluate.
|
||||
|
||||
**filename**: (Optional) A string for the filename or label, used in debugging or stack traces.
|
||||
|
||||
|
||||
**Returns**: The result of evaluating the given source code.
|
||||
|
||||
20
docs/api/modules/json.md
Normal file
20
docs/api/modules/json.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "json"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# json
|
||||
|
||||
### encode(val,space,replacer,whitelist) <sub>function</sub>
|
||||
|
||||
Produce a JSON text from a Javascript object. If a record value, at any level, contains a json() method, it will be called, and the value it returns (usually a simpler record) will be JSONified.
|
||||
|
||||
If the record does not have a json() method, and if whitelist is a record, then only the keys that are associated with true in the whitelist are included.
|
||||
|
||||
If the space input is true, then line breaks and extra whitespace will be included in the text.
|
||||
|
||||
### decode(text,reviver) <sub>function</sub>
|
||||
|
||||
The text text is parsed, and the resulting value (usually a record or an array) is returned.
|
||||
|
||||
The optional reviver input is a method that will be called for every key and value at every level of the result. Each value will be replaced by the result of the reviver function. This can be used to reform data-only records into method-bearing records, or to transform date strings into seconds.
|
||||
8
docs/api/modules/loop.md
Normal file
8
docs/api/modules/loop.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "loop"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# loop
|
||||
|
||||
### step() <sub>function</sub>
|
||||
120
docs/api/modules/math.md
Normal file
120
docs/api/modules/math.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: "math"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# math
|
||||
|
||||
### dot() <sub>function</sub>
|
||||
|
||||
Compute the dot product between two numeric arrays, returning a scalar. Extra elements are ignored.
|
||||
|
||||
### project() <sub>function</sub>
|
||||
|
||||
Project one vector onto another, returning a new array of the same dimension.
|
||||
|
||||
### rotate() <sub>function</sub>
|
||||
|
||||
Rotate a 2D point (or array of length 2) by the given angle (in turns) around an optional pivot.
|
||||
|
||||
### midpoint() <sub>function</sub>
|
||||
|
||||
Compute the midpoint of two arrays of numbers. Only the first two entries are used if 2D is intended.
|
||||
|
||||
### reflect() <sub>function</sub>
|
||||
|
||||
Reflect a vector across a plane normal. Both arguments must be numeric arrays.
|
||||
|
||||
### distance() <sub>function</sub>
|
||||
|
||||
Compute the Euclidean distance between two numeric arrays of matching length.
|
||||
|
||||
### direction() <sub>function</sub>
|
||||
|
||||
Compute the normalized direction vector from the first array to the second.
|
||||
|
||||
### angle() <sub>function</sub>
|
||||
|
||||
Given a 2D vector, return its angle from the X-axis in radians or some chosen units.
|
||||
|
||||
### norm() <sub>function</sub>
|
||||
|
||||
Return a normalized copy of the given numeric array. For 2D/3D/4D or arbitrary length.
|
||||
|
||||
### angle_between() <sub>function</sub>
|
||||
|
||||
Compute the angle between two vectors (2D/3D/4D).
|
||||
|
||||
### lerp() <sub>function</sub>
|
||||
|
||||
Linear interpolation between two numbers: lerp(a, b, t).
|
||||
|
||||
### gcd() <sub>function</sub>
|
||||
|
||||
Compute the greatest common divisor of two integers.
|
||||
|
||||
### lcm() <sub>function</sub>
|
||||
|
||||
Compute the least common multiple of two integers.
|
||||
|
||||
### clamp() <sub>function</sub>
|
||||
|
||||
Clamp a number between low and high. clamp(value, low, high).
|
||||
|
||||
### angledist() <sub>function</sub>
|
||||
|
||||
Compute the signed distance between two angles in 'turn' units, e.g. 0..1 range.
|
||||
|
||||
### jitter() <sub>function</sub>
|
||||
|
||||
Apply a random +/- percentage noise to a number. Example: jitter(100, 0.05) -> ~95..105.
|
||||
|
||||
### mean() <sub>function</sub>
|
||||
|
||||
Compute the arithmetic mean of an array of numbers.
|
||||
|
||||
### sum() <sub>function</sub>
|
||||
|
||||
Sum all elements of an array of numbers.
|
||||
|
||||
### sigma() <sub>function</sub>
|
||||
|
||||
Compute standard deviation of an array of numbers.
|
||||
|
||||
### median() <sub>function</sub>
|
||||
|
||||
Compute the median of an array of numbers.
|
||||
|
||||
### length() <sub>function</sub>
|
||||
|
||||
Return the length of a vector (i.e. sqrt of sum of squares).
|
||||
|
||||
### from_to() <sub>function</sub>
|
||||
|
||||
Return an array of points from a start to an end, spaced out by a certain distance.
|
||||
|
||||
### rand() <sub>function</sub>
|
||||
|
||||
Return a random float in [0,1).
|
||||
|
||||
### randi() <sub>function</sub>
|
||||
|
||||
Return a random 32-bit integer.
|
||||
|
||||
### srand() <sub>function</sub>
|
||||
|
||||
Seed the random number generator with the given integer, or with current time if none.
|
||||
|
||||
### TAU <sub>number</sub>
|
||||
|
||||
### deg2rad(deg) <sub>function</sub>
|
||||
|
||||
### rad2deg(rad) <sub>function</sub>
|
||||
|
||||
### turn2rad(x) <sub>function</sub>
|
||||
|
||||
### rad2turn(x) <sub>function</sub>
|
||||
|
||||
### turn2deg(x) <sub>function</sub>
|
||||
|
||||
### deg2turn(x) <sub>function</sub>
|
||||
32
docs/api/modules/miniz.md
Normal file
32
docs/api/modules/miniz.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "miniz"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# miniz
|
||||
|
||||
### read(data) <sub>function</sub>
|
||||
|
||||
Create a zip reader from the given ArrayBuffer containing an entire ZIP archive.
|
||||
Return undefined if the data is invalid.
|
||||
|
||||
|
||||
|
||||
**data**: An ArrayBuffer with the entire ZIP file.
|
||||
|
||||
|
||||
**Returns**: A 'zip reader' object with methods for reading from the archive (mod, exists, slurp).
|
||||
|
||||
|
||||
### write(path) <sub>function</sub>
|
||||
|
||||
Create a zip writer that writes to the specified file path. Overwrites the file if
|
||||
it already exists. Return undefined on error.
|
||||
|
||||
|
||||
|
||||
**path**: The file path where the ZIP archive will be written.
|
||||
|
||||
|
||||
**Returns**: A 'zip writer' object with methods for adding files to the archive (add_file).
|
||||
|
||||
35
docs/api/modules/nota.md
Normal file
35
docs/api/modules/nota.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: "nota"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# nota
|
||||
|
||||
### encode(value) <sub>function</sub>
|
||||
|
||||
Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
|
||||
|
||||
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
|
||||
|
||||
:throws: An error if no argument is provided.
|
||||
|
||||
|
||||
**value**: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
|
||||
|
||||
|
||||
**Returns**: An ArrayBuffer containing the NOTA-encoded data.
|
||||
|
||||
|
||||
### decode(buffer) <sub>function</sub>
|
||||
|
||||
Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
|
||||
|
||||
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
|
||||
|
||||
|
||||
|
||||
**buffer**: An ArrayBuffer containing NOTA-encoded data to decode.
|
||||
|
||||
|
||||
**Returns**: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
|
||||
|
||||
106
docs/api/modules/os.md
Normal file
106
docs/api/modules/os.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: "os"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# os
|
||||
|
||||
### make_transform() <sub>function</sub>
|
||||
|
||||
Create a new transform object that can be used for 2D/3D positioning, scaling, and rotation.
|
||||
|
||||
### clean_transforms() <sub>function</sub>
|
||||
|
||||
Force an update on all transforms to remove dangling references or perform house-keeping.
|
||||
|
||||
### platform() <sub>function</sub>
|
||||
|
||||
Return a string with the underlying platform name, like 'Windows', 'Linux', or 'macOS'.
|
||||
|
||||
### arch() <sub>function</sub>
|
||||
|
||||
Return the CPU architecture string for this system (e.g. 'x64', 'arm64').
|
||||
|
||||
### totalmem() <sub>function</sub>
|
||||
|
||||
Return the total system RAM in bytes.
|
||||
|
||||
### freemem() <sub>function</sub>
|
||||
|
||||
Return the amount of free system RAM in bytes, if known.
|
||||
|
||||
### hostname() <sub>function</sub>
|
||||
|
||||
Return the system's hostname, or an empty string if not available.
|
||||
|
||||
### version() <sub>function</sub>
|
||||
|
||||
Return the OS or kernel version string, if the platform provides it.
|
||||
|
||||
### kill() <sub>function</sub>
|
||||
|
||||
Send a signal (e.g., 'SIGINT', 'SIGTERM', etc.) to the current process.
|
||||
|
||||
### exit() <sub>function</sub>
|
||||
|
||||
Exit the application with the specified exit code.
|
||||
|
||||
### now() <sub>function</sub>
|
||||
|
||||
Return current time (in seconds as a float) with high resolution.
|
||||
|
||||
### openurl() <sub>function</sub>
|
||||
|
||||
Open the provided URL in the default web browser, if possible.
|
||||
|
||||
### make_timer() <sub>function</sub>
|
||||
|
||||
Create a new timer object that will call a specified function after a certain delay.
|
||||
|
||||
### update_timers() <sub>function</sub>
|
||||
|
||||
Advance all timers by the provided time delta (in seconds).
|
||||
|
||||
### sleep() <sub>function</sub>
|
||||
|
||||
Block execution for the specified number of seconds.
|
||||
|
||||
### battery_pct() <sub>function</sub>
|
||||
|
||||
Return the battery level (percentage) or negative if unknown.
|
||||
|
||||
### battery_voltage() <sub>function</sub>
|
||||
|
||||
Return the current battery voltage in volts, if available.
|
||||
|
||||
### battery_seconds() <sub>function</sub>
|
||||
|
||||
Return the estimated remaining battery time in seconds, or negative if unknown.
|
||||
|
||||
### power_state() <sub>function</sub>
|
||||
|
||||
Return a string describing power status: 'on battery', 'charging', 'charged', etc.
|
||||
|
||||
### on() <sub>function</sub>
|
||||
|
||||
Register a global callback for certain engine-wide or system-level events.
|
||||
|
||||
### rt_info() <sub>function</sub>
|
||||
|
||||
Return internal QuickJS runtime info, such as object counts.
|
||||
|
||||
### rusage() <sub>function</sub>
|
||||
|
||||
Return resource usage stats for this process, if the platform supports it.
|
||||
|
||||
### mallinfo() <sub>function</sub>
|
||||
|
||||
Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms.
|
||||
|
||||
### env() <sub>function</sub>
|
||||
|
||||
Fetch the value of a given environment variable, or undefined if it doesn't exist.
|
||||
|
||||
### system() <sub>function</sub>
|
||||
|
||||
Execute a shell command using the system() call. Returns the command's exit code.
|
||||
49
docs/api/modules/packer.md
Normal file
49
docs/api/modules/packer.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: "packer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# packer
|
||||
|
||||
### getAllFiles(dir) <sub>function</sub>
|
||||
|
||||
|
||||
Return a list of all files in the given directory that are not matched by .prosperonignore,
|
||||
skipping directories.
|
||||
|
||||
|
||||
|
||||
**dir**: The directory to search.
|
||||
|
||||
|
||||
**Returns**: An array of file paths found.
|
||||
|
||||
|
||||
### gatherStats(filePaths) <sub>function</sub>
|
||||
|
||||
|
||||
Analyze a list of files and categorize them as modules, programs, images, or other.
|
||||
|
||||
|
||||
|
||||
**filePaths**: An array of file paths to analyze.
|
||||
|
||||
|
||||
**Returns**: An object { modules, programs, images, other, total } with counts.
|
||||
|
||||
|
||||
### pack(dir, outPath) <sub>function</sub>
|
||||
|
||||
|
||||
Create a ZIP archive of all files (skipping those matched by .prosperonignore) in the
|
||||
specified directory and write it to outPath. This uses the miniz module.
|
||||
|
||||
|
||||
|
||||
**dir**: The directory to zip.
|
||||
|
||||
**outPath**: The path (including filename) for the resulting ZIP file.
|
||||
|
||||
|
||||
**Returns**: None (synchronous). Throws an Error if the directory does not exist.
|
||||
|
||||
101
docs/api/modules/render.md
Normal file
101
docs/api/modules/render.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "render"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# render
|
||||
|
||||
### _main <sub>object</sub>
|
||||
|
||||
A handle for low-level GPU operations via SDL GPU. Freed on GC.
|
||||
|
||||
|
||||
### device <sub>object</sub>
|
||||
|
||||
### stencil_writer(...args) <sub>function</sub>
|
||||
|
||||
### fillmask(ref) <sub>function</sub>
|
||||
|
||||
Draw a fullscreen shape using a 'screenfill' shader to populate the stencil buffer with a given reference.
|
||||
|
||||
|
||||
|
||||
**ref**: The stencil reference value to write.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### mask(image, pos, scale, rotation, ref) <sub>function</sub>
|
||||
|
||||
Draw an image to the stencil buffer, marking its area with a specified reference value.
|
||||
|
||||
|
||||
|
||||
**image**: A texture or string path (which is converted to a texture).
|
||||
|
||||
**pos**: The translation (x, y) for the image placement.
|
||||
|
||||
**scale**: Optional scaling applied to the texture.
|
||||
|
||||
**rotation**: Optional rotation in radians (unused by default).
|
||||
|
||||
**ref**: The stencil reference value to write.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### viewport(rect) <sub>function</sub>
|
||||
|
||||
Set the GPU viewport to the specified rectangle.
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle [x, y, width, height].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### scissor(rect) <sub>function</sub>
|
||||
|
||||
Set the GPU scissor region to the specified rectangle (alias of render.viewport).
|
||||
|
||||
|
||||
|
||||
**rect**: A rectangle [x, y, width, height].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### queue(cmd) <sub>function</sub>
|
||||
|
||||
Enqueue one or more draw commands. These commands are batched until render_camera is called.
|
||||
|
||||
|
||||
|
||||
**cmd**: Either a single command object or an array of command objects.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### setup_draw() <sub>function</sub>
|
||||
|
||||
Switch the current queue to the primary scene render queue, then invoke 'prosperon.draw' if defined.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### setup_hud() <sub>function</sub>
|
||||
|
||||
Switch the current queue to the HUD render queue, then invoke 'prosperon.hud' if defined.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
73
docs/api/modules/resources.md
Normal file
73
docs/api/modules/resources.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "resources"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# resources
|
||||
|
||||
### scripts <sub>object</sub>
|
||||
|
||||
### images <sub>object</sub>
|
||||
|
||||
### sounds <sub>object</sub>
|
||||
|
||||
### fonts <sub>object</sub>
|
||||
|
||||
### lib <sub>object</sub>
|
||||
|
||||
### canonical(file) <sub>function</sub>
|
||||
|
||||
### find_image(...args) <sub>function</sub>
|
||||
|
||||
### find_sound(...args) <sub>function</sub>
|
||||
|
||||
### find_script(...args) <sub>function</sub>
|
||||
|
||||
### find_font(...args) <sub>function</sub>
|
||||
|
||||
### getAllFiles(dir) <sub>function</sub>
|
||||
|
||||
|
||||
Return a list of recognized files in the given directory that are not matched by
|
||||
.prosperonignore, skipping directories. Recognized extensions include scripts,
|
||||
images, sounds, fonts, and libs.
|
||||
|
||||
|
||||
|
||||
**dir**: The directory to search.
|
||||
|
||||
|
||||
**Returns**: An array of recognized file paths.
|
||||
|
||||
|
||||
### gatherStats(filePaths) <sub>function</sub>
|
||||
|
||||
|
||||
Analyze a list of recognized files and categorize them by scripts, images, sounds,
|
||||
fonts, libs, or other. Return a stats object with these counts and the total.
|
||||
|
||||
|
||||
|
||||
**filePaths**: An array of file paths to analyze.
|
||||
|
||||
|
||||
**Returns**: { scripts, images, sounds, fonts, lib, other, total }
|
||||
|
||||
|
||||
### pack(dir, outPath) <sub>function</sub>
|
||||
|
||||
|
||||
Create a ZIP archive of all recognized files (skipping those matched by .prosperonignore)
|
||||
in the specified directory and write it to outPath. Recognized extensions are scripts,
|
||||
images, sounds, fonts, or libs.
|
||||
|
||||
:raises Error: If the directory does not exist.
|
||||
|
||||
|
||||
**dir**: The directory to zip.
|
||||
|
||||
**outPath**: The path (including filename) for the resulting ZIP file.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
16
docs/api/modules/sound.md
Normal file
16
docs/api/modules/sound.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "sound"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# sound
|
||||
|
||||
### undefined <sub>string</sub>
|
||||
|
||||
### pcm(file) <sub>function</sub>
|
||||
|
||||
### play(file) <sub>function</sub>
|
||||
|
||||
### cry(file) <sub>function</sub>
|
||||
|
||||
### music(file, fade = 0.5) <sub>function</sub>
|
||||
14
docs/api/modules/spline.md
Normal file
14
docs/api/modules/spline.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "spline"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# spline
|
||||
|
||||
### catmull() <sub>function</sub>
|
||||
|
||||
Perform Catmull-Rom spline sampling on an array of 2D points, returning an array of samples.
|
||||
|
||||
### bezier() <sub>function</sub>
|
||||
|
||||
Perform a Bezier spline (or catmull) sampling on 2D points, returning an array of sampled points.
|
||||
108
docs/api/modules/time.md
Normal file
108
docs/api/modules/time.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: "time"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# time
|
||||
|
||||
The main time object, handling date/time utilities in earth-seconds.
|
||||
|
||||
### now() <sub>function</sub>
|
||||
|
||||
Return the current system time in seconds (implemented in C extension).
|
||||
|
||||
### computer_dst() <sub>function</sub>
|
||||
|
||||
Return true if local system time is currently in DST (implemented in C extension).
|
||||
|
||||
### computer_zone() <sub>function</sub>
|
||||
|
||||
Return local time zone offset from UTC in hours (implemented in C extension).
|
||||
|
||||
### second <sub>number</sub>
|
||||
|
||||
Number of seconds in a (real) second (always 1).
|
||||
|
||||
### minute <sub>number</sub>
|
||||
|
||||
Number of seconds in a minute (60).
|
||||
|
||||
### hour <sub>number</sub>
|
||||
|
||||
Number of seconds in an hour (3600).
|
||||
|
||||
### day <sub>number</sub>
|
||||
|
||||
Number of seconds in a day (86400).
|
||||
|
||||
### week <sub>number</sub>
|
||||
|
||||
Number of seconds in a week (604800).
|
||||
|
||||
### weekdays <sub>object</sub>
|
||||
|
||||
Names of the days of the week, Sunday through Saturday.
|
||||
|
||||
### monthstr <sub>object</sub>
|
||||
|
||||
Full names of the months of the year, January through December.
|
||||
|
||||
### epoch <sub>number</sub>
|
||||
|
||||
Base epoch year, from which day 0 is calculated (default 1970).
|
||||
|
||||
### hour2minute() <sub>function</sub>
|
||||
|
||||
Return the ratio of hour to minute in seconds, e.g. 3600 / 60 => 60.
|
||||
|
||||
### day2hour() <sub>function</sub>
|
||||
|
||||
Return the ratio of day to hour in seconds, e.g. 86400 / 3600 => 24.
|
||||
|
||||
### minute2second() <sub>function</sub>
|
||||
|
||||
Return the ratio of minute to second in seconds, e.g. 60 / 1 => 60.
|
||||
|
||||
### week2day() <sub>function</sub>
|
||||
|
||||
Return the ratio of week to day in seconds, e.g. 604800 / 86400 => 7.
|
||||
|
||||
### strparse <sub>object</sub>
|
||||
|
||||
Mapping of format tokens (yyyy, mm, dd, etc.) to time fields (year, month, day...).
|
||||
|
||||
### isleap(year) <sub>function</sub>
|
||||
|
||||
Return true if a given year is leap, based on whether it has 366 days.
|
||||
|
||||
### yearsize(y) <sub>function</sub>
|
||||
|
||||
Given a year, return 365 or 366 depending on leap-year rules.
|
||||
|
||||
### timecode(t, fps = 24) <sub>function</sub>
|
||||
|
||||
Convert seconds into a "S:frames" timecode string, with optional FPS (default 24).
|
||||
|
||||
### monthdays <sub>object</sub>
|
||||
|
||||
An array of days in each month for a non-leap year.
|
||||
|
||||
### zones <sub>object</sub>
|
||||
|
||||
Table of recognized time zone abbreviations, with offsets (e.g., "-12" -> "IDLW").
|
||||
|
||||
### record(num, zone = this.computer_zone() <sub>function</sub>
|
||||
|
||||
Convert a timestamp (in seconds) into a record with fields like day, month, year, etc.
|
||||
|
||||
### number(rec) <sub>function</sub>
|
||||
|
||||
Convert a record back into a numeric timestamp (seconds).
|
||||
|
||||
### fmt <sub>string</sub>
|
||||
|
||||
Default format string for time.text(), containing tokens like 'yyyy', 'dd', 'hh', etc.
|
||||
|
||||
### text(num, fmt = this.fmt, zone) <sub>function</sub>
|
||||
|
||||
Format a numeric or record time into a string using a format pattern, e.g. 'hh:nn:ss'.
|
||||
63
docs/api/modules/tween.md
Normal file
63
docs/api/modules/tween.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "tween"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# tween
|
||||
|
||||
### Tween <sub>object</sub>
|
||||
|
||||
|
||||
An object providing methods to create and control tweens with additional features
|
||||
like looping, custom easing, multiple stages, etc.
|
||||
|
||||
Properties:
|
||||
- default: A template object with loop/time/ease/whole/cb properties.
|
||||
Methods:
|
||||
- start(obj, target, tvals, options): Create a tween over multiple target values.
|
||||
- make: Alias of start.
|
||||
|
||||
|
||||
### Ease <sub>object</sub>
|
||||
|
||||
|
||||
This object provides multiple easing functions that remap a 0..1 input to produce
|
||||
a smoothed or non-linear output. They can be used standalone or inside tweens.
|
||||
|
||||
Available functions:
|
||||
- linear(t)
|
||||
- in(t), out(t), inout(t)
|
||||
- quad.in, quad.out, quad.inout
|
||||
- cubic.in, cubic.out, cubic.inout
|
||||
- quart.in, quart.out, quart.inout
|
||||
- quint.in, quint.out, quint.inout
|
||||
- expo.in, expo.out, expo.inout
|
||||
- bounce.in, bounce.out, bounce.inout
|
||||
- sine.in, sine.out, sine.inout
|
||||
- elastic.in, elastic.out, elastic.inout
|
||||
|
||||
All easing functions expect t in [0..1] and return a remapped value in [0..1].
|
||||
|
||||
|
||||
### tween(from, to, time, fn, cb) <sub>function</sub>
|
||||
|
||||
|
||||
|
||||
Creates a simple tween that linearly interpolates from "from" to "to" over "time"
|
||||
and calls "fn" with each interpolated value. Once finished, "fn" is called with "to",
|
||||
then "cb" is invoked if provided, and the tween is cleaned up.
|
||||
|
||||
|
||||
**from**: The starting object or value to interpolate from.
|
||||
|
||||
**to**: The ending object or value to interpolate to.
|
||||
|
||||
**time**: The total duration of the tween in milliseconds or some time unit.
|
||||
|
||||
**fn**: A callback function that receives the interpolated value at each update.
|
||||
|
||||
**cb**: (Optional) A callback invoked once the tween completes.
|
||||
|
||||
|
||||
**Returns**: A function that, when called, cleans up and stops the tween.
|
||||
|
||||
197
docs/api/modules/util.md
Normal file
197
docs/api/modules/util.md
Normal file
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: "util"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# util
|
||||
|
||||
|
||||
A collection of general-purpose utility functions for object manipulation, merging,
|
||||
deep copying, safe property access, etc.
|
||||
|
||||
|
||||
### guid() <sub>function</sub>
|
||||
|
||||
|
||||
Return a random 32-character hexadecimal UUID-like string (not guaranteed RFC4122-compliant).
|
||||
|
||||
|
||||
**Returns**: A random 32-character string (hex).
|
||||
|
||||
|
||||
### insertion_sort(arr, cmp) <sub>function</sub>
|
||||
|
||||
|
||||
In-place insertion sort of an array using cmp(a,b)->Number for ordering.
|
||||
|
||||
|
||||
**arr**: The array to be sorted in-place.
|
||||
|
||||
**cmp**: Comparison function cmp(a,b)->Number.
|
||||
|
||||
|
||||
**Returns**: The same array, sorted in-place.
|
||||
|
||||
|
||||
### deepfreeze(obj) <sub>function</sub>
|
||||
|
||||
|
||||
Recursively freeze an object and all of its nested objects so they cannot be modified.
|
||||
|
||||
|
||||
**obj**: The object to recursively freeze.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### dainty_assign(target, source) <sub>function</sub>
|
||||
|
||||
|
||||
Copy non-function properties from source into matching keys of target without overwriting
|
||||
keys that don't exist in target. Arrays are deep-copied, and objects are recursively assigned.
|
||||
|
||||
|
||||
**target**: The target object whose keys may be updated.
|
||||
|
||||
**source**: The source object containing new values.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### get(obj, path, defValue) <sub>function</sub>
|
||||
|
||||
|
||||
Safely retrieve a nested property from obj at path (array or dot-string).
|
||||
Returns defValue if the property is undefined.
|
||||
|
||||
|
||||
**obj**: The object to traverse.
|
||||
|
||||
**path**: A string like "a.b.c" or an array of path segments.
|
||||
|
||||
**defValue**: The default value if the property is undefined.
|
||||
|
||||
|
||||
**Returns**: The nested property or defValue.
|
||||
|
||||
|
||||
### isEmpty(o) <sub>function</sub>
|
||||
|
||||
|
||||
Return true if the object has no own properties, otherwise false.
|
||||
|
||||
|
||||
**o**: The object to check.
|
||||
|
||||
|
||||
**Returns**: Boolean indicating if the object is empty.
|
||||
|
||||
|
||||
### dig(obj, path, def) <sub>function</sub>
|
||||
|
||||
|
||||
Ensure a nested path of objects exists inside obj; create objects if missing, and set
|
||||
the final path component to def.
|
||||
|
||||
|
||||
**obj**: The root object to modify.
|
||||
|
||||
**path**: A dot-string specifying nested objects to create.
|
||||
|
||||
**def**: The value to store in the final path component, default {}.
|
||||
|
||||
|
||||
**Returns**: The assigned final value.
|
||||
|
||||
|
||||
### access(obj, name) <sub>function</sub>
|
||||
|
||||
|
||||
Traverse obj by dot-separated path name, returning the final value or undefined
|
||||
if any step is missing.
|
||||
|
||||
|
||||
**obj**: The object to traverse.
|
||||
|
||||
**name**: A dot-string path (e.g. "foo.bar.baz").
|
||||
|
||||
|
||||
**Returns**: The value at that path, or undefined if missing.
|
||||
|
||||
|
||||
### mergekey(o1, o2, k) <sub>function</sub>
|
||||
|
||||
|
||||
Helper for merge, updating key k from o2 into o1. Arrays are deep-copied and objects are
|
||||
recursively merged.
|
||||
|
||||
|
||||
**o1**: The target object.
|
||||
|
||||
**o2**: The source object.
|
||||
|
||||
**k**: The key to merge.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### merge(target, objs) <sub>function</sub>
|
||||
|
||||
|
||||
Merge all passed objects into target, copying or merging each key as needed.
|
||||
Arrays are deep-copied, objects are recursively merged, etc.
|
||||
|
||||
|
||||
**target**: The target object.
|
||||
|
||||
**objs**: One or more objects to merge into target.
|
||||
|
||||
|
||||
**Returns**: The updated target object.
|
||||
|
||||
|
||||
### copy(proto, objs) <sub>function</sub>
|
||||
|
||||
|
||||
Create a new object with proto as its prototype, then mix in additional objects’ properties.
|
||||
|
||||
|
||||
**proto**: The prototype object for the new object.
|
||||
|
||||
**objs**: One or more objects whose properties will be mixed in.
|
||||
|
||||
|
||||
**Returns**: The newly created object.
|
||||
|
||||
|
||||
### obj_lerp(a, b, t) <sub>function</sub>
|
||||
|
||||
|
||||
Linearly interpolate between two objects a and b by factor t, assuming each property
|
||||
supports .lerp().
|
||||
|
||||
|
||||
**a**: The start object (its properties must have .lerp()).
|
||||
|
||||
**b**: The end object (matching properties).
|
||||
|
||||
**t**: Interpolation factor (0..1).
|
||||
|
||||
|
||||
**Returns**: A new object with interpolated properties.
|
||||
|
||||
|
||||
### normalizeSpacing(spacing) <sub>function</sub>
|
||||
|
||||
|
||||
Normalize any spacing input into a {l, r, t, b} object.
|
||||
|
||||
|
||||
**spacing**: A number, an array of length 2 or 4, or an object with l/r/t/b.
|
||||
|
||||
|
||||
**Returns**: An object {l, r, t, b}.
|
||||
|
||||
10
docs/api/modules/video.md
Normal file
10
docs/api/modules/video.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "video"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# video
|
||||
|
||||
### make_video() <sub>function</sub>
|
||||
|
||||
Decode a video file (MPEG, etc.) from an ArrayBuffer, returning a datastream object.
|
||||
40
docs/api/prosperon.md
Normal file
40
docs/api/prosperon.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "prosperon"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# prosperon
|
||||
|
||||
### c_types <sub>object</sub>
|
||||
|
||||
### argv <sub>object</sub>
|
||||
|
||||
### version <sub>string</sub>
|
||||
|
||||
### revision <sub>string</sub>
|
||||
|
||||
### engine_start() <sub>function</sub>
|
||||
|
||||
### DOC <sub>symbol</sub>
|
||||
|
||||
### on(type, callback) <sub>function</sub>
|
||||
|
||||
### dispatch(type, data) <sub>function</sub>
|
||||
|
||||
### PATH <sub>object</sub>
|
||||
|
||||
### appupdate(...args) <sub>function</sub>
|
||||
|
||||
### update(...args) <sub>function</sub>
|
||||
|
||||
### physupdate(...args) <sub>function</sub>
|
||||
|
||||
### gui(...args) <sub>function</sub>
|
||||
|
||||
### hud(...args) <sub>function</sub>
|
||||
|
||||
### draw(...args) <sub>function</sub>
|
||||
|
||||
### imgui(...args) <sub>function</sub>
|
||||
|
||||
### app(...args) <sub>function</sub>
|
||||
60
docs/api/types/PHYSFS_File.md
Normal file
60
docs/api/types/PHYSFS_File.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "PHYSFS_File"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# PHYSFS_File
|
||||
|
||||
A file handle opened via PhysFS for writing or reading. Freed automatically when references go away.
|
||||
|
||||
|
||||
### close() <sub>function</sub>
|
||||
|
||||
Close this file handle. Throws on error.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### write(data) <sub>function</sub>
|
||||
|
||||
Write data (string or ArrayBuffer) to the file. Throws on error.
|
||||
|
||||
|
||||
|
||||
**data**: The data to write (string or ArrayBuffer).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### buffer(size) <sub>function</sub>
|
||||
|
||||
Enable an internal write buffer of the given size on this file.
|
||||
|
||||
|
||||
|
||||
**size**: Size in bytes of the buffer.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### tell() <sub>function</sub>
|
||||
|
||||
Return the current position in the file.
|
||||
|
||||
|
||||
|
||||
**Returns**: A numeric offset.
|
||||
|
||||
|
||||
### eof() <sub>function</sub>
|
||||
|
||||
Return whether the file pointer is at end-of-file.
|
||||
|
||||
|
||||
|
||||
**Returns**: True if at EOF, false otherwise.
|
||||
|
||||
32
docs/api/types/SDL_Camera.md
Normal file
32
docs/api/types/SDL_Camera.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "SDL_Camera"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Camera
|
||||
|
||||
A handle to a physical camera device. Freed when references drop or camera is closed.
|
||||
|
||||
|
||||
### frame() <sub>function</sub>
|
||||
|
||||
Acquire the latest camera frame (as an SDL_Surface). Returns undefined if no
|
||||
new frame is available yet. Throws on error.
|
||||
|
||||
|
||||
|
||||
**Returns**: SDL_Surface or undefined.
|
||||
|
||||
|
||||
### release_frame(surface) <sub>function</sub>
|
||||
|
||||
Release a frame surface previously acquired via camera.frame(). Must be
|
||||
done for each acquired frame.
|
||||
|
||||
|
||||
|
||||
**surface**: The surface to release.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
9
docs/api/types/SDL_Cursor.md
Normal file
9
docs/api/types/SDL_Cursor.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "SDL_Cursor"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Cursor
|
||||
|
||||
An SDL cursor handle. Freed automatically on GC. No direct methods.
|
||||
|
||||
8
docs/api/types/SDL_GPUBuffer.md
Normal file
8
docs/api/types/SDL_GPUBuffer.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "SDL_GPUBuffer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUBuffer
|
||||
|
||||
### name() <sub>function</sub>
|
||||
236
docs/api/types/SDL_GPUCommandBuffer.md
Normal file
236
docs/api/types/SDL_GPUCommandBuffer.md
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
title: "SDL_GPUCommandBuffer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUCommandBuffer
|
||||
|
||||
A command buffer that accumulates rendering, copy, and compute operations. Freed after submission or GC.
|
||||
|
||||
|
||||
### render_pass(passDesc) <sub>function</sub>
|
||||
|
||||
Begin a render pass with color/depth attachments. Provide an object with
|
||||
'color_targets' and optional 'depth_stencil'. Returns an SDL_GPURenderPass handle.
|
||||
|
||||
|
||||
|
||||
**passDesc**: {color_targets:[...], depth_stencil:...}
|
||||
|
||||
|
||||
**Returns**: SDL_GPURenderPass
|
||||
|
||||
|
||||
### compute_pass(storageTextures, storageBuffers) <sub>function</sub>
|
||||
|
||||
Begin a compute pass reading/writing given arrays of textures and buffers.
|
||||
|
||||
|
||||
|
||||
**storageTextures**: array of read/write textures
|
||||
|
||||
**storageBuffers**: array of read/write buffers
|
||||
|
||||
|
||||
**Returns**: SDL_GPUComputePass
|
||||
|
||||
|
||||
### swapchain_pass(clearColor) <sub>function</sub>
|
||||
|
||||
Begin a render pass that directly targets the swapchain (the window). Clears
|
||||
with the specified color.
|
||||
|
||||
|
||||
|
||||
**clearColor**: [r,g,b,a]
|
||||
|
||||
|
||||
**Returns**: SDL_GPURenderPass
|
||||
|
||||
|
||||
### acquire_swapchain() <sub>function</sub>
|
||||
|
||||
Acquire the current swapchain texture from the window. Internal usage.
|
||||
|
||||
|
||||
|
||||
**Returns**: SDL_GPUTexture handle
|
||||
|
||||
|
||||
### bind_vertex_buffer(slot, buffer) <sub>function</sub>
|
||||
|
||||
Bind a GPU buffer as the vertex buffer at a given slot.
|
||||
|
||||
|
||||
|
||||
**slot**: Integer slot index.
|
||||
|
||||
**buffer**: The SDL_GPUBuffer.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_index_buffer(buffer, offset) <sub>function</sub>
|
||||
|
||||
Bind a GPU buffer as the index buffer (16-bit or 32-bit).
|
||||
|
||||
|
||||
|
||||
**buffer**: The SDL_GPUBuffer.
|
||||
|
||||
**offset**: Optional offset in bytes.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_fragment_sampler(slot, texture, sampler) <sub>function</sub>
|
||||
|
||||
Bind a texture+sampler pair to a particular fragment shader slot.
|
||||
|
||||
|
||||
|
||||
**slot**: Index of the sampler binding.
|
||||
|
||||
**texture**: The SDL_GPUTexture
|
||||
|
||||
**sampler**: The SDL_GPUSampler
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### push_vertex_uniform_data(slot, data) <sub>function</sub>
|
||||
|
||||
Push raw data to a vertex shader uniform block.
|
||||
|
||||
|
||||
|
||||
**slot**: The uniform buffer slot.
|
||||
|
||||
**data**: An ArrayBuffer with the data to upload.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### push_fragment_uniform_data(slot, data) <sub>function</sub>
|
||||
|
||||
Push raw data to a fragment shader uniform block.
|
||||
|
||||
|
||||
|
||||
**slot**: The uniform buffer slot index.
|
||||
|
||||
**data**: An ArrayBuffer with uniform data.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### push_compute_uniform_data(slot, data) <sub>function</sub>
|
||||
|
||||
Push raw data to a compute shader uniform buffer.
|
||||
|
||||
|
||||
|
||||
**slot**: The uniform buffer slot.
|
||||
|
||||
**data**: An ArrayBuffer with the data.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### submit() <sub>function</sub>
|
||||
|
||||
Submit this command buffer to the GPU and return a fence for synchronization.
|
||||
|
||||
|
||||
|
||||
**Returns**: An SDL_GPUFence
|
||||
|
||||
|
||||
### cancel() <sub>function</sub>
|
||||
|
||||
Cancel (discard) this command buffer without submitting.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### camera(cameraTransform, uniformSlot) <sub>function</sub>
|
||||
|
||||
Write a camera transform (projection/view) to a uniform slot for 3D or 2D usage.
|
||||
|
||||
|
||||
|
||||
**cameraTransform**: A camera object or transform with .pos, fov, etc.
|
||||
|
||||
**uniformSlot**: The integer uniform buffer slot to which data is pushed.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### hud(sizeVec2, uniformSlot) <sub>function</sub>
|
||||
|
||||
Write an orthographic full-screen "HUD" matrix to a uniform slot. Typically used
|
||||
for 2D overlays.
|
||||
|
||||
|
||||
|
||||
**sizeVec2**: [width, height] of the viewport area.
|
||||
|
||||
**uniformSlot**: The integer uniform buffer slot.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### push_debug_group(name) <sub>function</sub>
|
||||
|
||||
Push a named debug group marker onto the GPU command list (for debuggers/profilers).
|
||||
|
||||
|
||||
|
||||
**name**: The debug label string.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### pop_debug_group() <sub>function</sub>
|
||||
|
||||
Pop the most recent debug group marker.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### debug_label(label) <sub>function</sub>
|
||||
|
||||
Insert a one-off debug label at the current spot in the command list.
|
||||
|
||||
|
||||
|
||||
**label**: The debug label string
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### blit(blitDesc) <sub>function</sub>
|
||||
|
||||
Blit one GPU texture to another with optional flip mode, filter, and clear operations.
|
||||
|
||||
|
||||
|
||||
**blitDesc**: { src:{texture,mip_level, etc}, dst:{texture,...}, load_op, flip, filter, clear_color:[r,g,b,a] }
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
88
docs/api/types/SDL_GPUComputePass.md
Normal file
88
docs/api/types/SDL_GPUComputePass.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "SDL_GPUComputePass"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUComputePass
|
||||
|
||||
A compute pass for dispatching compute pipelines. Freed after end() or GC.
|
||||
|
||||
|
||||
### dispatch(x, y, z) <sub>function</sub>
|
||||
|
||||
Dispatch the compute pipeline with the specified threadgroup counts.
|
||||
|
||||
|
||||
|
||||
**x**: Number of groups in X dimension
|
||||
|
||||
**y**: Number of groups in Y dimension
|
||||
|
||||
**z**: Number of groups in Z dimension
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### end() <sub>function</sub>
|
||||
|
||||
End this compute pass.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### pipeline(computePipeline) <sub>function</sub>
|
||||
|
||||
Bind a compute pipeline in this pass.
|
||||
|
||||
|
||||
|
||||
**computePipeline**: The SDL_GPUComputePipeline
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### samplers(arrayOfSamplerBindings, firstSlot) <sub>function</sub>
|
||||
|
||||
Bind a set of texture/sampler pairs for compute usage.
|
||||
|
||||
|
||||
|
||||
**arrayOfSamplerBindings**: e.g. [ {texture, sampler}, ...]
|
||||
|
||||
**firstSlot**: The starting sampler slot.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### storage_buffers(arrayOfBuffers, firstSlot) <sub>function</sub>
|
||||
|
||||
Bind an array of storage buffers for the compute shader.
|
||||
|
||||
|
||||
|
||||
**arrayOfBuffers**: The buffers
|
||||
|
||||
**firstSlot**: Starting binding slot.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### storage_textures(arrayOfTextures, firstSlot) <sub>function</sub>
|
||||
|
||||
Bind an array of storage textures for the compute shader.
|
||||
|
||||
|
||||
|
||||
**arrayOfTextures**: The textures
|
||||
|
||||
**firstSlot**: Starting binding slot
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
10
docs/api/types/SDL_GPUComputePipeline.md
Normal file
10
docs/api/types/SDL_GPUComputePipeline.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "SDL_GPUComputePipeline"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUComputePipeline
|
||||
|
||||
Encapsulates a compute shader program plus associated resource layouts.
|
||||
Created via device.compute_pipeline(...).
|
||||
|
||||
9
docs/api/types/SDL_GPUCopyPass.md
Normal file
9
docs/api/types/SDL_GPUCopyPass.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "SDL_GPUCopyPass"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUCopyPass
|
||||
|
||||
A pass for CPU<->GPU or GPU<->GPU copy operations. No direct JS API besides internal usage.
|
||||
|
||||
241
docs/api/types/SDL_GPUDevice.md
Normal file
241
docs/api/types/SDL_GPUDevice.md
Normal file
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: "SDL_GPUDevice"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUDevice
|
||||
|
||||
A handle for low-level GPU operations via SDL GPU. Freed on GC.
|
||||
|
||||
|
||||
### claim_window(window) <sub>function</sub>
|
||||
|
||||
Claim an existing SDL_Window so this GPU device can render to it.
|
||||
|
||||
|
||||
|
||||
**window**: The SDL_Window to attach.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### make_pipeline(pipelineDesc) <sub>function</sub>
|
||||
|
||||
Create a new graphics pipeline from a descriptor object specifying shaders,
|
||||
blend states, vertex format, etc.
|
||||
|
||||
|
||||
|
||||
**pipelineDesc**: An object containing pipeline fields (vertexShader, blend, etc.).
|
||||
|
||||
|
||||
**Returns**: A SDL_GPUGraphicsPipeline handle.
|
||||
|
||||
|
||||
### compute_pipeline(desc) <sub>function</sub>
|
||||
|
||||
Create a compute pipeline from a descriptor (shader code, threadgroup sizes, etc.).
|
||||
|
||||
|
||||
|
||||
**desc**: An object with shader code, thread counts, etc.
|
||||
|
||||
|
||||
**Returns**: SDL_GPUComputePipeline handle.
|
||||
|
||||
|
||||
### set_swapchain(composition, presentMode) <sub>function</sub>
|
||||
|
||||
Specify how the swapchain (final rendered image) is composed, e.g. 'sdr', 'hdr',
|
||||
and present mode like 'vsync' or 'immediate'.
|
||||
|
||||
|
||||
|
||||
**composition**: E.g. 'sdr', 'linear', or 'hdr'.
|
||||
|
||||
**presentMode**: E.g. 'vsync', 'immediate', 'mailbox'.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### sort_sprite(a, b) <sub>function</sub>
|
||||
|
||||
A comparator function used for sorting sprite objects by layer, y, and texture.
|
||||
Usually used internally.
|
||||
|
||||
|
||||
|
||||
**a**: A sprite object.
|
||||
|
||||
**b**: Another sprite object.
|
||||
|
||||
|
||||
**Returns**: <0, 0, or >0 for sort ordering.
|
||||
|
||||
|
||||
### make_sampler(samplerDesc) <sub>function</sub>
|
||||
|
||||
Create a sampler object specifying filtering, wrapping, anisotropy, etc.
|
||||
|
||||
|
||||
|
||||
**samplerDesc**: An object with min_filter, mag_filter, etc.
|
||||
|
||||
|
||||
**Returns**: SDL_GPUSampler handle.
|
||||
|
||||
|
||||
### load_texture(surface, compressionLevel) <sub>function</sub>
|
||||
|
||||
Upload an SDL_Surface into a GPU texture, optionally compressing with DXT. Freed automatically.
|
||||
|
||||
|
||||
|
||||
**surface**: An SDL_Surface.
|
||||
|
||||
**compressionLevel**: 0=none, 1=DXT1 or DXT5, 2=high quality, etc.
|
||||
|
||||
|
||||
**Returns**: SDL_GPUTexture
|
||||
|
||||
|
||||
### texture(desc) <sub>function</sub>
|
||||
|
||||
Create a GPU texture with the specified format usage.
|
||||
|
||||
|
||||
|
||||
**desc**: Object with {width, height, layers, type, format, usage, etc.}
|
||||
|
||||
|
||||
**Returns**: SDL_GPUTexture
|
||||
|
||||
|
||||
### make_quad() <sub>function</sub>
|
||||
|
||||
Return a simple 2-triangle quad geometry covering [0,1]x[0,1].
|
||||
Useful for post-processing passes.
|
||||
|
||||
|
||||
|
||||
**Returns**: A mesh {pos, uv, color, indices}.
|
||||
|
||||
|
||||
### driver() <sub>function</sub>
|
||||
|
||||
Return the name of the underlying GPU driver in use (e.g. 'OpenGL').
|
||||
|
||||
|
||||
|
||||
**Returns**: A string with driver name.
|
||||
|
||||
|
||||
### make_shader(desc) <sub>function</sub>
|
||||
|
||||
Compile raw shader code (vertex or fragment) in e.g. SPIR-V, MSL, or DXIL format.
|
||||
|
||||
|
||||
|
||||
**desc**: {code:ArrayBuffer, stage:'vertex'|'fragment', format:'spv'|..., entrypoint:'main', ...}
|
||||
|
||||
|
||||
**Returns**: SDL_GPUShader object
|
||||
|
||||
|
||||
### acquire_cmd_buffer() <sub>function</sub>
|
||||
|
||||
Obtain a new command buffer for recording GPU commands. Must be submitted or canceled.
|
||||
|
||||
|
||||
|
||||
**Returns**: SDL_GPUCommandBuffer handle
|
||||
|
||||
|
||||
### upload(cmdBuffer, buffers, transferBuffer) <sub>function</sub>
|
||||
|
||||
Upload CPU data into a list of GPU buffers, optionally reusing or returning a
|
||||
transfer buffer. Typically you provide (cmdBuf, arrayOfTypedArrays, [transferBuffer]).
|
||||
|
||||
|
||||
|
||||
**cmdBuffer**: The command buffer in which to record copy commands.
|
||||
|
||||
**buffers**: An array of typed-array data to upload, each must have a 'gpu' property or so.
|
||||
|
||||
**transferBuffer**: Optional existing GPU transfer buffer to reuse.
|
||||
|
||||
|
||||
**Returns**: The transfer buffer used or newly created.
|
||||
|
||||
|
||||
### wait_for_fences(fences, waitAll) <sub>function</sub>
|
||||
|
||||
Wait on an array of GPU fence objects, optionally requiring all or any.
|
||||
|
||||
|
||||
|
||||
**fences**: An array of SDL_GPUFence objects.
|
||||
|
||||
**waitAll**: Boolean, true to wait for all fences, false for any.
|
||||
|
||||
|
||||
**Returns**: True if fences signaled, false on timeout or error.
|
||||
|
||||
|
||||
### query_fence(fence) <sub>function</sub>
|
||||
|
||||
Check if the given fence has been signaled yet. Non-blocking.
|
||||
|
||||
|
||||
|
||||
**fence**: SDL_GPUFence handle
|
||||
|
||||
|
||||
**Returns**: True if signaled, false if still pending
|
||||
|
||||
|
||||
### shader_format() <sub>function</sub>
|
||||
|
||||
Return an array of supported GPU shader binary formats (like 'spv', 'dxbc', etc.).
|
||||
|
||||
|
||||
|
||||
**Returns**: Array of strings naming supported formats.
|
||||
|
||||
|
||||
### slice9(texture, dstRect, edges) <sub>function</sub>
|
||||
|
||||
Generate a 9-slice tiling geometry in one shot. For advanced usage with GPU pipeline.
|
||||
|
||||
|
||||
|
||||
**texture**: An SDL_GPUTexture
|
||||
|
||||
**dstRect**: The rectangle {x, y, w, h}
|
||||
|
||||
**edges**: {l, r, t, b} edge sizes
|
||||
|
||||
|
||||
**Returns**: A mesh object
|
||||
|
||||
|
||||
### tile(texture, srcRect, dstRect, tileInfo) <sub>function</sub>
|
||||
|
||||
Generate geometry to tile a texture portion inside a dest rect.
|
||||
Often used for repeating backgrounds.
|
||||
|
||||
|
||||
|
||||
**texture**: The SDL_GPUTexture
|
||||
|
||||
**srcRect**: The portion to tile in pixels
|
||||
|
||||
**dstRect**: Where to fill
|
||||
|
||||
**tileInfo**: e.g. {repeat_x:true, repeat_y:true}
|
||||
|
||||
|
||||
**Returns**: A mesh object
|
||||
|
||||
10
docs/api/types/SDL_GPUFence.md
Normal file
10
docs/api/types/SDL_GPUFence.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "SDL_GPUFence"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUFence
|
||||
|
||||
A GPU fence for synchronization. Created upon commandBuffer.submit().
|
||||
Wait or query it with device.wait_for_fences or device.query_fence.
|
||||
|
||||
10
docs/api/types/SDL_GPUGraphicsPipeline.md
Normal file
10
docs/api/types/SDL_GPUGraphicsPipeline.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "SDL_GPUGraphicsPipeline"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUGraphicsPipeline
|
||||
|
||||
Encapsulates vertex+fragment shaders, blend/cull states, and vertex attribute layouts.
|
||||
Created via device.make_pipeline(...).
|
||||
|
||||
164
docs/api/types/SDL_GPURenderPass.md
Normal file
164
docs/api/types/SDL_GPURenderPass.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: "SDL_GPURenderPass"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPURenderPass
|
||||
|
||||
A single pass of drawing commands with color/depth attachments. Freed after end() or GC.
|
||||
|
||||
|
||||
### bind_pipeline(pipeline) <sub>function</sub>
|
||||
|
||||
Bind a previously created graphics pipeline (shaders, states, vertex layouts, etc.).
|
||||
|
||||
|
||||
|
||||
**pipeline**: The SDL_GPUGraphicsPipeline
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### viewport(rect) <sub>function</sub>
|
||||
|
||||
Set the viewport for clipping or scaling draws, in pass-local coordinates.
|
||||
|
||||
|
||||
|
||||
**rect**: {x,y,w,h}
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### scissor(rect) <sub>function</sub>
|
||||
|
||||
Set a scissor rectangle for discarding pixels outside it.
|
||||
|
||||
|
||||
|
||||
**rect**: {x,y,w,h}
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### draw(primitiveType, baseVertex, firstVertex, vertexCount) <sub>function</sub>
|
||||
|
||||
Issue a non-indexed draw call.
|
||||
|
||||
|
||||
|
||||
**primitiveType**: e.g. SDL_GPU_PRIMITIVETYPE_TRIANGLELIST
|
||||
|
||||
**baseVertex**: Starting vertex offset.
|
||||
|
||||
**firstVertex**: The first vertex to draw.
|
||||
|
||||
**vertexCount**: How many vertices to draw.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### draw_indexed(primitiveType, baseVertex, firstIndex, indexCount, instanceCount) <sub>function</sub>
|
||||
|
||||
Issue an indexed draw call from the bound index buffer.
|
||||
|
||||
|
||||
|
||||
**primitiveType**: The primitive type constant.
|
||||
|
||||
**baseVertex**: Offset in the vertex buffer.
|
||||
|
||||
**firstIndex**: Which index to start from.
|
||||
|
||||
**indexCount**: Number of indices to draw.
|
||||
|
||||
**instanceCount**: For instanced drawing, or 1 if normal.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### end() <sub>function</sub>
|
||||
|
||||
End this render pass, finalizing the draw operations.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_index_buffer(buffer, elementSize16bit) <sub>function</sub>
|
||||
|
||||
Bind an index buffer inside this pass, possibly overriding the global one.
|
||||
|
||||
|
||||
|
||||
**buffer**: The SDL_GPUBuffer
|
||||
|
||||
**elementSize16bit**: If 2, uses 16-bit indices; if 4, uses 32-bit indices
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_buffers(firstSlot, arrayOfBuffers) <sub>function</sub>
|
||||
|
||||
Bind multiple vertex buffers at consecutive slots.
|
||||
|
||||
|
||||
|
||||
**firstSlot**: The starting vertex buffer slot.
|
||||
|
||||
**arrayOfBuffers**: An array of GPUBuffer objects
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_samplers(vertexOrFragment, firstSlot, samplerBindings) <sub>function</sub>
|
||||
|
||||
Bind multiple texture/sampler pairs to either vertex or fragment slots.
|
||||
|
||||
|
||||
|
||||
**vertexOrFragment**: Boolean, true for vertex stage, false for fragment.
|
||||
|
||||
**firstSlot**: The first sampler slot to bind.
|
||||
|
||||
**samplerBindings**: An array of {texture, sampler}.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_storage_buffers(firstSlot, buffers) <sub>function</sub>
|
||||
|
||||
Bind one or more storage buffers for read/write in the pipeline.
|
||||
|
||||
|
||||
|
||||
**firstSlot**: Starting buffer slot index.
|
||||
|
||||
**buffers**: An array of SDL_GPUBuffer objects.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### bind_storage_textures(firstSlot, textures) <sub>function</sub>
|
||||
|
||||
Bind one or more storage textures for read/write in the pipeline.
|
||||
|
||||
|
||||
|
||||
**firstSlot**: Starting texture slot index.
|
||||
|
||||
**textures**: An array of SDL_GPUTexture objects.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
9
docs/api/types/SDL_GPUSampler.md
Normal file
9
docs/api/types/SDL_GPUSampler.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "SDL_GPUSampler"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUSampler
|
||||
|
||||
Defines how a texture is sampled (filter mode, address mode, anisotropy, compare op, etc.).
|
||||
|
||||
10
docs/api/types/SDL_GPUShader.md
Normal file
10
docs/api/types/SDL_GPUShader.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "SDL_GPUShader"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUShader
|
||||
|
||||
A single compiled shader (vertex or fragment) in a GPU-friendly format
|
||||
(e.g., SPIR-V, MSL). Combined into a pipeline for drawing.
|
||||
|
||||
8
docs/api/types/SDL_GPUTexture.md
Normal file
8
docs/api/types/SDL_GPUTexture.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "SDL_GPUTexture"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUTexture
|
||||
|
||||
### name() <sub>function</sub>
|
||||
10
docs/api/types/SDL_GPUTransferBuffer.md
Normal file
10
docs/api/types/SDL_GPUTransferBuffer.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "SDL_GPUTransferBuffer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_GPUTransferBuffer
|
||||
|
||||
A staging buffer used for copying data to or from GPU buffers/textures. Typically
|
||||
allocated/used internally by device.upload(...).
|
||||
|
||||
338
docs/api/types/SDL_Renderer.md
Normal file
338
docs/api/types/SDL_Renderer.md
Normal file
@@ -0,0 +1,338 @@
|
||||
---
|
||||
title: "SDL_Renderer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Renderer
|
||||
|
||||
A 2D rendering context using the SDL renderer API. Freed automatically.
|
||||
|
||||
|
||||
### draw_color(color) <sub>function</sub>
|
||||
|
||||
Set the render draw color for subsequent primitive calls (rect, line, etc.).
|
||||
|
||||
|
||||
|
||||
**color**: [r, g, b, a] in 0..1.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### present() <sub>function</sub>
|
||||
|
||||
Display whatever has been rendered (swap buffers). Must be called each frame.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### clear() <sub>function</sub>
|
||||
|
||||
Clear the current render target with the renderer's draw color.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### rect(rectOrArray, color) <sub>function</sub>
|
||||
|
||||
Draw one or more outlines of rectangles.
|
||||
|
||||
|
||||
|
||||
**rectOrArray**: A single rect {x,y,w,h} or an array of rects.
|
||||
|
||||
**color**: Optional [r,g,b,a]. If provided, overrides current draw color.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### fillrect(rectOrArray, color) <sub>function</sub>
|
||||
|
||||
Fill one or more rectangles with the renderer's current color or an optional override.
|
||||
|
||||
|
||||
|
||||
**rectOrArray**: A single rect {x,y,w,h} or an array of rects.
|
||||
|
||||
**color**: Optional [r,g,b,a].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### line(points, color) <sub>function</sub>
|
||||
|
||||
Draw a sequence of lines connecting points in an array.
|
||||
|
||||
|
||||
|
||||
**points**: An array of [x,y] points. Lines connect consecutive points.
|
||||
|
||||
**color**: Optional [r,g,b,a].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### point(points, color) <sub>function</sub>
|
||||
|
||||
Draw a list of points (pixels).
|
||||
|
||||
|
||||
|
||||
**points**: An array of [x,y] positions.
|
||||
|
||||
**color**: Optional [r,g,b,a].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### load_texture(surface) <sub>function</sub>
|
||||
|
||||
Create an SDL_Texture from a given SDL_Surface for use with this renderer.
|
||||
|
||||
|
||||
|
||||
**surface**: An SDL_Surface.
|
||||
|
||||
|
||||
**Returns**: An SDL_Texture object.
|
||||
|
||||
|
||||
### texture(tex, dstRect, srcRect, color) <sub>function</sub>
|
||||
|
||||
Draw a texture onto the render target.
|
||||
|
||||
|
||||
|
||||
**tex**: The SDL_Texture to draw.
|
||||
|
||||
**dstRect**: The destination rect {x, y, w, h}.
|
||||
|
||||
**srcRect**: Optional portion of the texture to draw {x, y, w, h}.
|
||||
|
||||
**color**: Optional color mod [r,g,b,a].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### slice9(tex, dstRect, edges, srcRect) <sub>function</sub>
|
||||
|
||||
Draw a texture with 9-slice scaling. The argument includes edges {l, r, t, b}
|
||||
for the corners/borders that remain unscaled. The rest is tiled or stretched.
|
||||
|
||||
|
||||
|
||||
**tex**: The SDL_Texture.
|
||||
|
||||
**dstRect**: Destination region {x, y, w, h}.
|
||||
|
||||
**edges**: {l, r, t, b} for corner sizes in pixels.
|
||||
|
||||
**srcRect**: Optional portion in the texture.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### tile(tex, dstRect, srcRect, scale) <sub>function</sub>
|
||||
|
||||
Tile a texture repeatedly within the specified region. Optionally use a srcRect.
|
||||
|
||||
|
||||
|
||||
**tex**: The SDL_Texture to tile.
|
||||
|
||||
**dstRect**: The region to fill {x, y, w, h}.
|
||||
|
||||
**srcRect**: Optional portion of texture.
|
||||
|
||||
**scale**: A float scale factor for each tile.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### get_image(rect) <sub>function</sub>
|
||||
|
||||
Read back the rendered pixels into a new SDL_Surface. If rect is undefined, capture entire output.
|
||||
|
||||
|
||||
|
||||
**rect**: Optional {x,y,w,h}.
|
||||
|
||||
|
||||
**Returns**: An SDL_Surface with the requested region's pixels.
|
||||
|
||||
|
||||
### fasttext(text, pos, color) <sub>function</sub>
|
||||
|
||||
Draw debug text using an internal fast path. Typically used for quick debugging overlays.
|
||||
|
||||
|
||||
|
||||
**text**: The string to draw.
|
||||
|
||||
**pos**: The [x, y] position to draw text.
|
||||
|
||||
**color**: Optional [r,g,b,a].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### geometry(texture, meshObject) <sub>function</sub>
|
||||
|
||||
Render custom geometry from a mesh object {pos, uv, color, indices, count} with an optional texture.
|
||||
|
||||
|
||||
|
||||
**texture**: The SDL_Texture or undefined.
|
||||
|
||||
**meshObject**: The geometry data with typed arrays.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### scale(scaleVec2) <sub>function</sub>
|
||||
|
||||
Set a scaling factor for all subsequent rendering on this renderer.
|
||||
|
||||
|
||||
|
||||
**scaleVec2**: [sx, sy] scaling factors.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### logical_size(size) <sub>function</sub>
|
||||
|
||||
Set a "logical" size that the renderer will scale to.
|
||||
For example, (320, 240) can auto-scale up to the window resolution.
|
||||
|
||||
|
||||
|
||||
**size**: [width, height].
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### viewport(rect) <sub>function</sub>
|
||||
|
||||
Set the clipping viewport for rendering. Pass undefined to use the full render target.
|
||||
|
||||
|
||||
|
||||
**rect**: {x, y, w, h}, or undefined.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### clip(rect) <sub>function</sub>
|
||||
|
||||
Set or clear the clipping rectangle for drawing. Pass undefined to clear.
|
||||
|
||||
|
||||
|
||||
**rect**: {x, y, w, h} or undefined.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### vsync(flag) <sub>function</sub>
|
||||
|
||||
Enable or disable vertical sync. This may have no effect depending on the driver.
|
||||
|
||||
|
||||
|
||||
**flag**: True or false.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### coords(pos) <sub>function</sub>
|
||||
|
||||
Convert window coordinates to this renderer's coordinate space.
|
||||
|
||||
|
||||
|
||||
**pos**: [x, y] in window space.
|
||||
|
||||
|
||||
**Returns**: [x, y] in renderer coordinate space.
|
||||
|
||||
|
||||
### camera(cameraTransform, centered) <sub>function</sub>
|
||||
|
||||
Set up a basic 2D camera matrix from a given transform. If 'centered' is true,
|
||||
the origin is the center of the viewport, else top-left.
|
||||
|
||||
|
||||
|
||||
**cameraTransform**: The transform whose pos is used.
|
||||
|
||||
**centered**: Boolean true or false.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### get_viewport() <sub>function</sub>
|
||||
|
||||
Return the current viewport rect.
|
||||
|
||||
|
||||
|
||||
**Returns**: {x, y, w, h}
|
||||
|
||||
|
||||
### screen2world(pos) <sub>function</sub>
|
||||
|
||||
Convert a screen coordinate to world space based on the current camera transform.
|
||||
|
||||
|
||||
|
||||
**pos**: [x, y] screen coords
|
||||
|
||||
|
||||
**Returns**: [wx, wy] in world space
|
||||
|
||||
|
||||
### target(texture) <sub>function</sub>
|
||||
|
||||
Set or clear the current render target texture. Pass undefined to reset to the default/window.
|
||||
|
||||
|
||||
|
||||
**texture**: An SDL_Texture or undefined
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### make_sprite_mesh(sprites) <sub>function</sub>
|
||||
|
||||
Generate a mesh from an array of sprite objects, combining their positions, UVs,
|
||||
and colors into a single geometry block.
|
||||
|
||||
|
||||
|
||||
**sprites**: An array of sprite-like objects.
|
||||
|
||||
|
||||
**Returns**: A 'mesh' object with pos, uv, color, indices, etc.
|
||||
|
||||
73
docs/api/types/SDL_Surface.md
Normal file
73
docs/api/types/SDL_Surface.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "SDL_Surface"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Surface
|
||||
|
||||
A software (CPU) image in memory. Freed when references vanish. Typically converted
|
||||
to SDL_Texture for drawing, or used as raw pixel data.
|
||||
|
||||
|
||||
### blit(dstRect, srcSurface, srcRect) <sub>function</sub>
|
||||
|
||||
Blit (copy) another surface onto this surface, scaling if needed.
|
||||
|
||||
|
||||
|
||||
**dstRect**: Destination {x, y, w, h}
|
||||
|
||||
**srcSurface**: The source SDL_Surface
|
||||
|
||||
**srcRect**: {x, y, w, h} portion from source
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### scale(newSize) <sub>function</sub>
|
||||
|
||||
Return a new SDL_Surface scaled to [width, height] using linear filtering.
|
||||
|
||||
|
||||
|
||||
**newSize**: [width, height]
|
||||
|
||||
|
||||
**Returns**: A new SDL_Surface with the scaled result.
|
||||
|
||||
|
||||
### fill(color) <sub>function</sub>
|
||||
|
||||
Fill the entire surface with a single color.
|
||||
|
||||
|
||||
|
||||
**color**: [r, g, b, a] in 0..1
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### rect(rect, color) <sub>function</sub>
|
||||
|
||||
Fill a sub-rectangle of the surface with a color.
|
||||
|
||||
|
||||
|
||||
**rect**: {x, y, w, h}
|
||||
|
||||
**color**: [r, g, b, a]
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### dup() <sub>function</sub>
|
||||
|
||||
Make a copy of this surface in RGBA format.
|
||||
|
||||
|
||||
|
||||
**Returns**: A new SDL_Surface copy.
|
||||
|
||||
21
docs/api/types/SDL_Texture.md
Normal file
21
docs/api/types/SDL_Texture.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "SDL_Texture"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Texture
|
||||
|
||||
A 2D GPU-accelerated texture for rendering with SDL_Renderer. Freed automatically.
|
||||
|
||||
|
||||
### mode(mode) <sub>function</sub>
|
||||
|
||||
Set texture scale mode or filtering mode (nearest/linear).
|
||||
|
||||
|
||||
|
||||
**mode**: A string or numeric mode to set (e.g., 'linear').
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
20
docs/api/types/SDL_Thread.md
Normal file
20
docs/api/types/SDL_Thread.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "SDL_Thread"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Thread
|
||||
|
||||
A handle to an SDL-created thread. Freed on GC after join.
|
||||
|
||||
Note: The engine generally doesn't expose custom usage for threads.
|
||||
|
||||
|
||||
### wait() <sub>function</sub>
|
||||
|
||||
Block until this thread terminates.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
131
docs/api/types/SDL_Window.md
Normal file
131
docs/api/types/SDL_Window.md
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "SDL_Window"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# SDL_Window
|
||||
|
||||
An application window, created via prosperon.engine_start or SDL calls. Freed on GC.
|
||||
|
||||
|
||||
### fullscreen() <sub>function</sub>
|
||||
|
||||
Toggle fullscreen mode for this window (SDL_WINDOW_FULLSCREEN).
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### make_renderer(name) <sub>function</sub>
|
||||
|
||||
Create an SDL_Renderer for 2D rendering tied to this window.
|
||||
|
||||
|
||||
|
||||
**name**: The renderer driver name, e.g. "opengl" (may be optional).
|
||||
|
||||
|
||||
**Returns**: An SDL_Renderer object.
|
||||
|
||||
|
||||
### make_gpu(debug, driverName) <sub>function</sub>
|
||||
|
||||
Create an SDL_GPUDevice for low-level GPU rendering on this window.
|
||||
|
||||
|
||||
|
||||
**debug**: If true, enable debugging in the GPU device.
|
||||
|
||||
**driverName**: The GPU back-end driver, e.g. "opengl".
|
||||
|
||||
|
||||
**Returns**: An SDL_GPUDevice.
|
||||
|
||||
|
||||
### keyboard_shown() <sub>function</sub>
|
||||
|
||||
Return whether the on-screen keyboard is visible (mobile/tablet).
|
||||
|
||||
|
||||
|
||||
**Returns**: True if shown, false otherwise.
|
||||
|
||||
|
||||
### theme() <sub>function</sub>
|
||||
|
||||
Currently returns undefined. Placeholder for retrieving OS window theme info.
|
||||
|
||||
|
||||
|
||||
**Returns**: undefined
|
||||
|
||||
|
||||
### safe_area() <sub>function</sub>
|
||||
|
||||
Return a rect describing any OS-specific "safe" region for UI, e.g. on iPhone with a notch.
|
||||
|
||||
|
||||
|
||||
**Returns**: A rect object {x, y, w, h}.
|
||||
|
||||
|
||||
### bordered(flag) <sub>function</sub>
|
||||
|
||||
Enable or disable window borders.
|
||||
|
||||
|
||||
|
||||
**flag**: True to show borders, false to hide.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### set_icon(surface) <sub>function</sub>
|
||||
|
||||
Set the window's icon from an SDL_Surface.
|
||||
|
||||
|
||||
|
||||
**surface**: An SDL_Surface holding the icon.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### title <sub>accessor</sub>
|
||||
|
||||
Get or set the window's title text in the title bar.
|
||||
|
||||
|
||||
|
||||
**newTitle**: (when setting) A string title.
|
||||
|
||||
|
||||
**Returns**: The current title if getting, or None if setting.
|
||||
|
||||
|
||||
### size <sub>accessor</sub>
|
||||
|
||||
Get or set the window's size as [width, height].
|
||||
|
||||
|
||||
|
||||
**newSize**: (when setting) e.g. [640, 480]
|
||||
|
||||
|
||||
**Returns**: The current [width, height] or None if setting.
|
||||
|
||||
|
||||
### mouse_grab(flag) <sub>function</sub>
|
||||
|
||||
Grab or ungrab the mouse for this window (so the pointer won't leave).
|
||||
|
||||
|
||||
|
||||
**flag**: True to grab mouse input, false to release.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
72
docs/api/types/datastream.md
Normal file
72
docs/api/types/datastream.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: "datastream"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# datastream
|
||||
|
||||
A streaming media handle, typically for MPEG video. Freed automatically.
|
||||
|
||||
|
||||
### time() <sub>function</sub>
|
||||
|
||||
Return the current playback time in seconds.
|
||||
|
||||
|
||||
|
||||
**Returns**: Current time as a float in seconds.
|
||||
|
||||
|
||||
### seek(seconds) <sub>function</sub>
|
||||
|
||||
Seek to the specified time (in seconds).
|
||||
|
||||
|
||||
|
||||
**seconds**: The time to jump to in the stream.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### advance(seconds) <sub>function</sub>
|
||||
|
||||
Advance by a certain number of seconds, decoding video as needed.
|
||||
|
||||
|
||||
|
||||
**seconds**: The amount of time to skip forward.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### duration() <sub>function</sub>
|
||||
|
||||
Return the total duration of the video stream, in seconds, if known.
|
||||
|
||||
|
||||
|
||||
**Returns**: Float seconds duration, or 0 if unknown.
|
||||
|
||||
|
||||
### framerate() <sub>function</sub>
|
||||
|
||||
Return the framerate (FPS) of the stream if known.
|
||||
|
||||
|
||||
|
||||
**Returns**: Float frames per second, or 0 if unknown.
|
||||
|
||||
|
||||
### callback <sub>accessor</sub>
|
||||
|
||||
A function to call whenever a new frame is decoded. If not set, no callback is invoked.
|
||||
|
||||
|
||||
|
||||
**fn**: (when setting) A function that receives (surface).
|
||||
|
||||
|
||||
**Returns**: The existing function or undefined if none.
|
||||
|
||||
71
docs/api/types/enet_host.md
Normal file
71
docs/api/types/enet_host.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: "enet_host"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# enet_host
|
||||
|
||||
### service(callback, timeout) <sub>function</sub>
|
||||
|
||||
|
||||
Poll for and process any available network events (connect, receive, disconnect, or none)
|
||||
from this host, calling the provided callback for each event. This function loops until
|
||||
no more events are available in the current timeframe.
|
||||
|
||||
Event object properties:
|
||||
- type: String, one of "connect", "receive", "disconnect", or "none".
|
||||
- peer: (present if type = "connect") The ENetPeer object for the new connection.
|
||||
- channelID: (present if type = "receive") The channel on which the data was received.
|
||||
- data: (present if type = "receive") The received data as a *plain JavaScript object*.
|
||||
If the JSON parse fails or the data isn't an object, a JavaScript error is thrown.
|
||||
|
||||
object as its single argument.
|
||||
|
||||
|
||||
**callback**: A function called once for each available event, receiving an event
|
||||
|
||||
**timeout**: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### connect(host, port) <sub>function</sub>
|
||||
|
||||
|
||||
Initiate a connection from this host to a remote server. Throws an error if the
|
||||
connection cannot be started.
|
||||
|
||||
|
||||
|
||||
**host**: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
|
||||
|
||||
**port**: The port number to connect to.
|
||||
|
||||
|
||||
**Returns**: An ENetPeer object representing the connection.
|
||||
|
||||
|
||||
### flush() <sub>function</sub>
|
||||
|
||||
|
||||
Flush all pending outgoing packets for this host immediately.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### broadcast(data) <sub>function</sub>
|
||||
|
||||
|
||||
Broadcast a JavaScript object to all connected peers on channel 0. The object is
|
||||
serialized to JSON, and the packet is sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to broadcast to all peers.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
107
docs/api/types/enet_peer.md
Normal file
107
docs/api/types/enet_peer.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: "enet_peer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# enet_peer
|
||||
|
||||
### send(data) <sub>function</sub>
|
||||
|
||||
|
||||
Send a JavaScript object to this peer on channel 0. The object is serialized to JSON and
|
||||
sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to send.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect() <sub>function</sub>
|
||||
|
||||
|
||||
Request a graceful disconnection from this peer. The connection will close after
|
||||
pending data is sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_now() <sub>function</sub>
|
||||
|
||||
|
||||
Immediately terminate the connection to this peer, discarding any pending data.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_later() <sub>function</sub>
|
||||
|
||||
|
||||
Request a disconnection from this peer after all queued packets are sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### reset() <sub>function</sub>
|
||||
|
||||
|
||||
Reset this peer's connection, immediately dropping it and clearing its internal state.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### ping() <sub>function</sub>
|
||||
|
||||
|
||||
Send a ping request to this peer to measure latency.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### throttle_configure(interval, acceleration, deceleration) <sub>function</sub>
|
||||
|
||||
|
||||
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
|
||||
rate based on packet loss or congestion.
|
||||
|
||||
|
||||
|
||||
**interval**: The interval (ms) between throttle adjustments.
|
||||
|
||||
**acceleration**: The factor to increase sending speed when conditions improve.
|
||||
|
||||
**deceleration**: The factor to decrease sending speed when conditions worsen.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### timeout(timeout_limit, timeout_min, timeout_max) <sub>function</sub>
|
||||
|
||||
|
||||
Set timeout parameters for this peer, determining how long ENet waits before considering
|
||||
the connection lost.
|
||||
|
||||
|
||||
|
||||
**timeout_limit**: The total time (ms) before the peer is disconnected.
|
||||
|
||||
**timeout_min**: The minimum timeout (ms) used for each timeout attempt.
|
||||
|
||||
**timeout_max**: The maximum timeout (ms) used for each timeout attempt.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
71
docs/api/types/font.md
Normal file
71
docs/api/types/font.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: "font"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# font
|
||||
|
||||
A bitmap or TTF-based font object storing glyph data.
|
||||
Used for measuring/drawing text. Freed when GC sees no references.
|
||||
|
||||
|
||||
### linegap <sub>accessor</sub>
|
||||
|
||||
Get or set the font's additional line spacing above the built-in metrics.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) The new line gap.
|
||||
|
||||
|
||||
**Returns**: The current line gap (when getting), or None (when setting).
|
||||
|
||||
|
||||
### height <sub>accessor</sub>
|
||||
|
||||
(read only)
|
||||
|
||||
The baseline-to-baseline height in pixels.
|
||||
|
||||
|
||||
|
||||
**Returns**: The font's total height in px.
|
||||
|
||||
|
||||
### ascent <sub>accessor</sub>
|
||||
|
||||
(read only)
|
||||
|
||||
How far above the baseline the font extends.
|
||||
|
||||
|
||||
|
||||
**Returns**: A scalar float for ascent.
|
||||
|
||||
|
||||
### descent <sub>accessor</sub>
|
||||
|
||||
(read only)
|
||||
|
||||
How far below baseline the font extends.
|
||||
|
||||
|
||||
|
||||
**Returns**: A scalar float for descent.
|
||||
|
||||
|
||||
### text_size(text, letterSpacing, wrap) <sub>function</sub>
|
||||
|
||||
Measure a piece of text's width/height when rendered with this font.
|
||||
|
||||
|
||||
|
||||
**text**: The string to measure.
|
||||
|
||||
**letterSpacing**: Extra spacing between characters.
|
||||
|
||||
**wrap**: If nonzero, word-wrap to this maximum width.
|
||||
|
||||
|
||||
**Returns**: [width, height] as a float array.
|
||||
|
||||
89
docs/api/types/rtree.md
Normal file
89
docs/api/types/rtree.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: "rtree"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# rtree
|
||||
|
||||
An R-tree for spatial lookups. Insert bounding boxes, query by bounding box, etc.
|
||||
|
||||
|
||||
### add(obj) <sub>function</sub>
|
||||
|
||||
Insert an object that has a 'rect' property {x, y, w, h} into the tree.
|
||||
|
||||
|
||||
|
||||
**obj**: The object to add (must have rectAtom).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### delete(obj) <sub>function</sub>
|
||||
|
||||
Remove an object from the tree. Must match the same rect as used when adding.
|
||||
|
||||
|
||||
|
||||
**obj**: The object to remove.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### query(rect) <sub>function</sub>
|
||||
|
||||
Return an array of objects whose bounding boxes intersect the given rect.
|
||||
|
||||
|
||||
|
||||
**rect**: {x, y, w, h} bounding region to query.
|
||||
|
||||
|
||||
**Returns**: Array of objects that overlap that region.
|
||||
|
||||
|
||||
### size <sub>accessor</sub>
|
||||
|
||||
(read only)
|
||||
|
||||
Indicates how many items are stored in the rtree.
|
||||
|
||||
|
||||
|
||||
**Returns**: Integer count of items in the tree.
|
||||
|
||||
|
||||
### forEach(callback) <sub>function</sub>
|
||||
|
||||
Call a function for every item in the rtree.
|
||||
|
||||
|
||||
|
||||
**callback**: A function called with no arguments, or possibly (item).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### has(obj) <sub>function</sub>
|
||||
|
||||
Return true if the specified object is in the tree, false otherwise.
|
||||
|
||||
|
||||
|
||||
**obj**: The object to check.
|
||||
|
||||
|
||||
**Returns**: True if found, else false.
|
||||
|
||||
|
||||
### values() <sub>function</sub>
|
||||
|
||||
Return an array of all items currently in the rtree.
|
||||
|
||||
|
||||
|
||||
**Returns**: Array of all stored objects.
|
||||
|
||||
73
docs/api/types/sprite.md
Normal file
73
docs/api/types/sprite.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "sprite"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# sprite
|
||||
|
||||
A 'sprite' is a simple struct for 2D drawing. It stores a rectangle (pos + size),
|
||||
UV coordinates, color, and layer, as well as an associated 'image' object. The sprite
|
||||
can be drawn via GPU or SDL_Renderer. Freed when no JS references remain.
|
||||
|
||||
|
||||
### set_affine(transform) <sub>function</sub>
|
||||
|
||||
Update this sprite's position and size from a transform's pos and scale.
|
||||
|
||||
|
||||
|
||||
**transform**: The transform whose pos/scale will overwrite the sprite's rect.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### set_rect(rect) <sub>function</sub>
|
||||
|
||||
Set the sprite's rect (x, y, w, h) directly.
|
||||
|
||||
|
||||
|
||||
**rect**: An object or array specifying x, y, width, and height.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### set_image(image) <sub>function</sub>
|
||||
|
||||
Assign or replace the sprite's underlying image. Automatically updates UV if
|
||||
the image has a 'rect' property.
|
||||
|
||||
|
||||
|
||||
**image**: A JS object representing the image (with .texture, .rect, etc.).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### layer <sub>accessor</sub>
|
||||
|
||||
Get or set the sprite's z-layer integer. Sprites with higher layers typically
|
||||
draw on top of lower layers.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) An integer specifying the layer.
|
||||
|
||||
|
||||
**Returns**: The current layer (when getting), or None (when setting).
|
||||
|
||||
|
||||
### color <sub>accessor</sub>
|
||||
|
||||
Get or set the sprite's color tint as [r, g, b, a].
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) An array [r, g, b, a] in the 0.0..1.0 range.
|
||||
|
||||
|
||||
**Returns**: The current color array (when getting), or None (when setting).
|
||||
|
||||
34
docs/api/types/timer.md
Normal file
34
docs/api/types/timer.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "timer"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# timer
|
||||
|
||||
A scheduled callback or countdown. Freed automatically once no longer referenced
|
||||
or once it completes.
|
||||
|
||||
|
||||
### remain <sub>accessor</sub>
|
||||
|
||||
Get or set how many seconds remain before the timer triggers.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) A float specifying new time remaining.
|
||||
|
||||
|
||||
**Returns**: The current time left (when getting), or None (when setting).
|
||||
|
||||
|
||||
### fn <sub>accessor</sub>
|
||||
|
||||
Get or set the function called when the timer expires.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) The function.
|
||||
|
||||
|
||||
**Returns**: The function or undefined if none is set.
|
||||
|
||||
219
docs/api/types/transform.md
Normal file
219
docs/api/types/transform.md
Normal file
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: "transform"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# transform
|
||||
|
||||
A hierarchical transform storing 3D or 2D position, rotation (as a quaternion),
|
||||
and scale. Can have a parent transform. Freed automatically on GC.
|
||||
|
||||
|
||||
### pos <sub>accessor</sub>
|
||||
|
||||
Get or set the transform's position as a 3D vector [x, y, z].
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) [x, y, z].
|
||||
|
||||
|
||||
**Returns**: The current position vector (when getting), or None (when setting).
|
||||
|
||||
|
||||
### scale <sub>accessor</sub>
|
||||
|
||||
Get or set the transform's scale as a 3D vector [x, y, z].
|
||||
For 2D usage, z is often 1.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) [sx, sy, sz].
|
||||
|
||||
|
||||
**Returns**: The current scale (when getting), or None (when setting).
|
||||
|
||||
|
||||
### rotation <sub>accessor</sub>
|
||||
|
||||
Get or set the transform's rotation as a quaternion [x, y, z, w].
|
||||
Angles in degrees or radians must first be converted prior to making a quaternion.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) [qx, qy, qz, qw].
|
||||
|
||||
|
||||
**Returns**: The current quaternion (when getting), or None (when setting).
|
||||
|
||||
|
||||
### parent <sub>accessor</sub>
|
||||
|
||||
Get or set the transform's parent. If set, this transform becomes a child of
|
||||
the parent (re-parenting). Must be another transform object or undefined.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) Another transform or undefined.
|
||||
|
||||
|
||||
**Returns**: The current parent transform (when getting), or None (when setting).
|
||||
|
||||
|
||||
### change_hook <sub>accessor</sub>
|
||||
|
||||
A user-supplied function that's called whenever the transform's local matrix changes.
|
||||
If undefined, no hook is called.
|
||||
|
||||
|
||||
|
||||
**value**: (when setting) A function.
|
||||
|
||||
|
||||
**Returns**: The current function or undefined.
|
||||
|
||||
|
||||
### trs(pos, quat, scale) <sub>function</sub>
|
||||
|
||||
Set the transform's position, rotation, and scale in one call.
|
||||
|
||||
|
||||
|
||||
**pos**: [x,y,z] for position, or undefined to keep existing.
|
||||
|
||||
**quat**: [qx,qy,qz,qw] for rotation, or undefined.
|
||||
|
||||
**scale**: [sx,sy,sz] for scale, or undefined.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### phys2d(velocity, angularVel, dt) <sub>function</sub>
|
||||
|
||||
Apply simple 2D velocity and angular velocity to this transform.
|
||||
|
||||
|
||||
|
||||
**velocity**: [vx, vy] added to 'pos' each frame.
|
||||
|
||||
**angularVel**: A scalar for rotation in (radians/second).
|
||||
|
||||
**dt**: The time delta in seconds.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### move(delta) <sub>function</sub>
|
||||
|
||||
Translate this transform by the specified vector.
|
||||
|
||||
|
||||
|
||||
**delta**: [dx, dy, dz] to add to .pos
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### rotate(axis, angle) <sub>function</sub>
|
||||
|
||||
Rotate this transform by an axis+angle.
|
||||
|
||||
|
||||
|
||||
**axis**: [ax, ay, az] the axis of rotation.
|
||||
|
||||
**angle**: The angle in turns or radians (depending on usage).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### angle(axis) <sub>function</sub>
|
||||
|
||||
Return the transform's rotation about a specified axis (x, y, or z).
|
||||
For example, angle([1,0,0]) returns the roll about the X-axis.
|
||||
|
||||
|
||||
|
||||
**axis**: Which axis [1,0,0] or [0,1,0] or [0,0,1].
|
||||
|
||||
|
||||
**Returns**: The numeric angle in 'turns' or radians, depending on usage.
|
||||
|
||||
|
||||
### lookat(target) <sub>function</sub>
|
||||
|
||||
Rotate this transform so it looks toward the given world position.
|
||||
|
||||
|
||||
|
||||
**target**: [x, y, z] position in world coords.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### direction(localDir) <sub>function</sub>
|
||||
|
||||
Rotate a vector by this transform's rotation, effectively "transforming"
|
||||
a direction from local space to world space.
|
||||
|
||||
|
||||
|
||||
**localDir**: [dx, dy, dz] in local transform coordinates.
|
||||
|
||||
|
||||
**Returns**: [dx', dy', dz'] direction in world space.
|
||||
|
||||
|
||||
### unit() <sub>function</sub>
|
||||
|
||||
Reset position, rotation, and scale to [0,0,0], identity rotation, and [1,1,1].
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### rect(rect) <sub>function</sub>
|
||||
|
||||
Set this transform's pos and scale from a 2D rect object {x, y, w, h}.
|
||||
|
||||
|
||||
|
||||
**rect**: Object with .x, .y, .w, .h
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### array() <sub>function</sub>
|
||||
|
||||
Return this transform's matrix as a 16-element float array in column-major order.
|
||||
|
||||
|
||||
|
||||
**Returns**: An array of 16 floats.
|
||||
|
||||
|
||||
### torect() <sub>function</sub>
|
||||
|
||||
Convert transform's 2D position/scale to a rect {x, y, w, h}.
|
||||
Rotation is currently ignored.
|
||||
|
||||
|
||||
|
||||
**Returns**: A rect object {x, y, w, h}.
|
||||
|
||||
|
||||
### children() <sub>function</sub>
|
||||
|
||||
Return an array of child transforms belonging to this transform.
|
||||
|
||||
|
||||
|
||||
**Returns**: An array of transform objects.
|
||||
|
||||
12
docs/api/use.md
Normal file
12
docs/api/use.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "use"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# use
|
||||
|
||||
### length <sub>number</sub>
|
||||
|
||||
### name <sub>string</sub>
|
||||
|
||||
### prototype <sub>object</sub>
|
||||
39
docs/design/actor_use_cases.md
Normal file
39
docs/design/actor_use_cases.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Actor Use Cases for Prosperon
|
||||
|
||||
Confirmed implementation targets. These are the concrete reasons actors add value beyond basic frame scheduling.
|
||||
|
||||
## 1. P2P Networking (Lockstep + Input Streaming)
|
||||
|
||||
Actor security model enables safe P2P:
|
||||
- **Lockstep**: Both peers run the same actor with the same inputs. Actors are deterministic (same messages in = same state). Synchronize input streams.
|
||||
- **Input streaming** (GGPO-style): Each peer sends inputs to the other. Actors only see inputs, not internal state, so cheaters can't fabricate state — only send inputs that the other peer validates by running the simulation.
|
||||
- Security-first actor system makes this safe by design.
|
||||
|
||||
## 2. Game Sharing (DS Download Play Style)
|
||||
|
||||
Send entire cell packages over the network. Receiver runs the game in a sandboxed actor.
|
||||
- A cell program/game sends its entire package (code + assets) using the cell package system.
|
||||
- The other person runs it in a sandboxed actor — can't touch host filesystem, can't read save data, can only draw sprites and send messages through the allowed interface.
|
||||
- Same trust model as mod sandboxing but for complete games.
|
||||
|
||||
## 3. Mod Sandboxing
|
||||
|
||||
Untrusted code runs in its own actor, communicates through a controlled message interface.
|
||||
- Mods can't corrupt game state.
|
||||
- Related to game sharing — same isolation model.
|
||||
|
||||
## 4. Background Asset Loading
|
||||
|
||||
Refactor the resource manager (currently single-threaded) into an actor:
|
||||
- Game sends: `{type: 'load', kind: 'image', path: 'player'}`
|
||||
- Resources replies: `{type: 'loaded', path: 'player', data: <blob>}`
|
||||
- Game never blocks. Request assets, keep running, handle them when they arrive.
|
||||
- Sprites show placeholder until real texture lands.
|
||||
- This is the natural actor pattern: send a message, get a message back.
|
||||
|
||||
## 5. Editor <-> Game Isolation
|
||||
|
||||
Editor runs as a separate actor, sends commands to the game actor.
|
||||
- Can't corrupt game state.
|
||||
- Clean separation of concerns.
|
||||
- Editor can inspect/modify game state only through the message interface.
|
||||
166
docs/entities.md
Normal file
166
docs/entities.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: "Entities"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Entities and the World
|
||||
|
||||
Prosperon uses a **script + overrides** model for entities, similar to Source engine entities. An entity type is defined by a script (`.cm` module), and instances are records that override specific attributes.
|
||||
|
||||
## Entity Types
|
||||
|
||||
An entity type is a module that returns a prototype object:
|
||||
|
||||
```javascript
|
||||
// entities/goblin.cm
|
||||
var sprite = use('sprite')
|
||||
|
||||
return {
|
||||
health: 100,
|
||||
speed: 2,
|
||||
image: "goblin.png",
|
||||
|
||||
init: function() {
|
||||
this.sprite = sprite({
|
||||
image: this.image,
|
||||
pos: this.pos,
|
||||
width: 32,
|
||||
height: 32,
|
||||
layer: 5
|
||||
})
|
||||
},
|
||||
|
||||
on_destroy: function() {
|
||||
this.sprite.destroy()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The return value defines every valid field with a default. This prototype IS the schema — the editor and tooling can introspect it to know what fields exist and what their defaults are.
|
||||
|
||||
## Creating Entities
|
||||
|
||||
Use the `world` module to create entity instances:
|
||||
|
||||
```javascript
|
||||
var world = use('world')
|
||||
var goblin_proto = use('entities/goblin')
|
||||
|
||||
var goblin = world.add_entity(goblin_proto, {
|
||||
pos: {x: 100, y: 200},
|
||||
health: 50
|
||||
})
|
||||
```
|
||||
|
||||
The second argument overrides specific fields from the prototype. Unspecified fields keep their defaults.
|
||||
|
||||
## Entity Lifecycle
|
||||
|
||||
Entities have two lifecycle hooks:
|
||||
|
||||
- `init()` — called when the entity is created (after overrides are applied)
|
||||
- `on_destroy()` — called when the entity is removed from the world
|
||||
|
||||
```javascript
|
||||
world.destroy_entity(goblin)
|
||||
```
|
||||
|
||||
## Levels as Data
|
||||
|
||||
A level is a collection of entity instances stored as JSON — each entry is a script path plus attribute overrides:
|
||||
|
||||
```json
|
||||
[
|
||||
{"script": "entities/goblin", "pos": {"x": 100, "y": 200}, "health": 50},
|
||||
{"script": "entities/goblin", "pos": {"x": 300, "y": 200}},
|
||||
{"script": "entities/tree", "pos": {"x": 200, "y": 150}},
|
||||
{"script": "entities/player_spawn", "pos": {"x": 50, "y": 50}}
|
||||
]
|
||||
```
|
||||
|
||||
The world loads this file, creates each entity from its prototype with the overrides applied. Since levels are JSON, they're easy to diff in source control and simple to generate from an editor.
|
||||
|
||||
## Composition via Modules
|
||||
|
||||
Entity behavior comes from composition, not inheritance. A goblin that patrols and has health uses separate modules:
|
||||
|
||||
```javascript
|
||||
// entities/patrol_goblin.cm
|
||||
var health = use('systems/health')
|
||||
var patrol = use('systems/patrol')
|
||||
|
||||
return {
|
||||
health: 100,
|
||||
patrol_speed: 1.5,
|
||||
patrol_points: [],
|
||||
|
||||
init: function() {
|
||||
health.attach(this)
|
||||
patrol.attach(this, this.patrol_points)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each system module provides behavior that operates on the entity's data. No class trees, no diamond inheritance — just modules that read and write fields.
|
||||
|
||||
## Querying Entities
|
||||
|
||||
The world module provides several ways to find and iterate entities:
|
||||
|
||||
```javascript
|
||||
var world = use('world')
|
||||
|
||||
// Iterate all entities
|
||||
world.each(function(entity) {
|
||||
log.console(entity)
|
||||
})
|
||||
|
||||
// Filter entities by predicate — returns array
|
||||
var enemies = world.query(function(e) { return e.team == 'enemy' })
|
||||
|
||||
// Find first matching entity
|
||||
var player = world.find(function(e) { return e.is_player })
|
||||
|
||||
// Get entity count
|
||||
var n = world.count()
|
||||
```
|
||||
|
||||
## Updating Entities
|
||||
|
||||
Call `world.update(dt)` each frame to tick all entities that have an `update(dt)` method:
|
||||
|
||||
```javascript
|
||||
// In your core.start() update callback:
|
||||
core.start({
|
||||
update: function(dt) {
|
||||
world.update(dt)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Loading Levels
|
||||
|
||||
Load a level from a JSON array of entity definitions:
|
||||
|
||||
```javascript
|
||||
world.load_level([
|
||||
{"script": "entities/goblin", "pos": {"x": 100, "y": 200}},
|
||||
{"script": "entities/tree", "pos": {"x": 300, "y": 100}}
|
||||
])
|
||||
```
|
||||
|
||||
Each entry's `script` field is loaded via `use()` as the prototype. All other fields are applied as overrides.
|
||||
|
||||
## Clearing the World
|
||||
|
||||
Remove and destroy all entities:
|
||||
|
||||
```javascript
|
||||
world.clear()
|
||||
```
|
||||
|
||||
## Override Rules
|
||||
|
||||
Overrides replace fields at the top level. If the prototype has `pos: {x: 0, y: 0}` and the override has `pos: {x: 50, y: 100}`, the entire `pos` is replaced. This is predictable and avoids ambiguity about partial nested merges.
|
||||
|
||||
Overrides should be pure data — field values only. No expressions, no conditionals, no logic. The script defines behavior; the override only tweaks data.
|
||||
189
docs/graphics.md
Normal file
189
docs/graphics.md
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
title: "Graphics"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Graphics Concepts
|
||||
|
||||
This page covers the fundamental graphics concepts in Prosperon.
|
||||
|
||||
## Textures and Images
|
||||
|
||||
A **texture** is a set of bytes on the GPU — not directly accessible from script code.
|
||||
|
||||
An **image** is a texture combined with a UV rect that specifies which region to draw:
|
||||
|
||||
```
|
||||
image = {
|
||||
texture: GPU texture handle,
|
||||
rect: {x, y, width, height} // UV coordinates
|
||||
}
|
||||
```
|
||||
|
||||
This means multiple images can share one texture (a sprite sheet or atlas), each referencing a different region.
|
||||
|
||||
## Supported Image Formats
|
||||
|
||||
| Format | Notes |
|
||||
|--------|-------|
|
||||
| PNG | Standard, lossless |
|
||||
| QOI | Fast decode, lossless |
|
||||
| GIF | Animated frames supported |
|
||||
| JPG/JPEG | Lossy |
|
||||
| Aseprite (.ase) | Frames, tags, slices, durations, pivots |
|
||||
|
||||
Aseprite files are fully parsed — named animation tags, per-frame durations, pivot points, and slice data are all available.
|
||||
|
||||
## Sprites
|
||||
|
||||
A sprite is the fundamental drawable. Create one with the `sprite` module:
|
||||
|
||||
```javascript
|
||||
var sprite = use('sprite')
|
||||
|
||||
var s = sprite({
|
||||
image: "player.png",
|
||||
pos: {x: 100, y: 200},
|
||||
width: 32,
|
||||
height: null, // derived from aspect ratio
|
||||
anchor_x: 0.5, // center horizontally
|
||||
anchor_y: 0, // bottom edge
|
||||
layer: 5,
|
||||
rotation: 0,
|
||||
flip: {x: false, y: false},
|
||||
color: {r: 1, g: 1, b: 1, a: 1},
|
||||
opacity: 1,
|
||||
tint: {r: 1, g: 1, b: 1, a: 1},
|
||||
filter: 'nearest',
|
||||
plane: 'default',
|
||||
groups: [],
|
||||
visible: true
|
||||
})
|
||||
```
|
||||
|
||||
### Fit Modes
|
||||
|
||||
When both `width` and `height` are specified, the `fit` property controls how the texture maps into the rectangle:
|
||||
|
||||
| Fit | Behavior |
|
||||
|-----|----------|
|
||||
| `none` | Use the texture's native pixel size |
|
||||
| `fill` | Stretch to exactly fill the rectangle (may distort) |
|
||||
| `contain` | Fit inside the rectangle, preserving aspect ratio |
|
||||
| `cover` | Fill the rectangle, preserving aspect ratio (crops via UV) |
|
||||
|
||||
If only one dimension is given (e.g. `width: 32, height: null`), the other is derived from the texture's aspect ratio.
|
||||
|
||||
### Anchors
|
||||
|
||||
The anchor determines which point of the sprite sits at `pos`:
|
||||
|
||||
- `anchor_x: 0, anchor_y: 0` — top-left at pos
|
||||
- `anchor_x: 0.5, anchor_y: 0.5` — center at pos
|
||||
- `anchor_x: 0.5, anchor_y: 0` — bottom-center at pos (good for characters)
|
||||
|
||||
### UV Mapping
|
||||
|
||||
For sprite sheets, control which region of the texture is drawn:
|
||||
|
||||
```javascript
|
||||
sprite({
|
||||
image: spritesheet_texture,
|
||||
uv: {offset: {x: 0.25, y: 0}, scale: {x: 0.25, y: 0.5}}
|
||||
})
|
||||
```
|
||||
|
||||
## Text
|
||||
|
||||
Create text with the `text2d` module:
|
||||
|
||||
```javascript
|
||||
var text2d = use('text2d')
|
||||
|
||||
var label = text2d({
|
||||
text: "Hello World",
|
||||
font: "myfont.ttf",
|
||||
size: 24,
|
||||
pos: {x: 100, y: 100},
|
||||
layer: 10
|
||||
})
|
||||
```
|
||||
|
||||
## Shapes
|
||||
|
||||
SDF-rendered shapes via the `shape2d` module:
|
||||
|
||||
```javascript
|
||||
var shape2d = use('shape2d')
|
||||
|
||||
var box = shape2d.rect({
|
||||
pos: {x: 50, y: 50},
|
||||
width: 100,
|
||||
height: 80,
|
||||
color: {r: 1, g: 0, b: 0, a: 1},
|
||||
layer: 3
|
||||
})
|
||||
|
||||
var ball = shape2d.circle({
|
||||
pos: {x: 200, y: 200},
|
||||
radius: 25,
|
||||
color: {r: 0, g: 1, b: 0, a: 1}
|
||||
})
|
||||
```
|
||||
|
||||
Available shapes: `rect`, `circle`, `ellipse`, `pill`.
|
||||
|
||||
## Tilemaps
|
||||
|
||||
Grid-based tile rendering:
|
||||
|
||||
```javascript
|
||||
var tilemap2d = use('tilemap2d')
|
||||
|
||||
var map = tilemap2d({
|
||||
tiles: tiles_2d_array,
|
||||
tile_width: 16,
|
||||
tile_height: 16,
|
||||
layer: 0
|
||||
})
|
||||
```
|
||||
|
||||
Tiles can be flipped, and the entire tilemap can have an affine transform applied. Camera culling for large maps is planned.
|
||||
|
||||
## Particles
|
||||
|
||||
Particle emitters produce sprite-like particles:
|
||||
|
||||
```javascript
|
||||
var particles2d = use('particles2d')
|
||||
|
||||
var emitter = particles2d({
|
||||
texture: "spark.png",
|
||||
pos: {x: 100, y: 100},
|
||||
layer: 8
|
||||
})
|
||||
```
|
||||
|
||||
## Coordinate System
|
||||
|
||||
Prosperon uses a Y-up coordinate system:
|
||||
|
||||
- **X+** goes right
|
||||
- **Y+** goes up
|
||||
- `[0, 0]` in world space is where the camera starts
|
||||
- `[0, 0]` in HUD space is the bottom-left of the screen
|
||||
|
||||
## Camera
|
||||
|
||||
A camera defines the viewport into the world. Its `width` and `height` set the game's internal resolution. Its `pos` determines what world coordinate is at the center of the screen.
|
||||
|
||||
```javascript
|
||||
var camera = use('camera')
|
||||
var cam = camera.make({
|
||||
pos: {x: 0, y: 0},
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
```
|
||||
|
||||
Everything in world space is drawn relative to the camera's position. HUD space is always screen-relative.
|
||||
59
docs/index.md
Normal file
59
docs/index.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "Prosperon Manual"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Prosperon
|
||||
|
||||
Prosperon is a 2D game programming environment built on [Pit](https://crumbpit.org), an actor-based language designed for safe, sandboxed scripting.
|
||||
|
||||
You write game logic in Pit — a simplified JavaScript with actor primitives — and Prosperon handles rendering, input, audio, and asset management. Games are composed from small, focused modules. No class hierarchies, no giant scene trees.
|
||||
|
||||
## How It Works
|
||||
|
||||
A Prosperon game is a collection of **modules** (`.cm`) and **programs** (`.ce`).
|
||||
|
||||
- **Modules** return a frozen value — an API, a config, a data table. Import them with `use()`.
|
||||
- **Programs** are actor entry points. They run top-to-bottom, register message handlers, and spawn children.
|
||||
|
||||
The engine provides modules for everything a 2D game needs:
|
||||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `sprite` | Create and register sprites |
|
||||
| `draw2d` | High-level factories for sprites, text, shapes, tilemaps |
|
||||
| `camera` | Create cameras with transform and projection |
|
||||
| `input` | Multi-device action mapping with control stacks |
|
||||
| `sound` | Audio playback with PCM caching and voice mixing |
|
||||
| `tween` | Fire-and-forget animation with easing |
|
||||
| `world` | Entity management and lifecycle |
|
||||
| `resources` | Asset discovery and path resolution |
|
||||
| `core` | Main loop, window, frame scheduling |
|
||||
|
||||
You create sprites, poke their positions, and the engine handles everything else — batching, sorting, effects, presentation scaling. Like programming sprites on a GBA, but with modern hardware behind it.
|
||||
|
||||
## Design Principles
|
||||
|
||||
**Minimal code.** The fewer lines it takes to do something, the fewer bugs it has, the faster it runs, and the less you need to keep in your head.
|
||||
|
||||
**Duck typing.** If an object looks like a sprite — has a position, an image, a layer — it works as a sprite. No base classes to inherit from.
|
||||
|
||||
**Text-based.** Every file in a Prosperon project is text. Scripts, configs, levels. Git diffs are readable, merges are clean.
|
||||
|
||||
**Modules, not globals.** Everything is accessed via `use()`. No global engine object. The world, camera, input — all imported as modules.
|
||||
|
||||
**Data-driven rendering.** Your game logic never touches the GPU. You declare drawables and their properties. The rendering pipeline sorts, batches, applies effects, and draws.
|
||||
|
||||
## Guides
|
||||
|
||||
- [Quickstart](quickstart/) — Run your first program
|
||||
- [Tutorial](tutorial/) — Build a game step by step
|
||||
- [Entities](entities/) — The world and entity model
|
||||
- [Rendering](rendering/) — Sprites, cameras, and the rendering pipeline
|
||||
- [Graphics](graphics/) — Textures, images, and coordinate systems
|
||||
- [Input](input/) — Action mapping, devices, and control stacks
|
||||
- [Resources](resources/) — Asset loading and discovery
|
||||
|
||||
## API Reference
|
||||
|
||||
- [API Index](api/) — All modules and types
|
||||
148
docs/input.md
Normal file
148
docs/input.md
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: "Input"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Input
|
||||
|
||||
Prosperon's input system normalizes keyboard, mouse, gamepad, and touch into **named actions**. You define what actions your game uses, bind them to physical inputs, and the engine routes events to the right place.
|
||||
|
||||
## Actions
|
||||
|
||||
An action is a named game event like `"jump"`, `"attack"`, or `"ui_up"`. Multiple physical inputs can trigger the same action:
|
||||
|
||||
```javascript
|
||||
var input = use('input')
|
||||
|
||||
input.configure({
|
||||
action_map: {
|
||||
jump: ['space', 'gamepad_a'],
|
||||
attack: ['mouse_button_left', 'gamepad_x'],
|
||||
move_left: ['a', 'left', 'gamepad_dpleft'],
|
||||
move_right: ['d', 'right', 'gamepad_dpright']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Default actions are provided for common UI navigation (`ui_up`, `ui_down`, `ui_left`, `ui_right`, `confirm`, `cancel`, `menu`).
|
||||
|
||||
## Players and Users
|
||||
|
||||
The input system supports multiple players. Each **user** has:
|
||||
|
||||
- Paired devices (keyboard, specific gamepad, etc.)
|
||||
- An action map (bindings can differ per player)
|
||||
- A **control stack** of possessed entities
|
||||
|
||||
```javascript
|
||||
var p1 = input.player1()
|
||||
```
|
||||
|
||||
### Device Pairing
|
||||
|
||||
By default, pairing is `'last_used'` — all input goes to player 1, and the active device switches when a button is pressed. For local multiplayer, use `'explicit'` pairing where each player is assigned specific devices.
|
||||
|
||||
```javascript
|
||||
input.configure({
|
||||
max_users: 2,
|
||||
pairing: 'explicit'
|
||||
})
|
||||
```
|
||||
|
||||
## Control Stack (Possession)
|
||||
|
||||
Players **possess** entities to route input to them. The control stack is ordered — the topmost entity gets input first.
|
||||
|
||||
```javascript
|
||||
var p1 = input.player1()
|
||||
|
||||
// Take control of the player character
|
||||
p1.possess(player_entity)
|
||||
|
||||
// Push a menu on top — it gets input priority
|
||||
p1.push(pause_menu)
|
||||
|
||||
// Pop the menu — player character gets input again
|
||||
p1.pop()
|
||||
```
|
||||
|
||||
The possessed entity must have an `on_input` method:
|
||||
|
||||
```javascript
|
||||
var player = {
|
||||
on_input: function(action, data) {
|
||||
if (action == 'jump' && data.pressed) {
|
||||
// jump logic
|
||||
}
|
||||
if (action == 'move_right') {
|
||||
// data.pressed, data.released, data.time
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Device Detection
|
||||
|
||||
The input system tracks what kind of device is active, so you can show appropriate button prompts:
|
||||
|
||||
```javascript
|
||||
var p1 = input.player1()
|
||||
var kind = p1.device_kind() // 'keyboard', 'mouse', 'gamepad'
|
||||
var type = p1.gamepad_type() // 'xbox', 'playstation', etc.
|
||||
```
|
||||
|
||||
Get the display name or icon for an action based on the current device:
|
||||
|
||||
```javascript
|
||||
var icon = p1.get_icon_for_action('jump')
|
||||
var binding = p1.get_primary_binding('jump')
|
||||
```
|
||||
|
||||
## Emacs-Style Keybindings
|
||||
|
||||
For editors or complex hotkey systems, Prosperon supports modifier notation:
|
||||
|
||||
| Prefix | Key |
|
||||
|--------|-----|
|
||||
| `C-` | Ctrl |
|
||||
| `M-` | Alt |
|
||||
| `S-` | Super |
|
||||
|
||||
So `C-a` means Ctrl+A, `C-M-s` means Ctrl+Alt+S. Case is preserved — `a` and `A` are different bindings.
|
||||
|
||||
The emacs module is enabled by default but can be disabled:
|
||||
|
||||
```javascript
|
||||
input.configure({ emacs: false })
|
||||
```
|
||||
|
||||
## Gestures
|
||||
|
||||
Touch gesture recognition is built in:
|
||||
|
||||
- Swipe detection (with configurable distance and time thresholds)
|
||||
- Pinch detection
|
||||
|
||||
```javascript
|
||||
input.configure({
|
||||
gestures: true,
|
||||
swipe_min_dist: 50,
|
||||
swipe_max_time: 0.5
|
||||
})
|
||||
```
|
||||
|
||||
## Saved Bindings
|
||||
|
||||
Player bindings are automatically saved and loaded, so remapped controls persist across sessions.
|
||||
|
||||
## Raw Events
|
||||
|
||||
For debugging or special cases, the raw SDL event can be accessed via the config callback:
|
||||
|
||||
```javascript
|
||||
core.start({
|
||||
input: function(raw_event) {
|
||||
// raw SDL event object
|
||||
}
|
||||
})
|
||||
```
|
||||
170
docs/ops.md
Normal file
170
docs/ops.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: "Compositor"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# The Compositor
|
||||
|
||||
The compositor is Prosperon's rendering orchestrator. You describe your scene — planes, layers, effects — and the compositor figures out what render passes are needed and executes them.
|
||||
|
||||
## The Basic Idea
|
||||
|
||||
You don't issue draw calls. You create sprites and set their properties. The compositor does the rest:
|
||||
|
||||
1. Queries all registered drawables
|
||||
2. Organizes them by plane and layer
|
||||
3. Applies effects to tagged groups
|
||||
4. Composites everything to the screen
|
||||
|
||||
Think of it like a GBA: you declare "sprite here, sprite there," get handles back, and poke their positions. The hardware (in this case, the compositor + GPU backend) handles the actual rendering.
|
||||
|
||||
## Scene Configuration
|
||||
|
||||
A scene is described as a config object passed to the compositor:
|
||||
|
||||
```javascript
|
||||
var compositor = use('compositor')
|
||||
|
||||
var plan = compositor.compile({
|
||||
clear: {r: 0, g: 0, b: 0, a: 1},
|
||||
planes: [
|
||||
{
|
||||
name: 'background',
|
||||
plane: 'background',
|
||||
resolution: {width: 320, height: 240},
|
||||
camera: bg_camera,
|
||||
presentation: 'integer_scale'
|
||||
},
|
||||
{
|
||||
name: 'game',
|
||||
plane: 'default',
|
||||
resolution: {width: 320, height: 240},
|
||||
camera: game_camera,
|
||||
layer_sort: {'5': 'y'},
|
||||
presentation: 'integer_scale'
|
||||
},
|
||||
{
|
||||
name: 'hud',
|
||||
plane: 'hud',
|
||||
resolution: {width: 1280, height: 720},
|
||||
camera: hud_camera,
|
||||
presentation: 'stretch'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Each plane renders independently at its own resolution and composites onto the screen.
|
||||
|
||||
## Planes
|
||||
|
||||
A **plane** is a named rendering group. Sprites belong to a plane via their `plane` property (default: `'default'`). Each plane in the compositor config:
|
||||
|
||||
- Has its own **resolution** (low-res pixel art, native UI, etc.)
|
||||
- Has its own **camera**
|
||||
- Has its own **layer sorting** rules
|
||||
- Has a **presentation mode** (how it maps to the window)
|
||||
|
||||
Planes composite in order — later planes draw on top of earlier ones.
|
||||
|
||||
## Layer Sorting
|
||||
|
||||
Within a plane, drawables are sorted by `layer` (integer). Within each layer, you can choose a sorting mode:
|
||||
|
||||
```javascript
|
||||
layer_sort: {
|
||||
'0': 'explicit', // engine may reorder for batching efficiency
|
||||
'5': 'y' // sort by Y position (top-down game depth)
|
||||
}
|
||||
```
|
||||
|
||||
Y-sorting is essential for top-down games where objects lower on screen should appear in front.
|
||||
|
||||
## Effects
|
||||
|
||||
Effects are applied to **groups** of sprites. Tag sprites with group names, then define effects for those groups:
|
||||
|
||||
```javascript
|
||||
var player = sprite({
|
||||
image: "player.png",
|
||||
groups: ['glow_objects']
|
||||
})
|
||||
|
||||
var plan = compositor.compile({
|
||||
planes: [{
|
||||
name: 'game',
|
||||
plane: 'default',
|
||||
camera: cam,
|
||||
resolution: {width: 320, height: 240}
|
||||
}],
|
||||
group_effects: {
|
||||
glow_objects: {
|
||||
effects: [
|
||||
{type: 'bloom', threshold: 0.5, intensity: 1.5, blur_passes: 2}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Available Effects
|
||||
|
||||
**Bloom** — extracts bright areas, blurs them, composites back:
|
||||
```javascript
|
||||
{type: 'bloom', threshold: 0.8, intensity: 1.0, blur_passes: 2}
|
||||
```
|
||||
|
||||
**Mask** — uses sprites in a mask group as a stencil:
|
||||
```javascript
|
||||
{type: 'mask', mask_group: 'mask_shapes', channel: 'alpha', invert: false}
|
||||
```
|
||||
|
||||
Sprites in the `mask_group` are not drawn directly — they only serve as the mask shape.
|
||||
|
||||
## Presentation Modes
|
||||
|
||||
Each plane specifies how its render target maps to the window:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `stretch` | Fill the window exactly (may distort aspect ratio) |
|
||||
| `letterbox` | Fit inside window, preserving aspect ratio, with black bars |
|
||||
| `integer_scale` | Scale by whole numbers only for pixel-perfect rendering |
|
||||
|
||||
`integer_scale` automatically uses nearest-neighbor filtering for crisp pixels.
|
||||
|
||||
## Execution
|
||||
|
||||
The compositor produces a **plan** — a list of render passes. The plan is executed by the GPU backend:
|
||||
|
||||
```javascript
|
||||
var result = compositor.execute(plan)
|
||||
// result.commands is sent to the GPU backend
|
||||
```
|
||||
|
||||
In practice, `core.start({render})` handles this for you. Your render callback returns the compositor result and the engine executes it.
|
||||
|
||||
## Manual Drawables
|
||||
|
||||
You can inject drawables that aren't registered with film2d by adding them directly to a plane config:
|
||||
|
||||
```javascript
|
||||
planes: [{
|
||||
name: 'game',
|
||||
plane: 'default',
|
||||
camera: cam,
|
||||
drawables: [
|
||||
{type: 'sprite', image: tex, pos: {x: 0, y: 0}, width: 32, height: 32, layer: 0}
|
||||
]
|
||||
}]
|
||||
```
|
||||
|
||||
These are merged with the registered drawables for that plane.
|
||||
|
||||
## Render Targets
|
||||
|
||||
The compositor automatically manages intermediate render targets for effects. You don't need to create or manage GPU textures — the compositor allocates and reuses them as needed.
|
||||
|
||||
## Debug
|
||||
|
||||
Pass `debug: 'graph'` to `core.start` config to log the compiled render plan as JSON. Pass `debug: 'cmd'` to log the final command list sent to the GPU.
|
||||
BIN
docs/prosperon.gif
Normal file
BIN
docs/prosperon.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
50
docs/prosperon.md
Normal file
50
docs/prosperon.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "About Prosperon"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Prosperon
|
||||
|
||||
Prosperon is a 2D game programming environment built on [Pit](https://crumbpit.org), an actor-based language designed for safe, sandboxed scripting.
|
||||
|
||||
Games are written as small, focused modules in Pit — a simplified JavaScript with actor primitives. Prosperon handles rendering, input, audio, and asset management. No class hierarchies, no giant scene trees.
|
||||
|
||||
## Design Goals
|
||||
|
||||
- **Minimal API surface.** A handful of modules cover sprites, input, sound, and composition. Each does one thing.
|
||||
- **Data-driven rendering.** You create sprites and set properties. The compositor handles batching, sorting, and GPU submission.
|
||||
- **Composition over inheritance.** Entity behavior comes from modules, not class trees. An entity type is a script that returns a prototype object.
|
||||
- **AI-friendly.** Many small files with simple dependencies. An LLM can read a module, understand it, and modify it.
|
||||
- **Text-first.** Assets are referenced as string paths. Levels are JSON. Everything diffs cleanly in source control.
|
||||
|
||||
## Architecture
|
||||
|
||||
Prosperon is not a monolithic engine with a global state object. It is a collection of modules:
|
||||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `core` | Main loop, window, GPU init |
|
||||
| `sprite` | Create and manage sprites |
|
||||
| `compositor` | Build render plans from scene configs |
|
||||
| `input` | Action mapping, device routing |
|
||||
| `sound` | Audio playback |
|
||||
| `world` | Entity management (add, query, update, levels) |
|
||||
| `camera` | Viewport into the world |
|
||||
| `draw2d` | Re-exports sprite, shape, text, tilemap, anim |
|
||||
| `text2d` | Text rendering |
|
||||
| `shape2d` | SDF shapes (rect, circle, ellipse, pill) |
|
||||
| `anim2d` | Sprite animation playback (aseprite/gif) |
|
||||
| `collision2d` | AABB + circle overlap queries |
|
||||
| `tilemap2d` | Grid-based tile rendering |
|
||||
| `tween` | Value interpolation |
|
||||
| `resources` | Asset path resolution |
|
||||
|
||||
Import what you need with `use()`. If you don't use a module, it doesn't load.
|
||||
|
||||
## The Language
|
||||
|
||||
Pit is an actor-based language. Programs (`.ce` files) are actors that run top-to-bottom, can register message handlers, and spawn child actors. Modules (`.cm` files) return frozen values and are cached.
|
||||
|
||||
Actor primitives: `$start`, `$receiver`, `send`, `$delay`, `$clock`, `$stop`.
|
||||
|
||||
See [Quickstart](../quickstart/) for a hands-on introduction.
|
||||
111
docs/quickstart.md
Normal file
111
docs/quickstart.md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: "Quickstart"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Quickstart
|
||||
|
||||
This quickstart walks through creating modules, programs, and running examples.
|
||||
|
||||
## Your First Module
|
||||
|
||||
A module (`.cm`) returns a frozen value — an API, a config, data.
|
||||
|
||||
Create `hello.cm`:
|
||||
|
||||
```javascript
|
||||
return {
|
||||
greet: function(name) {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Your First Program
|
||||
|
||||
A program (`.ce`) is an entry point. It runs top-to-bottom and can register handlers.
|
||||
|
||||
Create `hello.ce`:
|
||||
|
||||
```javascript
|
||||
var hello = use('hello')
|
||||
|
||||
log.console(hello.greet('Prosperon'))
|
||||
|
||||
$receiver(function(msg) {
|
||||
if (msg.type == 'ping') {
|
||||
send(msg, {type: 'pong'})
|
||||
}
|
||||
})
|
||||
|
||||
$delay(_ => $stop(), 0.1)
|
||||
```
|
||||
|
||||
## Modules vs Programs
|
||||
|
||||
| | Modules (`.cm`) | Programs (`.ce`) |
|
||||
|---|---|---|
|
||||
| Must return a value | Yes | No |
|
||||
| Return value frozen | Yes | N/A |
|
||||
| Can register handlers | No | Yes (`$receiver`) |
|
||||
| Can spawn children | No | Yes (`$start`) |
|
||||
| Cached after first load | Yes | N/A |
|
||||
|
||||
## Spawning Actors
|
||||
|
||||
Programs can spawn other programs as child actors:
|
||||
|
||||
```javascript
|
||||
$receiver(function(e) {
|
||||
if (e.type == 'greet') {
|
||||
log.console('Child greeted me')
|
||||
}
|
||||
})
|
||||
|
||||
$start(function(info) {
|
||||
if (info.type == 'greet') {
|
||||
log.console('Spawned child actor')
|
||||
}
|
||||
}, 'hello.ce')
|
||||
|
||||
$delay(_ => $stop(), 0.5)
|
||||
```
|
||||
|
||||
## Actor Primitives
|
||||
|
||||
| Primitive | Purpose |
|
||||
|-----------|---------|
|
||||
| `$start(callback, path)` | Spawn a child actor from a program |
|
||||
| `$receiver(callback)` | Register a message handler |
|
||||
| `send(target, message)` | Send a message to an actor |
|
||||
| `$delay(callback, seconds)` | Schedule a one-shot callback |
|
||||
| `$clock(callback, interval)` | Schedule a repeating callback |
|
||||
| `$stop()` | Terminate the current actor |
|
||||
|
||||
## Module Resolution
|
||||
|
||||
`use('path')` looks for modules in this order:
|
||||
|
||||
1. The current module's directory for `path.cm`
|
||||
2. The mounted roots (the program's directory is mounted) for `path.cm`
|
||||
3. Embedded/native modules if no script file is found
|
||||
|
||||
Modules compile to bytecode (`.o` files) and are cached for fast reloading.
|
||||
|
||||
## Example Games
|
||||
|
||||
Prosperon ships with example games:
|
||||
|
||||
- **Pong** — two-player paddle game
|
||||
- **Snake** — growing snake on a grid
|
||||
- **Tetris** — falling block puzzle
|
||||
- **Chess** — full chess with move validation
|
||||
- **Bunnymark** — sprite rendering benchmark
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Tutorial](tutorial/) — Build a game step by step
|
||||
- [Entities](entities/) — The world and entity model
|
||||
- [Rendering](rendering/) — How drawing works
|
||||
- [Input](input/) — Handling player input
|
||||
- [API Reference](api/) — Full module documentation
|
||||
113
docs/rendering.md
Normal file
113
docs/rendering.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: "Rendering"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Rendering
|
||||
|
||||
Prosperon's rendering is **data-driven**. You create sprites and other drawables, set their properties, and the engine handles drawing them. You never touch the GPU directly.
|
||||
|
||||
## The Model
|
||||
|
||||
Think of it like programming sprites on a classic console:
|
||||
|
||||
1. **Create** a sprite (or text, shape, tilemap, particle emitter)
|
||||
2. **Set** its position, image, layer, and visual properties
|
||||
3. The engine **draws** everything each frame, sorted and batched automatically
|
||||
|
||||
```javascript
|
||||
var sprite = use('sprite')
|
||||
|
||||
var player = sprite({
|
||||
image: "player.png",
|
||||
pos: {x: 100, y: 200},
|
||||
layer: 5
|
||||
})
|
||||
|
||||
// Move it later
|
||||
player.pos.x = 150
|
||||
player.pos.y = 250
|
||||
```
|
||||
|
||||
That's it. The sprite is registered with the engine when created and drawn every frame until destroyed.
|
||||
|
||||
## Coordinate System
|
||||
|
||||
There are two coordinate spaces:
|
||||
|
||||
**World space** is the game world. The camera's position determines what's visible. If the camera is at `[100, 100]`, that world coordinate is at the center of the screen. X+ goes right, Y+ goes up.
|
||||
|
||||
**HUD space** is the screen itself. `[0, 0]` is the bottom-left corner, `[camera.width, camera.height]` is the top-right. HUD elements don't move with the camera.
|
||||
|
||||
## Layers
|
||||
|
||||
Every drawable has a `layer` — an integer that determines draw order. Lower layers draw first (behind), higher layers draw last (in front).
|
||||
|
||||
Within a layer, objects can be sorted by different criteria:
|
||||
|
||||
- **explicit** (default) — engine may reorder for performance
|
||||
- **y-sort** — sort by Y position, useful for top-down games where "lower" objects appear in front
|
||||
|
||||
## Planes
|
||||
|
||||
Drawables are organized into **planes**. A plane is a named group that can have its own resolution, camera, and effects. By default, everything goes into the `'default'` plane.
|
||||
|
||||
Planes render independently and composite onto the screen. This lets you have a game world at 320x240 pixel-art resolution and a HUD at native resolution, for example.
|
||||
|
||||
## Effects
|
||||
|
||||
Planes and groups of sprites can have effects applied:
|
||||
|
||||
- **Bloom** — threshold + multi-pass Gaussian blur
|
||||
- **Mask** — stencil masking using other sprites as the mask shape
|
||||
|
||||
Effects are declared in the compositor config and applied automatically. You tag sprites with groups, then apply effects to those groups.
|
||||
|
||||
## Presentation Modes
|
||||
|
||||
The final composited frame is presented to the window in one of three modes:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `stretch` | Fill the window, may distort |
|
||||
| `letterbox` | Fit inside window, preserve aspect ratio, black bars |
|
||||
| `integer_scale` | Scale by whole numbers only, nearest-neighbor, pixel-perfect |
|
||||
|
||||
## Camera
|
||||
|
||||
A camera defines the viewport into the world:
|
||||
|
||||
```javascript
|
||||
var camera = use('camera')
|
||||
var cam = camera.make({
|
||||
pos: {x: 0, y: 0},
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
```
|
||||
|
||||
The camera's `width` and `height` set the game's internal resolution. The `pos` determines what world coordinate is at the center of the screen.
|
||||
|
||||
## Drawable Types
|
||||
|
||||
| Type | Module | Description |
|
||||
|------|--------|-------------|
|
||||
| Sprite | `sprite` | Textured quad with transform, tint, UV mapping |
|
||||
| Text | `text2d` | Rendered text with font, size, wrapping |
|
||||
| Shape | `shape2d` | SDF-rendered rectangles, circles, ellipses, pills |
|
||||
| Tilemap | `tilemap2d` | Grid of tiles on a single layer |
|
||||
| Particles | `particles2d` | Particle emitter producing sprite-like particles |
|
||||
| Line | `line2d` | Polylines and line segments as triangle meshes |
|
||||
|
||||
All drawable types share common properties: `pos`, `layer`, `plane`, `groups`, `visible`, `opacity`, `tint`.
|
||||
|
||||
## How It Works Internally
|
||||
|
||||
For those curious about the pipeline (you don't need to know this to use Prosperon):
|
||||
|
||||
1. Drawables register with `film2d` — the internal sprite registry
|
||||
2. Each frame, the **compositor** queries film2d for all visible drawables
|
||||
3. Drawables are sorted by layer, batched by texture/material
|
||||
4. Effect groups are rendered to intermediate targets, effects applied
|
||||
5. Planes composite to the screen via the **SDL GPU backend**
|
||||
6. The backend uses Metal, Vulkan, or DirectX depending on platform
|
||||
48
docs/resources.md
Normal file
48
docs/resources.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "Resources"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# Resources
|
||||
|
||||
Prosperon uses string paths to reference assets — images, sounds, fonts, scripts. The resource system resolves these paths and caches loaded assets.
|
||||
|
||||
## Module Resolution
|
||||
|
||||
`use('path')` loads a module by searching for `path.cm` in this order:
|
||||
|
||||
1. The current module's directory
|
||||
2. Mounted roots (the program's directory is always mounted)
|
||||
3. Embedded/native modules if no script file is found
|
||||
|
||||
Modules compile to bytecode (`.o` files) and are cached after first load. Subsequent `use()` calls return the cached value.
|
||||
|
||||
## Asset Resolution
|
||||
|
||||
When you reference an asset like `"player.png"`, the resource system searches mounted paths for a match. Assets should generally be referenced **without** a file extension — the engine tries appropriate extensions based on context:
|
||||
|
||||
- **Images:** png, qoi, gif, jpg, jpeg, ase
|
||||
- **Sounds:** wav, ogg, mp3
|
||||
- **Fonts:** ttf
|
||||
|
||||
Omitting extensions lets the engine swap optimized formats transparently. If you specify an extension, only that exact file is searched for.
|
||||
|
||||
## Caching
|
||||
|
||||
All loaded assets are cached. When two sprites reference the same image path, they share one GPU texture. Sound data, fonts, and bytecode are similarly deduplicated.
|
||||
|
||||
## Mounts
|
||||
|
||||
Prosperon uses a virtual filesystem. The write directory is set to the folder the engine runs from. That same folder is mounted as a read directory, along with the executable itself (which contains a zip archive of core engine assets).
|
||||
|
||||
Additional folders or zip archives can be mounted, allowing mods to override specific files:
|
||||
|
||||
```javascript
|
||||
// A mod zip containing sprites/bug.png will override the game's bug.png
|
||||
```
|
||||
|
||||
Because all asset references are string paths resolved through the mount system, modding is straightforward — mount a new archive and its files take priority.
|
||||
|
||||
## .prosperonignore
|
||||
|
||||
A `.prosperonignore` file in a mounted directory excludes files from resolution, similar to `.gitignore`. This is useful for keeping source assets (e.g., Aseprite working files) out of the build.
|
||||
225
docs/tutorial.md
Normal file
225
docs/tutorial.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
title: "Tutorial"
|
||||
type: docs
|
||||
---
|
||||
|
||||
# A Tutorial Introduction
|
||||
|
||||
This tutorial walks through building a simple game from scratch. By the end, you'll understand modules, programs, rendering, and input.
|
||||
|
||||
## How Prosperon Starts
|
||||
|
||||
Prosperon runs a `.ce` program file as the root actor. This is your game's entry point. There is no `config.js` or `main.js` — everything is modules you import with `use()`.
|
||||
|
||||
## Step 1: A Window
|
||||
|
||||
Create `game.ce`:
|
||||
|
||||
```javascript
|
||||
var core = use('core')
|
||||
|
||||
core.start({
|
||||
title: "My Game",
|
||||
width: 1280,
|
||||
height: 720
|
||||
})
|
||||
```
|
||||
|
||||
Run it with `prosperon game.ce`. You get a window. `core.start()` initializes the GPU backend, opens the window, and starts the main loop.
|
||||
|
||||
## Step 2: Drawing a Sprite
|
||||
|
||||
Sprites are the fundamental drawable. Create one and it appears on screen — you don't issue draw calls.
|
||||
|
||||
```javascript
|
||||
var core = use('core')
|
||||
var sprite = use('sprite')
|
||||
var compositor = use('compositor')
|
||||
var camera = use('camera')
|
||||
|
||||
var cam = camera.make({
|
||||
pos: {x: 0, y: 0},
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
|
||||
var player = sprite({
|
||||
image: "player.png",
|
||||
pos: {x: 0, y: 0},
|
||||
width: 32,
|
||||
layer: 5
|
||||
})
|
||||
|
||||
var plan = compositor.compile({
|
||||
planes: [{
|
||||
name: 'game',
|
||||
plane: 'default',
|
||||
resolution: {width: 320, height: 240},
|
||||
camera: cam,
|
||||
presentation: 'integer_scale'
|
||||
}]
|
||||
})
|
||||
|
||||
core.start({
|
||||
title: "Sprite Test",
|
||||
width: 1280,
|
||||
height: 720,
|
||||
render: function() {
|
||||
return compositor.execute(plan)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Key ideas:
|
||||
- `sprite()` creates a sprite and registers it with the engine automatically
|
||||
- The **compositor** describes how to render the scene: which planes, at what resolution, with what camera
|
||||
- Your `render` callback returns the compositor result — the engine sends it to the GPU
|
||||
|
||||
## Step 3: Moving the Sprite
|
||||
|
||||
Add an `update` callback to move things each frame:
|
||||
|
||||
```javascript
|
||||
core.start({
|
||||
title: "Moving Sprite",
|
||||
width: 1280,
|
||||
height: 720,
|
||||
update: function(dt) {
|
||||
player.pos.x += 60 * dt
|
||||
},
|
||||
render: function() {
|
||||
return compositor.execute(plan)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`dt` is the time elapsed since the last frame, in seconds. Multiply speeds by `dt` for frame-rate-independent movement.
|
||||
|
||||
## Step 4: Handling Input
|
||||
|
||||
Use the `input` module to map physical keys to named actions, then route those actions to your game objects:
|
||||
|
||||
```javascript
|
||||
var input = use('input')
|
||||
|
||||
input.configure({
|
||||
action_map: {
|
||||
move_left: ['a', 'left'],
|
||||
move_right: ['d', 'right'],
|
||||
jump: ['space']
|
||||
}
|
||||
})
|
||||
|
||||
var player_entity = {
|
||||
on_input: function(action, data) {
|
||||
if (action == 'move_left' && data.pressed) {
|
||||
player.pos.x -= 16
|
||||
}
|
||||
if (action == 'move_right' && data.pressed) {
|
||||
player.pos.x += 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var p1 = input.player1()
|
||||
p1.possess(player_entity)
|
||||
```
|
||||
|
||||
The input system handles keyboard, gamepad, and touch. You define actions once and bind them to any physical input.
|
||||
|
||||
## Step 5: Adding Sound
|
||||
|
||||
```javascript
|
||||
var sound = use('sound')
|
||||
|
||||
sound.play("jump.wav")
|
||||
```
|
||||
|
||||
Sounds are loaded by path. The engine caches audio data so loading the same sound twice reuses the existing data.
|
||||
|
||||
## Step 6: Multiple Planes
|
||||
|
||||
Real games often need separate rendering layers — pixel art at low res, HUD at native res:
|
||||
|
||||
```javascript
|
||||
var plan = compositor.compile({
|
||||
clear: {r: 0.1, g: 0.1, b: 0.2, a: 1},
|
||||
planes: [
|
||||
{
|
||||
name: 'game',
|
||||
plane: 'default',
|
||||
resolution: {width: 320, height: 240},
|
||||
camera: cam,
|
||||
layer_sort: {'5': 'y'},
|
||||
presentation: 'integer_scale'
|
||||
},
|
||||
{
|
||||
name: 'hud',
|
||||
plane: 'hud',
|
||||
resolution: {width: 1280, height: 720},
|
||||
camera: hud_cam,
|
||||
presentation: 'stretch'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Sprites belong to a plane via their `plane` property. Each plane renders at its own resolution with its own camera, then composites onto the screen in order.
|
||||
|
||||
## Step 7: Entities
|
||||
|
||||
For anything more than a few sprites, use the entity system. Define entity types as modules, create instances with overrides:
|
||||
|
||||
```javascript
|
||||
// entities/coin.cm
|
||||
var sprite = use('sprite')
|
||||
|
||||
return {
|
||||
value: 1,
|
||||
image: "coin.png",
|
||||
|
||||
init: function() {
|
||||
this.sprite = sprite({
|
||||
image: this.image,
|
||||
pos: this.pos,
|
||||
width: 16,
|
||||
layer: 3
|
||||
})
|
||||
},
|
||||
|
||||
on_destroy: function() {
|
||||
this.sprite.destroy()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// In your game program
|
||||
var world = use('world')
|
||||
var coin_proto = use('entities/coin')
|
||||
|
||||
var c = world.add_entity(coin_proto, {
|
||||
pos: {x: 50, y: 80},
|
||||
value: 5
|
||||
})
|
||||
```
|
||||
|
||||
The entity's `init()` runs after overrides are applied. The prototype defines every valid field with a default — it is the schema.
|
||||
|
||||
## Putting It Together
|
||||
|
||||
A complete minimal game has:
|
||||
|
||||
1. A `.ce` program that imports modules and calls `core.start()`
|
||||
2. Sprite and entity modules (`.cm`) that define game objects
|
||||
3. A compositor config that describes the rendering setup
|
||||
4. Input configuration that maps keys to actions
|
||||
|
||||
The engine does the rest: batching sprites, sorting layers, managing GPU resources, polling events, scheduling frames.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Graphics](../graphics/) — Sprites, text, shapes, tilemaps
|
||||
- [Compositor](../ops/) — Planes, effects, presentation modes
|
||||
- [Input](../input/) — Actions, players, control stacks
|
||||
- [Entities](../entities/) — The entity model and world system
|
||||
260
draw2d.cm
260
draw2d.cm
@@ -1,251 +1,13 @@
|
||||
var math = use('math')
|
||||
var color = use('color')
|
||||
var gamestate = use('gamestate')
|
||||
var sprite = use('sprite')
|
||||
var tilemap = use('tilemap2d')
|
||||
var text = use('text2d')
|
||||
var shape = use('shape2d')
|
||||
var anim = use('anim2d')
|
||||
|
||||
var draw = {}
|
||||
|
||||
var current_list = []
|
||||
|
||||
// Clear current list
|
||||
draw.clear = function() {
|
||||
current_list = []
|
||||
return {
|
||||
sprite,
|
||||
tilemap,
|
||||
text,
|
||||
shape,
|
||||
anim
|
||||
}
|
||||
|
||||
// Get commands from current list
|
||||
draw.get_commands = function() {
|
||||
return current_list
|
||||
}
|
||||
|
||||
// Helper to add a command
|
||||
function add_command(type, data) {
|
||||
data.cmd = type
|
||||
current_list.push(data)
|
||||
}
|
||||
|
||||
// Default geometry definitions
|
||||
var ellipse_def = {
|
||||
start: 0,
|
||||
end: 1,
|
||||
mode: 'fill',
|
||||
thickness: 1,
|
||||
}
|
||||
|
||||
var line_def = {
|
||||
thickness: 1,
|
||||
cap:"butt",
|
||||
}
|
||||
|
||||
var rect_def = {
|
||||
thickness:1,
|
||||
radius: 0
|
||||
}
|
||||
|
||||
var image_info = {
|
||||
tile_x: false,
|
||||
tile_y: false,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
mode: 'linear'
|
||||
}
|
||||
|
||||
var circle_def = {
|
||||
inner_radius:1, // percentage: 1 means filled circle
|
||||
start:0,
|
||||
end: 1,
|
||||
}
|
||||
|
||||
// Drawing functions
|
||||
draw.point = function(pos, size, opt = {}, material) {
|
||||
add_command("draw_point", {
|
||||
pos: pos,
|
||||
size: size,
|
||||
opt: opt,
|
||||
material: material
|
||||
})
|
||||
}
|
||||
|
||||
draw.ellipse = function(pos, radii, defl, material) {
|
||||
var opt = defl ? {...ellipse_def, ...defl} : ellipse_def
|
||||
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
|
||||
|
||||
add_command("draw_ellipse", {
|
||||
pos: pos,
|
||||
radii: radii,
|
||||
opt: opt,
|
||||
material: material
|
||||
})
|
||||
}
|
||||
|
||||
draw.line = function(points, defl, material)
|
||||
{
|
||||
var opt = defl ? {...line_def, ...defl} : line_def
|
||||
|
||||
add_command("draw_line", {
|
||||
points: points,
|
||||
opt: opt,
|
||||
material: material
|
||||
})
|
||||
}
|
||||
|
||||
draw.cross = function render_cross(pos, size, defl, material) {
|
||||
var a = [pos.add([0, size]), pos.add([0, -size])]
|
||||
var b = [pos.add([size, 0]), pos.add([-size, 0])]
|
||||
draw.line(a, defl, material)
|
||||
draw.line(b, defl, material)
|
||||
}
|
||||
|
||||
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, defl, material) {
|
||||
var dir = math.norm(end.sub(start))
|
||||
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
|
||||
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
|
||||
draw.line([start, end], defl, material)
|
||||
draw.line(wing1, defl, material)
|
||||
draw.line(wing2, defl, material)
|
||||
}
|
||||
|
||||
draw.rectangle = function render_rectangle(rect, defl, material = {color:{r:1,g:1,b:1,a:1}}) {
|
||||
var opt = defl ? {...rect_def, ...defl} : rect_def
|
||||
|
||||
add_command("draw_rect", {
|
||||
rect,
|
||||
opt,
|
||||
material
|
||||
})
|
||||
}
|
||||
|
||||
var slice9_info = {
|
||||
tile_top:true,
|
||||
tile_bottom:true,
|
||||
tile_left:true,
|
||||
tile_right:true,
|
||||
tile_center_x:true,
|
||||
tile_center_right:true,
|
||||
}
|
||||
|
||||
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, material) {
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
|
||||
add_command("draw_slice9", {
|
||||
image,
|
||||
rect,
|
||||
slice,
|
||||
info,
|
||||
material
|
||||
})
|
||||
}
|
||||
|
||||
draw.image = function image(image, rect, scale = {x:1,y:1}, anchor, shear, info, material) {
|
||||
if (!rect) throw Error('Need rectangle to render image.')
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
|
||||
if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.')
|
||||
|
||||
add_command("draw_image", {
|
||||
image,
|
||||
rect,
|
||||
scale,
|
||||
anchor,
|
||||
shear,
|
||||
info,
|
||||
material
|
||||
})
|
||||
}
|
||||
|
||||
draw.circle = function render_circle(pos, radius, defl, material) {
|
||||
draw.ellipse(pos, [radius,radius], defl, material)
|
||||
}
|
||||
|
||||
// wrap is the width before wrapping
|
||||
// config is any additional config to pass to the text renderer
|
||||
var text_base_config = {
|
||||
align: 'left', // left, right, center, justify
|
||||
break: 'word', // word, character
|
||||
}
|
||||
draw.text = function text(text, pos, font = 'fonts/c64.8', color = {r:1,g:1,b:1,a:1}, wrap = 0, config = {}) {
|
||||
config.align ??= text_base_config.align
|
||||
config.break ??= text_base_config.break
|
||||
|
||||
add_command("draw_text", {
|
||||
text,
|
||||
pos,
|
||||
font,
|
||||
wrap,
|
||||
material: {color},
|
||||
config
|
||||
})
|
||||
}
|
||||
|
||||
draw.grid = function grid(rect, spacing, thickness = 1, offset = {x: 0, y: 0}, material) {
|
||||
if (!rect || rect.x == null || rect.y == null ||
|
||||
rect.width == null || rect.height == null) {
|
||||
throw Error('Grid requires rect with x, y, width, height')
|
||||
}
|
||||
if (!spacing || typeof spacing.x == 'undefined' || typeof spacing.y == 'undefined') {
|
||||
throw Error('Grid requires spacing with x and y')
|
||||
}
|
||||
|
||||
var left = rect.x
|
||||
var right = rect.x + rect.width
|
||||
var top = rect.y
|
||||
var bottom = rect.y + rect.height
|
||||
|
||||
// Apply offset and align to grid
|
||||
var start_x = Math.floor((left - offset.x) / spacing.x) * spacing.x + offset.x
|
||||
var end_x = Math.ceil((right - offset.x) / spacing.x) * spacing.x + offset.x
|
||||
var start_y = Math.floor((top - offset.y) / spacing.y) * spacing.y + offset.y
|
||||
var end_y = Math.ceil((bottom - offset.y) / spacing.y) * spacing.y + offset.y
|
||||
|
||||
// Draw vertical lines
|
||||
for (var x = start_x; x <= end_x; x += spacing.x) {
|
||||
if (x >= left && x <= right) {
|
||||
var line_top = Math.max(top, start_y)
|
||||
var line_bottom = Math.min(bottom, end_y)
|
||||
draw.line([[x, line_top], [x, line_bottom]], {thickness: thickness}, material)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw horizontal lines
|
||||
for (var y = start_y; y <= end_y; y += spacing.y) {
|
||||
if (y >= top && y <= bottom) {
|
||||
var line_left = Math.max(left, start_x)
|
||||
var line_right = Math.min(right, end_x)
|
||||
draw.line([[line_left, y], [line_right, y]], {thickness: thickness}, material)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw.scissor = function(rect)
|
||||
{
|
||||
var screen_rect = null
|
||||
if (rect && gamestate.camera) {
|
||||
var bottom_left = gamestate.camera.world_to_window(rect.x, rect.y)
|
||||
var top_right = gamestate.camera.world_to_window(rect.x + rect.width, rect.y + rect.height)
|
||||
var screen_left = bottom_left.x
|
||||
var screen_top = bottom_left.y
|
||||
var screen_right = top_right.x
|
||||
var screen_bottom = top_right.y
|
||||
|
||||
screen_rect = {
|
||||
x: Math.round(screen_left),
|
||||
y: Math.round(screen_top),
|
||||
width: Math.round(screen_right - screen_left),
|
||||
height: Math.round(screen_bottom - screen_top)
|
||||
}
|
||||
|
||||
// TODO: must be a better way than manually inverting here. Some camera specific function.
|
||||
var sensor = gamestate.camera.sensor()
|
||||
screen_rect.y = sensor.height - screen_rect.y - screen_rect.height
|
||||
}
|
||||
|
||||
current_list.push({
|
||||
cmd: "scissor",
|
||||
rect: screen_rect
|
||||
})
|
||||
}
|
||||
|
||||
draw.add_command = function(cmd)
|
||||
{
|
||||
current_list.push(cmd)
|
||||
}
|
||||
|
||||
return draw
|
||||
66
ease.cm
66
ease.cm
@@ -1,3 +1,5 @@
|
||||
var math = use('math/radians')
|
||||
|
||||
var Ease = {
|
||||
linear(t) {
|
||||
return t
|
||||
@@ -19,16 +21,16 @@ function make_easing_fns(num) {
|
||||
var obj = {}
|
||||
|
||||
obj.in = function (t) {
|
||||
return Math.pow(t, num)
|
||||
return math.power(t, num)
|
||||
}
|
||||
|
||||
obj.out = function (t) {
|
||||
return 1 - Math.pow(1 - t, num)
|
||||
return 1 - math.power(1 - t, num)
|
||||
}
|
||||
|
||||
var mult = Math.pow(2, num - 1)
|
||||
var mult = math.power(2, num - 1)
|
||||
obj.inout = function (t) {
|
||||
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
|
||||
return t < 0.5 ? mult * math.power(t, num) : 1 - math.power(-2 * t + 2, num) / 2
|
||||
}
|
||||
|
||||
return obj
|
||||
@@ -41,10 +43,10 @@ Ease.quint = make_easing_fns(5)
|
||||
|
||||
Ease.expo = {
|
||||
in(t) {
|
||||
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
|
||||
return t == 0 ? 0 : math.power(2, 10 * t - 10)
|
||||
},
|
||||
out(t) {
|
||||
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
||||
return t == 1 ? 1 : 1 - math.power(2, -10 * t)
|
||||
},
|
||||
inout(t) {
|
||||
return t == 0
|
||||
@@ -52,8 +54,8 @@ Ease.expo = {
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5
|
||||
? Math.pow(2, 20 * t - 10) / 2
|
||||
: (2 - Math.pow(2, -20 * t + 10)) / 2
|
||||
? math.power(2, 20 * t - 10) / 2
|
||||
: (2 - math.power(2, -20 * t + 10)) / 2
|
||||
},
|
||||
}
|
||||
|
||||
@@ -64,13 +66,19 @@ Ease.bounce = {
|
||||
out(t) {
|
||||
var n1 = 7.5625
|
||||
var d1 = 2.75
|
||||
if (t < 1 / d1) {
|
||||
return n1 * t * t
|
||||
} else if (t < 2 / d1) {
|
||||
return n1 * (t -= 1.5 / d1) * t + 0.75
|
||||
} else if (t < 2.5 / d1) {
|
||||
return n1 * (t -= 2.25 / d1) * t + 0.9375
|
||||
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
|
||||
var u = t
|
||||
if (u < 1 / d1) {
|
||||
return n1 * u * u
|
||||
} else if (u < 2 / d1) {
|
||||
u = u - 1.5 / d1
|
||||
return n1 * u * u + 0.75
|
||||
} else if (u < 2.5 / d1) {
|
||||
u = u - 2.25 / d1
|
||||
return n1 * u * u + 0.9375
|
||||
} else {
|
||||
u = u - 2.625 / d1
|
||||
return n1 * u * u + 0.984375
|
||||
}
|
||||
},
|
||||
inout(t) {
|
||||
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
|
||||
@@ -79,13 +87,13 @@ Ease.bounce = {
|
||||
|
||||
Ease.sine = {
|
||||
in(t) {
|
||||
return 1 - Math.cos((t * Math.PI) / 2)
|
||||
return 1 - math.cosine((t * pi) / 2)
|
||||
},
|
||||
out(t) {
|
||||
return Math.sin((t * Math.PI) / 2)
|
||||
return math.sine((t * pi) / 2)
|
||||
},
|
||||
inout(t) {
|
||||
return -(Math.cos(Math.PI * t) - 1) / 2
|
||||
return -(math.cosine(pi * t) - 1) / 2
|
||||
},
|
||||
}
|
||||
|
||||
@@ -95,16 +103,16 @@ Ease.elastic = {
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: -Math.pow(2, 10 * t - 10) *
|
||||
Math.sin((t * 10 - 10.75) * this.c4)
|
||||
: -math.power(2, 10 * t - 10) *
|
||||
math.sine((t * 10 - 10.75) * this.c4)
|
||||
},
|
||||
out(t) {
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: Math.pow(2, -10 * t) *
|
||||
Math.sin((t * 10 - 0.75) * this.c4) +
|
||||
: math.power(2, -10 * t) *
|
||||
math.sine((t * 10 - 0.75) * this.c4) +
|
||||
1
|
||||
},
|
||||
inout(t) {
|
||||
@@ -113,13 +121,13 @@ Ease.elastic = {
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5
|
||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
|
||||
? -(math.power(2, 20 * t - 10) * math.sine((20 * t - 11.125) * this.c5)) / 2
|
||||
: (math.power(2, -20 * t + 10) * math.sine((20 * t - 11.125) * this.c5)) / 2 + 1
|
||||
},
|
||||
}
|
||||
|
||||
Ease.elastic.c4 = (2 * Math.PI) / 3
|
||||
Ease.elastic.c5 = (2 * Math.PI) / 4.5
|
||||
Ease.elastic.c4 = (2 * pi) / 3
|
||||
Ease.elastic.c5 = (2 * pi) / 4.5
|
||||
|
||||
Ease.zoom = {
|
||||
// Creates a smooth zoom that maintains constant perceptual speed
|
||||
@@ -128,9 +136,9 @@ Ease.zoom = {
|
||||
return function(t) {
|
||||
if (t == 0) return 0
|
||||
if (t == 1) return 1
|
||||
if (Math.abs(ratio - 1) < 0.001) return t
|
||||
if (abs(ratio - 1) < 0.001) return t
|
||||
// Position interpolation formula: (r^t - 1) / (r - 1)
|
||||
return (Math.pow(ratio, t) - 1) / (ratio - 1)
|
||||
return (math.power(ratio, t) - 1) / (ratio - 1)
|
||||
}
|
||||
},
|
||||
// Exponential interpolation for zoom values
|
||||
@@ -140,7 +148,7 @@ Ease.zoom = {
|
||||
if (t == 0) return startZoom
|
||||
if (t == 1) return endZoom
|
||||
// Scale := Exp(LinearInterpolate(Ln(Scale1), Ln(Scale2), t))
|
||||
return Math.exp(Math.log(startZoom) + t * (Math.log(endZoom) - Math.log(startZoom)))
|
||||
return math.exp(math.ln(startZoom) + t * (math.ln(endZoom) - math.ln(startZoom)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
391
effects.cm
Normal file
391
effects.cm
Normal file
@@ -0,0 +1,391 @@
|
||||
// effects.cm - Effect Registry with Built-in Effect Recipes
|
||||
//
|
||||
// Effects are defined as recipes that produce abstract render passes.
|
||||
// The compositor uses these recipes to build render plans.
|
||||
// Backends implement the actual shader logic.
|
||||
|
||||
var effects = {}
|
||||
|
||||
// Effect registry
|
||||
var _effects = {}
|
||||
|
||||
effects.register = function(name, deff) {
|
||||
_effects[name] = deff
|
||||
}
|
||||
|
||||
effects.get = function(name) {
|
||||
return _effects[name]
|
||||
}
|
||||
|
||||
effects.list = function() {
|
||||
return array(_effects)
|
||||
}
|
||||
|
||||
// Built-in effect: Bloom
|
||||
effects.register('bloom', {
|
||||
type: 'multi_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
threshold: {default: 0.8, type: 'float'},
|
||||
intensity: {default: 1.0, type: 'float'},
|
||||
blur_passes: {default: 3, type: 'int'}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
var passes = []
|
||||
var size = ctx.target_size
|
||||
|
||||
// Threshold extraction
|
||||
var thresh_target = ctx.alloc_target(size.width, size.height, 'bloom_thresh')
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'threshold',
|
||||
input: input,
|
||||
output: thresh_target,
|
||||
uniforms: {
|
||||
threshold: params.threshold != null ? params.threshold : 0.8,
|
||||
intensity: params.intensity != null ? params.intensity : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Blur ping-pong
|
||||
var blur_a = ctx.alloc_target(size.width, size.height, 'bloom_blur_a')
|
||||
var blur_b = ctx.alloc_target(size.width, size.height, 'bloom_blur_b')
|
||||
var blur_src = thresh_target
|
||||
var texel = {x: 1 / size.width, y: 1 / size.height}
|
||||
var blur_count = params.blur_passes != null ? params.blur_passes : 3
|
||||
var i = 0
|
||||
|
||||
for (i = 0; i < blur_count; i++) {
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
input: blur_src,
|
||||
output: blur_a,
|
||||
uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
|
||||
}
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
input: blur_a,
|
||||
output: blur_b,
|
||||
uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
|
||||
}
|
||||
blur_src = blur_b
|
||||
}
|
||||
|
||||
// Additive composite
|
||||
passes[] = {
|
||||
type: 'composite',
|
||||
base: input,
|
||||
overlay: blur_src,
|
||||
output: output,
|
||||
blend: 'add'
|
||||
}
|
||||
|
||||
return passes
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Mask
|
||||
effects.register('mask', {
|
||||
type: 'conditional',
|
||||
requires_target: true,
|
||||
params: {
|
||||
source: {required: false}, // Legacy: direct handle reference
|
||||
source_id: {required: false}, // New: ID string for film2d.get()
|
||||
channel: {default: 'alpha'},
|
||||
invert: {default: false},
|
||||
soft: {default: false},
|
||||
space: {default: 'local'}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
var passes = []
|
||||
var size = ctx.target_size
|
||||
|
||||
// Check backend capabilities for stencil optimization
|
||||
if (!params.soft && ctx.backend && ctx.backend.caps && ctx.backend.caps.has_stencil) {
|
||||
// Could use stencil - but for now use texture approach
|
||||
}
|
||||
|
||||
if (ctx.backend && ctx.backend.caps && !ctx.backend.caps.has_render_targets) {
|
||||
// Can't do masks on this backend - just pass through
|
||||
return [{type: 'blit', source: input, dest: output}]
|
||||
}
|
||||
|
||||
// Resolve mask source
|
||||
var mask_source = params.source
|
||||
if (params.source_id && ctx.film2d) {
|
||||
mask_source = ctx.film2d.get(params.source_id)
|
||||
}
|
||||
|
||||
if (!mask_source) {
|
||||
// No mask source - pass through
|
||||
return [{type: 'blit', source: input, dest: output}]
|
||||
}
|
||||
|
||||
// Render mask source to target
|
||||
var mask_target = ctx.alloc_target(size.width, size.height, 'mask_src')
|
||||
passes[] = {
|
||||
type: 'render_subtree',
|
||||
root: mask_source,
|
||||
output: mask_target,
|
||||
clear: {r: 0, g: 0, b: 0, a: 0},
|
||||
space: params.space || 'local'
|
||||
}
|
||||
|
||||
// Apply mask shader
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'mask',
|
||||
inputs: [input, mask_target],
|
||||
output: output,
|
||||
uniforms: {
|
||||
channel: params.channel == 'alpha' ? 0 : 1,
|
||||
invert: params.invert ? 1 : 0
|
||||
}
|
||||
}
|
||||
|
||||
return passes
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: CRT
|
||||
effects.register('crt', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
curvature: {default: 0.1},
|
||||
scanline_intensity: {default: 0.3},
|
||||
vignette: {default: 0.2}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'crt',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
curvature: params.curvature != null ? params.curvature : 0.1,
|
||||
scanline_intensity: params.scanline_intensity != null ? params.scanline_intensity : 0.3,
|
||||
vignette: params.vignette != null ? params.vignette : 0.2,
|
||||
resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Blur
|
||||
effects.register('blur', {
|
||||
type: 'multi_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
passes: {default: 2}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
var passes = []
|
||||
var size = ctx.target_size
|
||||
var texel = {x: 1 / size.width, y: 1 / size.height}
|
||||
var blur_a = ctx.alloc_target(size.width, size.height, 'blur_a')
|
||||
var blur_b = ctx.alloc_target(size.width, size.height, 'blur_b')
|
||||
var src = input
|
||||
var blur_count = params.passes != null ? params.passes : 2
|
||||
var i = 0
|
||||
|
||||
for (i = 0; i < blur_count; i++) {
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
input: src,
|
||||
output: blur_a,
|
||||
uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
|
||||
}
|
||||
passes[] = {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
input: blur_a,
|
||||
output: blur_b,
|
||||
uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
|
||||
}
|
||||
src = blur_b
|
||||
}
|
||||
|
||||
// Final blit to output
|
||||
passes[] = {type: 'blit', source: src, dest: output}
|
||||
return passes
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Accumulator (motion blur / trails)
|
||||
effects.register('accumulator', {
|
||||
type: 'stateful',
|
||||
requires_target: true,
|
||||
params: {
|
||||
decay: {default: 0.9}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
var size = ctx.target_size
|
||||
var prev = ctx.get_persistent_target('accum_prev', size.width, size.height)
|
||||
var curr = ctx.get_persistent_target('accum_curr', size.width, size.height)
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'shader',
|
||||
shader: 'accumulator',
|
||||
inputs: [input, prev],
|
||||
output: curr,
|
||||
uniforms: {decay: params.decay != null ? params.decay : 0.9}
|
||||
},
|
||||
{type: 'blit', source: curr, dest: prev},
|
||||
{type: 'blit', source: curr, dest: output}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Pixelate
|
||||
effects.register('pixelate', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
pixel_size: {default: 4}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'pixelate',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
pixel_size: params.pixel_size != null ? params.pixel_size : 4,
|
||||
resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Color grading
|
||||
effects.register('color_grade', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
brightness: {default: 0},
|
||||
contrast: {default: 1},
|
||||
saturation: {default: 1},
|
||||
gamma: {default: 1}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'color_grade',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
brightness: params.brightness != null ? params.brightness : 0,
|
||||
contrast: params.contrast != null ? params.contrast : 1,
|
||||
saturation: params.saturation != null ? params.saturation : 1,
|
||||
gamma: params.gamma != null ? params.gamma : 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Vignette
|
||||
effects.register('vignette', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
intensity: {default: 0.3},
|
||||
softness: {default: 0.5}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'vignette',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
intensity: params.intensity != null ? params.intensity : 0.3,
|
||||
softness: params.softness != null ? params.softness : 0.5,
|
||||
resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Chromatic aberration
|
||||
effects.register('chromatic', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
offset: {default: 0.005}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'chromatic',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
offset: params.offset != null ? params.offset : 0.005
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Built-in effect: Outline
|
||||
effects.register('outline', {
|
||||
type: 'single_pass',
|
||||
requires_target: true,
|
||||
params: {
|
||||
color: {default: {r: 0, g: 0, b: 0, a: 1}},
|
||||
width: {default: 1}
|
||||
},
|
||||
build_passes: function(input, output, params, ctx) {
|
||||
var c = params.color || {r: 0, g: 0, b: 0, a: 1}
|
||||
return [{
|
||||
type: 'shader',
|
||||
shader: 'outline',
|
||||
input: input,
|
||||
output: output,
|
||||
uniforms: {
|
||||
outline_color: c,
|
||||
outline_width: params.width != null ? params.width : 1,
|
||||
texel_size: {x: 1 / ctx.target_size.width, y: 1 / ctx.target_size.height}
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// Helper: Check if an effect requires a render target
|
||||
effects.requires_target = function(effect_type) {
|
||||
var deff = _effects[effect_type]
|
||||
return deff ? (deff.requires_target || false) : false
|
||||
}
|
||||
|
||||
// Helper: Get default params for an effect
|
||||
effects.default_params = function(effect_type) {
|
||||
var deff = _effects[effect_type]
|
||||
if (!deff || !deff.params) return {}
|
||||
|
||||
var defaults = {}
|
||||
arrfor(array(deff.params), k => {
|
||||
if (deff.params[k].default != null) {
|
||||
defaults[k] = deff.params[k].default
|
||||
}
|
||||
})
|
||||
return defaults
|
||||
}
|
||||
|
||||
// Helper: Validate effect params
|
||||
effects.validate_params = function(effect_type, params) {
|
||||
var deff = _effects[effect_type]
|
||||
if (!deff || !deff.params) return true
|
||||
|
||||
arrfor(array(deff.params), k => {
|
||||
if (deff.params[k].required && params[k] == null) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return effects
|
||||
87
emacs.cm
Normal file
87
emacs.cm
Normal file
@@ -0,0 +1,87 @@
|
||||
// Emacs-style input handler
|
||||
// Converts raw input with modifiers to standard emacs notation
|
||||
|
||||
// Valid keys for emacs bindings - only process these
|
||||
var valid_keys = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'return', 'enter', 'space', 'escape', 'tab', 'backspace', 'delete',
|
||||
'up', 'down', 'left', 'right', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
|
||||
]
|
||||
|
||||
var action = {}
|
||||
action.prefix_key = null // Current prefix key (C-x or C-c)
|
||||
action.on_input = function(action_id, action_data)
|
||||
{
|
||||
if (!action_data.pressed) return
|
||||
|
||||
if (find(valid_keys, action_id) == null)
|
||||
return
|
||||
|
||||
// Only process key events with modifiers or if we're waiting for a prefix continuation
|
||||
if (!action_data.ctrl && !action_data.alt && !this.prefix_key)
|
||||
return
|
||||
|
||||
var emacs_notation = ""
|
||||
|
||||
// Build emacs notation
|
||||
if (action_data.ctrl)
|
||||
emacs_notation += "C-"
|
||||
|
||||
if (action_data.alt)
|
||||
emacs_notation += "M-"
|
||||
|
||||
// Convert action_id to emacs key notation
|
||||
var key = action_id
|
||||
if (length(key) == 1) {
|
||||
// Single character keys
|
||||
emacs_notation += lower(key)
|
||||
} else {
|
||||
// Handle special keys
|
||||
if (key == 'return' || key == 'enter') {
|
||||
emacs_notation += "RET"
|
||||
} else if (key == 'space') {
|
||||
emacs_notation += "SPC"
|
||||
} else if (key == 'escape') {
|
||||
emacs_notation += "ESC"
|
||||
} else if (key == 'tab') {
|
||||
emacs_notation += "TAB"
|
||||
} else if (key == 'backspace') {
|
||||
emacs_notation += "DEL"
|
||||
} else if (key == 'delete') {
|
||||
emacs_notation += "delete"
|
||||
} else if (key == 'up') {
|
||||
emacs_notation += "up"
|
||||
} else if (key == 'down') {
|
||||
emacs_notation += "down"
|
||||
} else if (key == 'left') {
|
||||
emacs_notation += "left"
|
||||
} else if (key == 'right') {
|
||||
emacs_notation += "right"
|
||||
} else {
|
||||
emacs_notation += key
|
||||
}
|
||||
}
|
||||
|
||||
// Handle prefix keys C-x and C-c
|
||||
if (emacs_notation == "C-x" || emacs_notation == "C-c") {
|
||||
this.prefix_key = emacs_notation
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a prefix key, build the full command
|
||||
var full_command = null
|
||||
if (this.prefix_key) {
|
||||
full_command = this.prefix_key + " " + emacs_notation
|
||||
this.prefix_key = null // Reset prefix key
|
||||
// scene.recurse(game.root, 'on_input', [full_command, action_data])
|
||||
} else {
|
||||
// scene.recurse(game.root, 'on_input', [emacs_notation, action_data])
|
||||
}
|
||||
}
|
||||
|
||||
return function()
|
||||
{
|
||||
var obj = meme(action)
|
||||
return obj
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user