pixospritz-specs 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -0
- package/constants/directions.json +67 -0
- package/constants/events.json +137 -0
- package/constants/shader-types.json +208 -0
- package/formats/manifest.schema.json +206 -0
- package/formats/map.schema.json +278 -0
- package/formats/save.schema.json +226 -0
- package/formats/sprite.schema.json +184 -0
- package/generate-c-headers.cjs +353 -0
- package/index.js +121 -0
- package/math/camera.spec.json +157 -0
- package/math/matrix.spec.json +160 -0
- package/math/vector.spec.json +137 -0
- package/package.json +36 -0
- package/shaders/common/lighting.glsl +150 -0
- package/shaders/common/transforms.glsl +164 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pixospritz.dev/schemas/matrix.spec.json",
|
|
4
|
+
"title": "Matrix Specification",
|
|
5
|
+
"description": "Shared matrix math specifications for JS and C engines",
|
|
6
|
+
|
|
7
|
+
"definitions": {
|
|
8
|
+
"Matrix4": {
|
|
9
|
+
"description": "4x4 transformation matrix in column-major order (WebGL/OpenGL compatible)",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"data": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": { "type": "number" },
|
|
15
|
+
"minItems": 16,
|
|
16
|
+
"maxItems": 16,
|
|
17
|
+
"description": "16 floats in column-major order: [m00, m10, m20, m30, m01, m11, m21, m31, ...]"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": ["data"]
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
"Matrix3": {
|
|
24
|
+
"description": "3x3 matrix for rotations and normals",
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"data": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": { "type": "number" },
|
|
30
|
+
"minItems": 9,
|
|
31
|
+
"maxItems": 9
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["data"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"operations": {
|
|
39
|
+
"Matrix4": {
|
|
40
|
+
"identity": {
|
|
41
|
+
"signature": "() -> Matrix4",
|
|
42
|
+
"description": "Create identity matrix",
|
|
43
|
+
"result": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
44
|
+
"implementation": {
|
|
45
|
+
"js": "new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])",
|
|
46
|
+
"c": "mat4_identity()"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"multiply": {
|
|
50
|
+
"signature": "(a: Matrix4, b: Matrix4) -> Matrix4",
|
|
51
|
+
"description": "Matrix multiplication (a * b)",
|
|
52
|
+
"implementation": {
|
|
53
|
+
"js": "mat4_multiply(a, b)",
|
|
54
|
+
"c": "mat4_multiply(a, b)"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"translate": {
|
|
58
|
+
"signature": "(m: Matrix4, v: Vector3) -> Matrix4",
|
|
59
|
+
"description": "Apply translation",
|
|
60
|
+
"implementation": {
|
|
61
|
+
"js": "mat4_translate(m, v.x, v.y, v.z)",
|
|
62
|
+
"c": "mat4_translate(m, v.x, v.y, v.z)"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"rotate": {
|
|
66
|
+
"signature": "(m: Matrix4, angle: number, axis: Vector3) -> Matrix4",
|
|
67
|
+
"description": "Apply rotation around axis (angle in radians)",
|
|
68
|
+
"implementation": {
|
|
69
|
+
"js": "mat4_rotate(m, angle, axis.x, axis.y, axis.z)",
|
|
70
|
+
"c": "mat4_rotate(m, angle, axis.x, axis.y, axis.z)"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"rotateX": {
|
|
74
|
+
"signature": "(m: Matrix4, angle: number) -> Matrix4",
|
|
75
|
+
"description": "Rotate around X axis",
|
|
76
|
+
"implementation": {
|
|
77
|
+
"js": "mat4_rotateX(m, angle)",
|
|
78
|
+
"c": "mat4_rotate_x(m, angle)"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"rotateY": {
|
|
82
|
+
"signature": "(m: Matrix4, angle: number) -> Matrix4",
|
|
83
|
+
"description": "Rotate around Y axis",
|
|
84
|
+
"implementation": {
|
|
85
|
+
"js": "mat4_rotateY(m, angle)",
|
|
86
|
+
"c": "mat4_rotate_y(m, angle)"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"rotateZ": {
|
|
90
|
+
"signature": "(m: Matrix4, angle: number) -> Matrix4",
|
|
91
|
+
"description": "Rotate around Z axis",
|
|
92
|
+
"implementation": {
|
|
93
|
+
"js": "mat4_rotateZ(m, angle)",
|
|
94
|
+
"c": "mat4_rotate_z(m, angle)"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"scale": {
|
|
98
|
+
"signature": "(m: Matrix4, v: Vector3) -> Matrix4",
|
|
99
|
+
"description": "Apply scaling",
|
|
100
|
+
"implementation": {
|
|
101
|
+
"js": "mat4_scale(m, v.x, v.y, v.z)",
|
|
102
|
+
"c": "mat4_scale(m, v.x, v.y, v.z)"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"perspective": {
|
|
106
|
+
"signature": "(fov: number, aspect: number, near: number, far: number) -> Matrix4",
|
|
107
|
+
"description": "Create perspective projection matrix (fov in radians)",
|
|
108
|
+
"implementation": {
|
|
109
|
+
"js": "mat4_perspective(fov, aspect, near, far)",
|
|
110
|
+
"c": "mat4_perspective(fov, aspect, near, far)"
|
|
111
|
+
},
|
|
112
|
+
"notes": "fov is vertical field of view in radians"
|
|
113
|
+
},
|
|
114
|
+
"orthographic": {
|
|
115
|
+
"signature": "(left: number, right: number, bottom: number, top: number, near: number, far: number) -> Matrix4",
|
|
116
|
+
"description": "Create orthographic projection matrix",
|
|
117
|
+
"implementation": {
|
|
118
|
+
"js": "mat4_ortho(left, right, bottom, top, near, far)",
|
|
119
|
+
"c": "mat4_ortho(left, right, bottom, top, near, far)"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"lookAt": {
|
|
123
|
+
"signature": "(eye: Vector3, target: Vector3, up: Vector3) -> Matrix4",
|
|
124
|
+
"description": "Create view matrix looking from eye to target",
|
|
125
|
+
"implementation": {
|
|
126
|
+
"js": "mat4_lookAt(eye, target, up)",
|
|
127
|
+
"c": "mat4_look_at(eye, target, up)"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"invert": {
|
|
131
|
+
"signature": "(m: Matrix4) -> Matrix4",
|
|
132
|
+
"description": "Compute inverse matrix",
|
|
133
|
+
"implementation": {
|
|
134
|
+
"js": "mat4_invert(m)",
|
|
135
|
+
"c": "mat4_invert(m)"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"transpose": {
|
|
139
|
+
"signature": "(m: Matrix4) -> Matrix4",
|
|
140
|
+
"description": "Transpose matrix",
|
|
141
|
+
"implementation": {
|
|
142
|
+
"js": "mat4_transpose(m)",
|
|
143
|
+
"c": "mat4_transpose(m)"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
"layout": {
|
|
150
|
+
"description": "Column-major order for WebGL/OpenGL compatibility",
|
|
151
|
+
"indexMapping": {
|
|
152
|
+
"m00": 0, "m10": 1, "m20": 2, "m30": 3,
|
|
153
|
+
"m01": 4, "m11": 5, "m21": 6, "m31": 7,
|
|
154
|
+
"m02": 8, "m12": 9, "m22": 10, "m32": 11,
|
|
155
|
+
"m03": 12, "m13": 13, "m23": 14, "m33": 15
|
|
156
|
+
},
|
|
157
|
+
"translationIndices": [12, 13, 14],
|
|
158
|
+
"scaleIndices": [0, 5, 10]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pixospritz.dev/schemas/vector.spec.json",
|
|
4
|
+
"title": "Vector Specification",
|
|
5
|
+
"description": "Shared vector math specifications for JS and C engines",
|
|
6
|
+
|
|
7
|
+
"definitions": {
|
|
8
|
+
"Vector2": {
|
|
9
|
+
"description": "2D vector/coordinate",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"x": { "type": "number", "default": 0 },
|
|
13
|
+
"y": { "type": "number", "default": 0 }
|
|
14
|
+
},
|
|
15
|
+
"required": ["x", "y"]
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
"Vector3": {
|
|
19
|
+
"description": "3D vector",
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"x": { "type": "number", "default": 0 },
|
|
23
|
+
"y": { "type": "number", "default": 0 },
|
|
24
|
+
"z": { "type": "number", "default": 0 }
|
|
25
|
+
},
|
|
26
|
+
"required": ["x", "y", "z"]
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"Vector4": {
|
|
30
|
+
"description": "4D vector (homogeneous coordinates)",
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"x": { "type": "number", "default": 0 },
|
|
34
|
+
"y": { "type": "number", "default": 0 },
|
|
35
|
+
"z": { "type": "number", "default": 0 },
|
|
36
|
+
"w": { "type": "number", "default": 1 }
|
|
37
|
+
},
|
|
38
|
+
"required": ["x", "y", "z", "w"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
"operations": {
|
|
43
|
+
"Vector3": {
|
|
44
|
+
"add": {
|
|
45
|
+
"signature": "(a: Vector3, b: Vector3) -> Vector3",
|
|
46
|
+
"description": "Component-wise addition",
|
|
47
|
+
"implementation": {
|
|
48
|
+
"js": "{ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }",
|
|
49
|
+
"c": "vec3_add(a, b)"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"sub": {
|
|
53
|
+
"signature": "(a: Vector3, b: Vector3) -> Vector3",
|
|
54
|
+
"description": "Component-wise subtraction",
|
|
55
|
+
"implementation": {
|
|
56
|
+
"js": "{ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }",
|
|
57
|
+
"c": "vec3_sub(a, b)"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"scale": {
|
|
61
|
+
"signature": "(v: Vector3, s: number) -> Vector3",
|
|
62
|
+
"description": "Scalar multiplication",
|
|
63
|
+
"implementation": {
|
|
64
|
+
"js": "{ x: v.x * s, y: v.y * s, z: v.z * s }",
|
|
65
|
+
"c": "vec3_scale(v, s)"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"dot": {
|
|
69
|
+
"signature": "(a: Vector3, b: Vector3) -> number",
|
|
70
|
+
"description": "Dot product",
|
|
71
|
+
"implementation": {
|
|
72
|
+
"js": "a.x * b.x + a.y * b.y + a.z * b.z",
|
|
73
|
+
"c": "vec3_dot(a, b)"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"cross": {
|
|
77
|
+
"signature": "(a: Vector3, b: Vector3) -> Vector3",
|
|
78
|
+
"description": "Cross product",
|
|
79
|
+
"implementation": {
|
|
80
|
+
"js": "{ x: a.y * b.z - a.z * b.y, y: a.z * b.x - a.x * b.z, z: a.x * b.y - a.y * b.x }",
|
|
81
|
+
"c": "vec3_cross(a, b)"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"length": {
|
|
85
|
+
"signature": "(v: Vector3) -> number",
|
|
86
|
+
"description": "Vector magnitude",
|
|
87
|
+
"implementation": {
|
|
88
|
+
"js": "Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)",
|
|
89
|
+
"c": "vec3_length(v)"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"normalize": {
|
|
93
|
+
"signature": "(v: Vector3) -> Vector3",
|
|
94
|
+
"description": "Unit vector",
|
|
95
|
+
"implementation": {
|
|
96
|
+
"js": "const len = length(v); return len > 0 ? scale(v, 1/len) : { x: 0, y: 0, z: 0 }",
|
|
97
|
+
"c": "vec3_normalize(v)"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"distance": {
|
|
101
|
+
"signature": "(a: Vector3, b: Vector3) -> number",
|
|
102
|
+
"description": "Euclidean distance between two points",
|
|
103
|
+
"implementation": {
|
|
104
|
+
"js": "length(sub(b, a))",
|
|
105
|
+
"c": "vec3_distance(a, b)"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"lerp": {
|
|
109
|
+
"signature": "(a: Vector3, b: Vector3, t: number) -> Vector3",
|
|
110
|
+
"description": "Linear interpolation",
|
|
111
|
+
"implementation": {
|
|
112
|
+
"js": "{ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t, z: a.z + (b.z - a.z) * t }",
|
|
113
|
+
"c": "vec3_lerp(a, b, t)"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"negate": {
|
|
117
|
+
"signature": "(v: Vector3) -> Vector3",
|
|
118
|
+
"description": "Negate all components",
|
|
119
|
+
"implementation": {
|
|
120
|
+
"js": "{ x: -v.x, y: -v.y, z: -v.z }",
|
|
121
|
+
"c": "vec3_negate(v)"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
"constants": {
|
|
128
|
+
"ZERO": { "x": 0, "y": 0, "z": 0 },
|
|
129
|
+
"ONE": { "x": 1, "y": 1, "z": 1 },
|
|
130
|
+
"UP": { "x": 0, "y": 1, "z": 0 },
|
|
131
|
+
"DOWN": { "x": 0, "y": -1, "z": 0 },
|
|
132
|
+
"LEFT": { "x": -1, "y": 0, "z": 0 },
|
|
133
|
+
"RIGHT": { "x": 1, "y": 0, "z": 0 },
|
|
134
|
+
"FORWARD": { "x": 0, "y": 0, "z": -1 },
|
|
135
|
+
"BACK": { "x": 0, "y": 0, "z": 1 }
|
|
136
|
+
}
|
|
137
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pixospritz-specs",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Shared specifications, schemas, and constants for PixoSpritz engines",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./math/*": "./math/*.json",
|
|
10
|
+
"./formats/*": "./formats/*.json",
|
|
11
|
+
"./constants/*": "./constants/*.json",
|
|
12
|
+
"./shaders/*": "./shaders/*"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"generate-c-headers": "./generate-c-headers.cjs"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"generate": "node generate-c-headers.cjs",
|
|
19
|
+
"generate:core-c": "node generate-c-headers.cjs --output-dir ../core-c/src/generated"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"index.js",
|
|
23
|
+
"math/",
|
|
24
|
+
"formats/",
|
|
25
|
+
"constants/",
|
|
26
|
+
"shaders/",
|
|
27
|
+
"generate-c-headers.cjs"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"pixospritz",
|
|
31
|
+
"game-engine",
|
|
32
|
+
"specifications"
|
|
33
|
+
],
|
|
34
|
+
"author": "ConflictingTheories",
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Lighting Calculations
|
|
3
|
+
* Shared GLSL fragment for both JS (WebGL) and C (OpenGL) engines
|
|
4
|
+
*
|
|
5
|
+
* Include this in fragment shaders that need lighting:
|
|
6
|
+
* #include "lighting.glsl"
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Maximum number of lights supported
|
|
10
|
+
#define MAX_LIGHTS 8
|
|
11
|
+
|
|
12
|
+
// Light types
|
|
13
|
+
#define LIGHT_TYPE_POINT 0
|
|
14
|
+
#define LIGHT_TYPE_DIRECTIONAL 1
|
|
15
|
+
#define LIGHT_TYPE_SPOT 2
|
|
16
|
+
|
|
17
|
+
// Light structure (must match engine uniform layout)
|
|
18
|
+
struct Light {
|
|
19
|
+
vec3 position;
|
|
20
|
+
vec3 direction;
|
|
21
|
+
vec3 color;
|
|
22
|
+
float intensity;
|
|
23
|
+
float range;
|
|
24
|
+
float innerAngle;
|
|
25
|
+
float outerAngle;
|
|
26
|
+
int type;
|
|
27
|
+
bool enabled;
|
|
28
|
+
bool castShadow;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Calculate attenuation for point/spot lights
|
|
32
|
+
float calculateAttenuation(float distance, float range) {
|
|
33
|
+
// Inverse square falloff with range limit
|
|
34
|
+
float attenuation = 1.0 / (1.0 + distance * distance);
|
|
35
|
+
|
|
36
|
+
// Smooth falloff at range boundary
|
|
37
|
+
float rangeAttenuation = 1.0 - smoothstep(range * 0.75, range, distance);
|
|
38
|
+
|
|
39
|
+
return attenuation * rangeAttenuation;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Calculate spot light cone factor
|
|
43
|
+
float calculateSpotFactor(vec3 lightDir, vec3 spotDir, float innerAngle, float outerAngle) {
|
|
44
|
+
float theta = dot(lightDir, normalize(-spotDir));
|
|
45
|
+
float epsilon = innerAngle - outerAngle;
|
|
46
|
+
return clamp((theta - outerAngle) / epsilon, 0.0, 1.0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Diffuse lighting (Lambert)
|
|
50
|
+
float calculateDiffuse(vec3 normal, vec3 lightDir) {
|
|
51
|
+
return max(dot(normal, lightDir), 0.0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Specular lighting (Blinn-Phong)
|
|
55
|
+
float calculateSpecular(vec3 normal, vec3 lightDir, vec3 viewDir, float shininess) {
|
|
56
|
+
vec3 halfDir = normalize(lightDir + viewDir);
|
|
57
|
+
float specAngle = max(dot(normal, halfDir), 0.0);
|
|
58
|
+
return pow(specAngle, shininess);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Calculate contribution from a single light
|
|
62
|
+
vec3 calculateLightContribution(
|
|
63
|
+
Light light,
|
|
64
|
+
vec3 fragPos,
|
|
65
|
+
vec3 normal,
|
|
66
|
+
vec3 viewDir,
|
|
67
|
+
vec3 albedo,
|
|
68
|
+
float shininess
|
|
69
|
+
) {
|
|
70
|
+
if (!light.enabled) {
|
|
71
|
+
return vec3(0.0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
vec3 lightDir;
|
|
75
|
+
float attenuation = 1.0;
|
|
76
|
+
|
|
77
|
+
if (light.type == LIGHT_TYPE_DIRECTIONAL) {
|
|
78
|
+
// Directional light - no attenuation
|
|
79
|
+
lightDir = normalize(-light.direction);
|
|
80
|
+
} else {
|
|
81
|
+
// Point or spot light
|
|
82
|
+
vec3 toLight = light.position - fragPos;
|
|
83
|
+
float distance = length(toLight);
|
|
84
|
+
lightDir = normalize(toLight);
|
|
85
|
+
attenuation = calculateAttenuation(distance, light.range);
|
|
86
|
+
|
|
87
|
+
if (light.type == LIGHT_TYPE_SPOT) {
|
|
88
|
+
// Apply spot cone
|
|
89
|
+
float spotFactor = calculateSpotFactor(
|
|
90
|
+
lightDir,
|
|
91
|
+
light.direction,
|
|
92
|
+
light.innerAngle,
|
|
93
|
+
light.outerAngle
|
|
94
|
+
);
|
|
95
|
+
attenuation *= spotFactor;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Diffuse
|
|
100
|
+
float diff = calculateDiffuse(normal, lightDir);
|
|
101
|
+
vec3 diffuse = diff * albedo * light.color;
|
|
102
|
+
|
|
103
|
+
// Specular
|
|
104
|
+
float spec = calculateSpecular(normal, lightDir, viewDir, shininess);
|
|
105
|
+
vec3 specular = spec * light.color;
|
|
106
|
+
|
|
107
|
+
return (diffuse + specular) * light.intensity * attenuation;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Calculate total lighting from all lights
|
|
111
|
+
vec3 calculateTotalLighting(
|
|
112
|
+
Light lights[MAX_LIGHTS],
|
|
113
|
+
int numLights,
|
|
114
|
+
vec3 fragPos,
|
|
115
|
+
vec3 normal,
|
|
116
|
+
vec3 viewDir,
|
|
117
|
+
vec3 albedo,
|
|
118
|
+
vec3 ambient,
|
|
119
|
+
float shininess
|
|
120
|
+
) {
|
|
121
|
+
// Start with ambient
|
|
122
|
+
vec3 result = ambient * albedo;
|
|
123
|
+
|
|
124
|
+
// Add contribution from each light
|
|
125
|
+
for (int i = 0; i < MAX_LIGHTS; i++) {
|
|
126
|
+
if (i >= numLights) break;
|
|
127
|
+
result += calculateLightContribution(
|
|
128
|
+
lights[i],
|
|
129
|
+
fragPos,
|
|
130
|
+
normal,
|
|
131
|
+
viewDir,
|
|
132
|
+
albedo,
|
|
133
|
+
shininess
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Simple ambient occlusion approximation
|
|
141
|
+
float calculateAO(vec3 normal, vec3 up) {
|
|
142
|
+
float ao = dot(normal, up) * 0.5 + 0.5;
|
|
143
|
+
return mix(0.5, 1.0, ao);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fresnel effect for rim lighting
|
|
147
|
+
float calculateFresnel(vec3 normal, vec3 viewDir, float power) {
|
|
148
|
+
float fresnel = 1.0 - max(dot(normal, viewDir), 0.0);
|
|
149
|
+
return pow(fresnel, power);
|
|
150
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Transform Calculations
|
|
3
|
+
* Shared GLSL functions for vertex transformations
|
|
4
|
+
*
|
|
5
|
+
* Include this in vertex shaders:
|
|
6
|
+
* #include "transforms.glsl"
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Billboard types
|
|
10
|
+
#define BILLBOARD_NONE 0
|
|
11
|
+
#define BILLBOARD_SPHERICAL 1 // Face camera fully
|
|
12
|
+
#define BILLBOARD_CYLINDRICAL 2 // Face camera horizontally only (Y-axis locked)
|
|
13
|
+
#define BILLBOARD_SCREEN 3 // Align to screen plane
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a billboard rotation matrix that faces the camera
|
|
17
|
+
* @param viewMatrix The camera's view matrix
|
|
18
|
+
* @param billboardType Type of billboarding to apply
|
|
19
|
+
* @return 3x3 rotation matrix for billboard orientation
|
|
20
|
+
*/
|
|
21
|
+
mat3 createBillboardMatrix(mat4 viewMatrix, int billboardType) {
|
|
22
|
+
if (billboardType == BILLBOARD_NONE) {
|
|
23
|
+
return mat3(1.0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Extract camera right and up vectors from view matrix
|
|
27
|
+
vec3 right = vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);
|
|
28
|
+
vec3 up = vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]);
|
|
29
|
+
vec3 forward = vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]);
|
|
30
|
+
|
|
31
|
+
if (billboardType == BILLBOARD_CYLINDRICAL) {
|
|
32
|
+
// Lock Y-axis, only rotate around it
|
|
33
|
+
up = vec3(0.0, 1.0, 0.0);
|
|
34
|
+
right = normalize(cross(up, forward));
|
|
35
|
+
forward = cross(right, up);
|
|
36
|
+
} else if (billboardType == BILLBOARD_SCREEN) {
|
|
37
|
+
// Align exactly to screen plane
|
|
38
|
+
right = vec3(1.0, 0.0, 0.0);
|
|
39
|
+
up = vec3(0.0, 1.0, 0.0);
|
|
40
|
+
forward = vec3(0.0, 0.0, 1.0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return mat3(right, up, forward);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Transform a vertex position for billboard rendering
|
|
48
|
+
* @param localPos Local vertex position (usually quad corners)
|
|
49
|
+
* @param worldPos World position of the billboard center
|
|
50
|
+
* @param scale Scale of the billboard
|
|
51
|
+
* @param viewMatrix Camera view matrix
|
|
52
|
+
* @param projMatrix Camera projection matrix
|
|
53
|
+
* @param billboardType Type of billboarding
|
|
54
|
+
* @return Clip space position
|
|
55
|
+
*/
|
|
56
|
+
vec4 billboardTransform(
|
|
57
|
+
vec3 localPos,
|
|
58
|
+
vec3 worldPos,
|
|
59
|
+
vec2 scale,
|
|
60
|
+
mat4 viewMatrix,
|
|
61
|
+
mat4 projMatrix,
|
|
62
|
+
int billboardType
|
|
63
|
+
) {
|
|
64
|
+
mat3 billboardRot = createBillboardMatrix(viewMatrix, billboardType);
|
|
65
|
+
|
|
66
|
+
// Apply scale and rotation to local position
|
|
67
|
+
vec3 scaledPos = vec3(localPos.x * scale.x, localPos.y * scale.y, localPos.z);
|
|
68
|
+
vec3 rotatedPos = billboardRot * scaledPos;
|
|
69
|
+
|
|
70
|
+
// Add to world position
|
|
71
|
+
vec4 worldVertex = vec4(worldPos + rotatedPos, 1.0);
|
|
72
|
+
|
|
73
|
+
// Transform to clip space
|
|
74
|
+
return projMatrix * viewMatrix * worldVertex;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Apply isometric projection offset
|
|
79
|
+
* Standard 2:1 isometric ratio
|
|
80
|
+
*/
|
|
81
|
+
vec2 toIsometric(vec3 worldPos) {
|
|
82
|
+
float isoX = (worldPos.x - worldPos.z) * 0.5;
|
|
83
|
+
float isoY = (worldPos.x + worldPos.z) * 0.25 - worldPos.y * 0.5;
|
|
84
|
+
return vec2(isoX, isoY);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert screen coordinates to world ray
|
|
89
|
+
* For picking/raycasting
|
|
90
|
+
*/
|
|
91
|
+
vec3 screenToWorldRay(vec2 screenPos, vec2 screenSize, mat4 invProjMatrix, mat4 invViewMatrix) {
|
|
92
|
+
// Normalize to [-1, 1]
|
|
93
|
+
vec2 ndc = (screenPos / screenSize) * 2.0 - 1.0;
|
|
94
|
+
ndc.y = -ndc.y; // Flip Y
|
|
95
|
+
|
|
96
|
+
// Create clip space position
|
|
97
|
+
vec4 clipPos = vec4(ndc, -1.0, 1.0);
|
|
98
|
+
|
|
99
|
+
// Transform to view space
|
|
100
|
+
vec4 viewPos = invProjMatrix * clipPos;
|
|
101
|
+
viewPos = vec4(viewPos.xy, -1.0, 0.0);
|
|
102
|
+
|
|
103
|
+
// Transform to world space
|
|
104
|
+
vec3 worldDir = (invViewMatrix * viewPos).xyz;
|
|
105
|
+
|
|
106
|
+
return normalize(worldDir);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate sprite depth for proper sorting
|
|
111
|
+
* Considers Y position for top-down games
|
|
112
|
+
*/
|
|
113
|
+
float calculateSpriteDepth(vec3 worldPos, float yBias) {
|
|
114
|
+
// Base depth from Z
|
|
115
|
+
float depth = worldPos.z;
|
|
116
|
+
|
|
117
|
+
// Add Y bias for proper sprite stacking
|
|
118
|
+
depth += worldPos.y * yBias;
|
|
119
|
+
|
|
120
|
+
return depth;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* UV coordinate transformations for sprite sheets
|
|
125
|
+
*/
|
|
126
|
+
vec2 transformUV(vec2 uv, vec2 frameOffset, vec2 frameSize, vec2 sheetSize) {
|
|
127
|
+
vec2 normalizedOffset = frameOffset / sheetSize;
|
|
128
|
+
vec2 normalizedSize = frameSize / sheetSize;
|
|
129
|
+
return normalizedOffset + uv * normalizedSize;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Flip UV coordinates
|
|
134
|
+
*/
|
|
135
|
+
vec2 flipUV(vec2 uv, bool flipX, bool flipY) {
|
|
136
|
+
if (flipX) uv.x = 1.0 - uv.x;
|
|
137
|
+
if (flipY) uv.y = 1.0 - uv.y;
|
|
138
|
+
return uv;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Rotate UV coordinates
|
|
143
|
+
* @param uv UV coordinates
|
|
144
|
+
* @param angle Rotation in radians
|
|
145
|
+
* @param center Rotation center (usually 0.5, 0.5)
|
|
146
|
+
*/
|
|
147
|
+
vec2 rotateUV(vec2 uv, float angle, vec2 center) {
|
|
148
|
+
float s = sin(angle);
|
|
149
|
+
float c = cos(angle);
|
|
150
|
+
vec2 offset = uv - center;
|
|
151
|
+
return vec2(
|
|
152
|
+
offset.x * c - offset.y * s,
|
|
153
|
+
offset.x * s + offset.y * c
|
|
154
|
+
) + center;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Calculate parallax offset for depth effects
|
|
159
|
+
*/
|
|
160
|
+
vec2 parallaxOffset(vec2 uv, vec3 viewDir, float height, float scale) {
|
|
161
|
+
float h = height * scale;
|
|
162
|
+
vec2 offset = viewDir.xy / viewDir.z * h;
|
|
163
|
+
return uv - offset;
|
|
164
|
+
}
|