pixospritz-core 0.10.1 → 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 +36 -286
- package/dist/bundle.js +13 -3
- package/dist/bundle.js.map +1 -1
- package/dist/style.css +1 -0
- package/package.json +43 -44
- package/src/components/WebGLView.jsx +318 -0
- package/src/css/pixos.css +372 -0
- package/src/engine/actions/animate.js +41 -0
- package/src/engine/actions/changezone.js +135 -0
- package/src/engine/actions/chat.js +109 -0
- package/src/engine/actions/dialogue.js +90 -0
- package/src/engine/actions/face.js +22 -0
- package/src/engine/actions/greeting.js +28 -0
- package/src/engine/actions/interact.js +86 -0
- package/src/engine/actions/move.js +67 -0
- package/src/engine/actions/patrol.js +109 -0
- package/src/engine/actions/prompt.js +185 -0
- package/src/engine/actions/script.js +42 -0
- package/src/engine/core/audio/AudioSystem.js +543 -0
- package/src/engine/core/cutscene/PxcPlayer.js +956 -0
- package/src/engine/core/cutscene/manager.js +243 -0
- package/src/engine/core/database/index.js +75 -0
- package/src/engine/core/debug/index.js +371 -0
- package/src/engine/core/hud/index.js +765 -0
- package/src/engine/core/index.js +540 -0
- package/src/engine/core/input/gamepad/Controller.js +71 -0
- package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
- package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
- package/src/engine/core/input/gamepad/index.js +592 -0
- package/src/engine/core/input/keyboard.js +196 -0
- package/src/engine/core/input/manager.js +485 -0
- package/src/engine/core/input/mouse.js +203 -0
- package/src/engine/core/input/touch.js +175 -0
- package/src/engine/core/mode/manager.js +199 -0
- package/src/engine/core/net/manager.js +535 -0
- package/src/engine/core/queue/action.js +83 -0
- package/src/engine/core/queue/event.js +82 -0
- package/src/engine/core/queue/index.js +44 -0
- package/src/engine/core/queue/loadable.js +33 -0
- package/src/engine/core/render/CameraEffects.js +494 -0
- package/src/engine/core/render/FrustumCuller.js +417 -0
- package/src/engine/core/render/LODManager.js +285 -0
- package/src/engine/core/render/ParticleManager.js +529 -0
- package/src/engine/core/render/TextureAtlas.js +465 -0
- package/src/engine/core/render/camera.js +338 -0
- package/src/engine/core/render/light.js +197 -0
- package/src/engine/core/render/manager.js +1079 -0
- package/src/engine/core/render/shaders.js +110 -0
- package/src/engine/core/render/skybox.js +342 -0
- package/src/engine/core/resource/manager.js +133 -0
- package/src/engine/core/resource/object.js +611 -0
- package/src/engine/core/resource/texture.js +103 -0
- package/src/engine/core/resource/tileset.js +177 -0
- package/src/engine/core/scene/avatar.js +215 -0
- package/src/engine/core/scene/speech.js +138 -0
- package/src/engine/core/scene/sprite.js +702 -0
- package/src/engine/core/scene/spritz.js +189 -0
- package/src/engine/core/scene/world.js +681 -0
- package/src/engine/core/scene/zone.js +1167 -0
- package/src/engine/core/store/index.js +110 -0
- package/src/engine/dynamic/animatedSprite.js +64 -0
- package/src/engine/dynamic/animatedTile.js +98 -0
- package/src/engine/dynamic/avatar.js +110 -0
- package/src/engine/dynamic/map.js +174 -0
- package/src/engine/dynamic/sprite.js +255 -0
- package/src/engine/dynamic/spritz.js +119 -0
- package/src/engine/events/EventSystem.js +609 -0
- package/src/engine/events/camera.js +142 -0
- package/src/engine/events/chat.js +75 -0
- package/src/engine/events/menu.js +186 -0
- package/src/engine/scripting/CallbackManager.js +514 -0
- package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
- package/src/engine/scripting/PixoScriptLibrary.js +704 -0
- package/src/engine/shaders/effects/index.js +450 -0
- package/src/engine/shaders/fs.js +222 -0
- package/src/engine/shaders/particles/fs.js +41 -0
- package/src/engine/shaders/particles/vs.js +61 -0
- package/src/engine/shaders/picker/fs.js +34 -0
- package/src/engine/shaders/picker/init.js +62 -0
- package/src/engine/shaders/picker/vs.js +42 -0
- package/src/engine/shaders/pxsl/README.md +250 -0
- package/src/engine/shaders/pxsl/index.js +25 -0
- package/src/engine/shaders/pxsl/library.js +608 -0
- package/src/engine/shaders/pxsl/manager.js +338 -0
- package/src/engine/shaders/pxsl/specification.js +363 -0
- package/src/engine/shaders/pxsl/transpiler.js +753 -0
- package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
- package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
- package/src/engine/shaders/skybox/matrix/fs.js +127 -0
- package/src/engine/shaders/skybox/matrix/vs.js +23 -0
- package/src/engine/shaders/skybox/morning/fs.js +109 -0
- package/src/engine/shaders/skybox/morning/vs.js +23 -0
- package/src/engine/shaders/skybox/neon/fs.js +119 -0
- package/src/engine/shaders/skybox/neon/vs.js +23 -0
- package/src/engine/shaders/skybox/sky/fs.js +114 -0
- package/src/engine/shaders/skybox/sky/vs.js +23 -0
- package/src/engine/shaders/skybox/sunset/fs.js +101 -0
- package/src/engine/shaders/skybox/sunset/vs.js +23 -0
- package/src/engine/shaders/transition/blur/fs.js +42 -0
- package/src/engine/shaders/transition/blur/vs.js +26 -0
- package/src/engine/shaders/transition/cross/fs.js +36 -0
- package/src/engine/shaders/transition/cross/vs.js +26 -0
- package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
- package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
- package/src/engine/shaders/transition/dissolve/fs.js +78 -0
- package/src/engine/shaders/transition/dissolve/vs.js +24 -0
- package/src/engine/shaders/transition/fade/fs.js +31 -0
- package/src/engine/shaders/transition/fade/vs.js +27 -0
- package/src/engine/shaders/transition/iris/fs.js +52 -0
- package/src/engine/shaders/transition/iris/vs.js +24 -0
- package/src/engine/shaders/transition/pixelate/fs.js +44 -0
- package/src/engine/shaders/transition/pixelate/vs.js +24 -0
- package/src/engine/shaders/transition/slide/fs.js +53 -0
- package/src/engine/shaders/transition/slide/vs.js +24 -0
- package/src/engine/shaders/transition/swirl/fs.js +39 -0
- package/src/engine/shaders/transition/swirl/vs.js +26 -0
- package/src/engine/shaders/transition/wipe/fs.js +50 -0
- package/src/engine/shaders/transition/wipe/vs.js +24 -0
- package/src/engine/shaders/vs.js +60 -0
- package/src/engine/utils/CameraController.js +506 -0
- package/src/engine/utils/ObjHelper.js +551 -0
- package/src/engine/utils/debug-logger.js +110 -0
- package/src/engine/utils/enums.js +305 -0
- package/src/engine/utils/generator.js +156 -0
- package/src/engine/utils/index.js +21 -0
- package/src/engine/utils/loaders/ActionLoader.js +77 -0
- package/src/engine/utils/loaders/AudioLoader.js +157 -0
- package/src/engine/utils/loaders/EventLoader.js +66 -0
- package/src/engine/utils/loaders/ObjectLoader.js +67 -0
- package/src/engine/utils/loaders/SpriteLoader.js +77 -0
- package/src/engine/utils/loaders/TilesetLoader.js +103 -0
- package/src/engine/utils/loaders/index.js +21 -0
- package/src/engine/utils/math/matrix4.js +367 -0
- package/src/engine/utils/math/vector.js +458 -0
- package/src/engine/utils/obj/_old_js/index.js +46 -0
- package/src/engine/utils/obj/_old_js/layout.js +308 -0
- package/src/engine/utils/obj/_old_js/material.js +711 -0
- package/src/engine/utils/obj/_old_js/mesh.js +761 -0
- package/src/engine/utils/obj/_old_js/utils.js +647 -0
- package/src/engine/utils/obj/index.js +24 -0
- package/src/engine/utils/obj/js/index.js +277 -0
- package/src/engine/utils/obj/js/loader.js +232 -0
- package/src/engine/utils/obj/layout.js +246 -0
- package/src/engine/utils/obj/material.js +665 -0
- package/src/engine/utils/obj/mesh.js +657 -0
- package/src/engine/utils/obj/ts/index.ts +72 -0
- package/src/engine/utils/obj/ts/layout.ts +265 -0
- package/src/engine/utils/obj/ts/material.ts +760 -0
- package/src/engine/utils/obj/ts/mesh.ts +785 -0
- package/src/engine/utils/obj/ts/utils.ts +501 -0
- package/src/engine/utils/obj/utils.js +428 -0
- package/src/engine/utils/resources.js +18 -0
- package/src/index.jsx +55 -0
- package/src/spritz/player.js +18 -0
- package/src/spritz/readme.md +18 -0
- package/LICENSE +0 -437
- package/dist/bundle.js.LICENSE.txt +0 -31
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/* *\
|
|
2
|
+
** ----------------------------------------------- **
|
|
3
|
+
** Calliope - Pixos Game Engine **
|
|
4
|
+
** ----------------------------------------------- **
|
|
5
|
+
** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
|
|
6
|
+
** **
|
|
7
|
+
** Any unauthorized distribution or transfer **
|
|
8
|
+
** of this work is strictly prohibited. **
|
|
9
|
+
** **
|
|
10
|
+
** All Rights Reserved. **
|
|
11
|
+
** ----------------------------------------------- **
|
|
12
|
+
\* */
|
|
13
|
+
|
|
14
|
+
import { Vector } from '../../utils/math/vector.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} ParticleConfig
|
|
18
|
+
* @property {number} [count=8] - Number of particles to emit.
|
|
19
|
+
* @property {number} [life=1000] - Lifetime in milliseconds.
|
|
20
|
+
* @property {number} [speed=0.02] - Initial speed.
|
|
21
|
+
* @property {number} [spread=0.5] - Spread factor for direction.
|
|
22
|
+
* @property {number} [size=0.5] - Particle size.
|
|
23
|
+
* @property {number[]} [color=[1.0, 0.7, 0.2]] - RGB color array.
|
|
24
|
+
* @property {number[]} [gravity=[0, -0.00098, 0]] - Gravity vector.
|
|
25
|
+
* @property {number} [drag=0.995] - Drag coefficient.
|
|
26
|
+
* @property {string} [preset] - Preset name for quick config.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} Particle
|
|
31
|
+
* @property {number[]} pos - Position [x, y, z].
|
|
32
|
+
* @property {number[]} vel - Velocity [vx, vy, vz].
|
|
33
|
+
* @property {number} life - Total lifetime.
|
|
34
|
+
* @property {number} age - Current age.
|
|
35
|
+
* @property {number} size - Size scalar.
|
|
36
|
+
* @property {number[]} color - RGB color.
|
|
37
|
+
* @property {number[]} gravity - Gravity vector.
|
|
38
|
+
* @property {number} drag - Drag coefficient.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ParticleManager - Manages particle effects in the Pixos game engine.
|
|
43
|
+
* Handles emission, physics updates, and rendering of particles.
|
|
44
|
+
*/
|
|
45
|
+
export default class ParticleManager {
|
|
46
|
+
/**
|
|
47
|
+
* Creates an instance of ParticleManager.
|
|
48
|
+
* @param {import('./manager.js').default} renderManager - The render manager instance.
|
|
49
|
+
*/
|
|
50
|
+
constructor(renderManager) {
|
|
51
|
+
/** @type {import('./manager.js').default} */
|
|
52
|
+
this.renderManager = renderManager;
|
|
53
|
+
/** @type {import('../index.js').default} */
|
|
54
|
+
this.engine = renderManager.engine;
|
|
55
|
+
/** @type {Particle[]} */
|
|
56
|
+
this.particles = [];
|
|
57
|
+
/** @type {boolean} */
|
|
58
|
+
this.initialized = false;
|
|
59
|
+
/** @type {WebGLBuffer|null} */
|
|
60
|
+
this.vertexPosBuf = null;
|
|
61
|
+
/** @type {WebGLBuffer|null} */
|
|
62
|
+
this.vertexTexBuf = null;
|
|
63
|
+
/** @type {number|null} */
|
|
64
|
+
this.lastUpdateTime = null;
|
|
65
|
+
|
|
66
|
+
// Instanced rendering buffers
|
|
67
|
+
/** @type {WebGLBuffer|null} */
|
|
68
|
+
this.instancePositionBuf = null;
|
|
69
|
+
/** @type {WebGLBuffer|null} */
|
|
70
|
+
this.instanceColorBuf = null;
|
|
71
|
+
/** @type {WebGLBuffer|null} */
|
|
72
|
+
this.instanceSizeBuf = null;
|
|
73
|
+
/** @type {boolean} */
|
|
74
|
+
this.useInstancing = true;
|
|
75
|
+
/** @type {number} */
|
|
76
|
+
this.maxInstances = 10000;
|
|
77
|
+
/** @type {Float32Array|null} */
|
|
78
|
+
this.instancePositions = null;
|
|
79
|
+
/** @type {Float32Array|null} */
|
|
80
|
+
this.instanceColors = null;
|
|
81
|
+
/** @type {Float32Array|null} */
|
|
82
|
+
this.instanceSizes = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Initializes GL buffers. Called after RenderManager has initialized shaders/GL.
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
init = () => {
|
|
90
|
+
/** @type {WebGL2RenderingContext} */
|
|
91
|
+
const gl = this.engine.gl;
|
|
92
|
+
if (!gl) return;
|
|
93
|
+
|
|
94
|
+
// A simple unit quad centered at origin (two triangles)
|
|
95
|
+
const quad = [
|
|
96
|
+
-0.5, -0.5, 0,
|
|
97
|
+
-0.5, 0.5, 0,
|
|
98
|
+
0.5, 0.5, 0,
|
|
99
|
+
-0.5, -0.5, 0,
|
|
100
|
+
0.5, 0.5, 0,
|
|
101
|
+
0.5, -0.5, 0,
|
|
102
|
+
];
|
|
103
|
+
// Simple UVs (not used when not texturing)
|
|
104
|
+
const uvs = [
|
|
105
|
+
0, 0,
|
|
106
|
+
0, 1,
|
|
107
|
+
1, 1,
|
|
108
|
+
0, 0,
|
|
109
|
+
1, 1,
|
|
110
|
+
1, 0,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
this.vertexPosBuf = this.renderManager.createBuffer(quad, gl.STATIC_DRAW, 3);
|
|
114
|
+
this.vertexTexBuf = this.renderManager.createBuffer(uvs, gl.STATIC_DRAW, 2);
|
|
115
|
+
|
|
116
|
+
// Initialize instanced rendering buffers
|
|
117
|
+
this.initInstancedBuffers();
|
|
118
|
+
|
|
119
|
+
this.initialized = true;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initializes buffers for instanced rendering.
|
|
124
|
+
* @returns {void}
|
|
125
|
+
*/
|
|
126
|
+
initInstancedBuffers = () => {
|
|
127
|
+
const gl = this.engine.gl;
|
|
128
|
+
if (!gl) return;
|
|
129
|
+
|
|
130
|
+
// Pre-allocate typed arrays for instance data
|
|
131
|
+
this.instancePositions = new Float32Array(this.maxInstances * 3);
|
|
132
|
+
this.instanceColors = new Float32Array(this.maxInstances * 4);
|
|
133
|
+
this.instanceSizes = new Float32Array(this.maxInstances);
|
|
134
|
+
|
|
135
|
+
// Create instance buffers
|
|
136
|
+
this.instancePositionBuf = gl.createBuffer();
|
|
137
|
+
this.instanceColorBuf = gl.createBuffer();
|
|
138
|
+
this.instanceSizeBuf = gl.createBuffer();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Updates instance buffers with current particle data.
|
|
143
|
+
* @returns {number} Number of particles to render.
|
|
144
|
+
*/
|
|
145
|
+
updateInstanceBuffers = () => {
|
|
146
|
+
const gl = this.engine.gl;
|
|
147
|
+
if (!gl || !this.particles.length) return 0;
|
|
148
|
+
|
|
149
|
+
const count = Math.min(this.particles.length, this.maxInstances);
|
|
150
|
+
|
|
151
|
+
// Sort particles back-to-front for proper alpha blending
|
|
152
|
+
const cameraPos = this.renderManager.camera.cameraPosition;
|
|
153
|
+
const sortedParticles = [...this.particles].sort((a, b) => {
|
|
154
|
+
const distA = Math.pow(a.pos[0] - cameraPos.x, 2) +
|
|
155
|
+
Math.pow(a.pos[1] - cameraPos.y, 2) +
|
|
156
|
+
Math.pow(a.pos[2] - cameraPos.z, 2);
|
|
157
|
+
const distB = Math.pow(b.pos[0] - cameraPos.x, 2) +
|
|
158
|
+
Math.pow(b.pos[1] - cameraPos.y, 2) +
|
|
159
|
+
Math.pow(b.pos[2] - cameraPos.z, 2);
|
|
160
|
+
return distB - distA;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Fill instance arrays
|
|
164
|
+
for (let i = 0; i < count; i++) {
|
|
165
|
+
const p = sortedParticles[i];
|
|
166
|
+
const lifeRatio = p.age / p.life;
|
|
167
|
+
const alpha = Math.max(0, 1.0 - lifeRatio * lifeRatio);
|
|
168
|
+
|
|
169
|
+
// Position (x, y, z)
|
|
170
|
+
this.instancePositions[i * 3] = p.pos[0];
|
|
171
|
+
this.instancePositions[i * 3 + 1] = p.pos[1];
|
|
172
|
+
this.instancePositions[i * 3 + 2] = p.pos[2];
|
|
173
|
+
|
|
174
|
+
// Color (r, g, b, a)
|
|
175
|
+
this.instanceColors[i * 4] = p.color[0];
|
|
176
|
+
this.instanceColors[i * 4 + 1] = p.color[1];
|
|
177
|
+
this.instanceColors[i * 4 + 2] = p.color[2];
|
|
178
|
+
this.instanceColors[i * 4 + 3] = alpha;
|
|
179
|
+
|
|
180
|
+
// Size
|
|
181
|
+
this.instanceSizes[i] = p.size;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Upload to GPU
|
|
185
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancePositionBuf);
|
|
186
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.instancePositions.subarray(0, count * 3), gl.DYNAMIC_DRAW);
|
|
187
|
+
|
|
188
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceColorBuf);
|
|
189
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.instanceColors.subarray(0, count * 4), gl.DYNAMIC_DRAW);
|
|
190
|
+
|
|
191
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceSizeBuf);
|
|
192
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.instanceSizes.subarray(0, count), gl.DYNAMIC_DRAW);
|
|
193
|
+
|
|
194
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
195
|
+
|
|
196
|
+
return count;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Emits particles based on a config object.
|
|
201
|
+
* @param {number[]|Vector} [position=[0, 0, 0]] - Position [x, y, z] or Vector.
|
|
202
|
+
* @param {ParticleConfig} [config={}] - Configuration for particles.
|
|
203
|
+
* @returns {void}
|
|
204
|
+
*/
|
|
205
|
+
emit = (position = [0, 0, 0], config = {}) => {
|
|
206
|
+
/** @type {number[]} */
|
|
207
|
+
let pos = Array.isArray(position) ? position : position.toArray ? position.toArray() : [0, 0, 0];
|
|
208
|
+
let x = pos[0], y = pos[1], zOffset = pos[2] || 0;
|
|
209
|
+
/** @type {import('../../scene/zone.js').Zone|null} */
|
|
210
|
+
let zone = this.engine.spritz.world.zoneContaining(x, y);
|
|
211
|
+
let z = zOffset;
|
|
212
|
+
if (zone) {
|
|
213
|
+
z += zone.getHeight(x, y);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pos = [x, y, z];
|
|
217
|
+
|
|
218
|
+
/** @type {ParticleConfig} */
|
|
219
|
+
const c = Object.assign(
|
|
220
|
+
{
|
|
221
|
+
count: 8,
|
|
222
|
+
life: 1000, // ms
|
|
223
|
+
speed: 0.02,
|
|
224
|
+
spread: 0.5,
|
|
225
|
+
size: 0.5,
|
|
226
|
+
color: [1.0, 0.7, 0.2],
|
|
227
|
+
gravity: [0, -0.00098, 0],
|
|
228
|
+
drag: 0.995,
|
|
229
|
+
},
|
|
230
|
+
config
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < c.count; i++) {
|
|
234
|
+
// random direction in unit sphere
|
|
235
|
+
const rx = (Math.random() * 2 - 1) * c.spread;
|
|
236
|
+
const ry = (Math.random() * 2 - 1) * c.spread;
|
|
237
|
+
const rz = (Math.random() * 2 - 1) * c.spread;
|
|
238
|
+
const vx = rx * c.speed * (0.5 + Math.random() * 1.5);
|
|
239
|
+
const vy = ry * c.speed * (0.5 + Math.random() * 1.5);
|
|
240
|
+
const vz = rz * c.speed * (0.5 + Math.random() * 1.5);
|
|
241
|
+
|
|
242
|
+
/** @type {Particle} */
|
|
243
|
+
const particle = {
|
|
244
|
+
pos: [pos[0], pos[1], pos[2]],
|
|
245
|
+
vel: [vx, vy, vz],
|
|
246
|
+
life: c.life,
|
|
247
|
+
age: 0,
|
|
248
|
+
size: c.size * (0.8 + Math.random() * 0.8),
|
|
249
|
+
color: c.color,
|
|
250
|
+
gravity: c.gravity,
|
|
251
|
+
drag: c.drag,
|
|
252
|
+
};
|
|
253
|
+
this.particles.push(particle);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Returns a preset configuration for particles.
|
|
259
|
+
* @param {string} name - The preset name.
|
|
260
|
+
* @returns {ParticleConfig|null} The preset config or null if not found.
|
|
261
|
+
*/
|
|
262
|
+
preset = (name) => {
|
|
263
|
+
switch ((name || '').toLowerCase()) {
|
|
264
|
+
case 'sparks':
|
|
265
|
+
return { count: 12, life: 700, speed: 0.06, spread: 1.2, size: 0.15, color: [1, 0.8, 0.2], gravity: [0, -0.002, 0] };
|
|
266
|
+
case 'flame':
|
|
267
|
+
return { count: 200, life: 2000, speed: 0.02, spread: 0.8, size: 0.06, color: [1, 0.5, 0.1], gravity: [0, -0.0003, 0], drag: 0.995 };
|
|
268
|
+
case 'water':
|
|
269
|
+
return { count: 20, life: 800, speed: 0.05, spread: 1.5, size: 0.12, color: [0.6, 0.7, 1.0], gravity: [0, -0.003, 0], drag: 0.996 };
|
|
270
|
+
case 'weapon':
|
|
271
|
+
return { count: 6, life: 600, speed: 0.08, spread: 0.3, size: 0.18, color: [1, 1, 0.6], gravity: [0, -0.001, 0] };
|
|
272
|
+
default:
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Updates particle physics. Timestamp in ms.
|
|
279
|
+
* @param {number} timestamp - Current timestamp.
|
|
280
|
+
* @returns {void}
|
|
281
|
+
*/
|
|
282
|
+
update = (timestamp) => {
|
|
283
|
+
if (!this.lastUpdateTime) this.lastUpdateTime = timestamp;
|
|
284
|
+
const dt = timestamp - this.lastUpdateTime;
|
|
285
|
+
this.lastUpdateTime = timestamp;
|
|
286
|
+
if (!dt) return;
|
|
287
|
+
|
|
288
|
+
// Update particle physics
|
|
289
|
+
for (let i = this.particles.length - 1; i >= 0; i--) {
|
|
290
|
+
/** @type {Particle} */
|
|
291
|
+
const p = this.particles[i];
|
|
292
|
+
// apply gravity
|
|
293
|
+
p.vel[0] += (p.gravity[0] || 0) * dt;
|
|
294
|
+
p.vel[1] += (p.gravity[1] || 0) * dt;
|
|
295
|
+
p.vel[2] += (p.gravity[2] || 0) * dt;
|
|
296
|
+
// apply drag
|
|
297
|
+
p.vel[0] *= Math.pow(p.drag || 1, dt / 16.6667);
|
|
298
|
+
p.vel[1] *= Math.pow(p.drag || 1, dt / 16.6667);
|
|
299
|
+
p.vel[2] *= Math.pow(p.drag || 1, dt / 16.6667);
|
|
300
|
+
// integrate
|
|
301
|
+
p.pos[0] += p.vel[0] * dt;
|
|
302
|
+
p.pos[1] += p.vel[1] * dt;
|
|
303
|
+
p.pos[2] += p.vel[2] * dt;
|
|
304
|
+
p.age += dt;
|
|
305
|
+
if (p.age >= p.life) {
|
|
306
|
+
this.particles.splice(i, 1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Renders particles using instanced rendering for optimal performance.
|
|
313
|
+
* Falls back to individual draw calls if instancing is not available.
|
|
314
|
+
* @returns {void}
|
|
315
|
+
*/
|
|
316
|
+
render = () => {
|
|
317
|
+
if (!this.initialized) this.init();
|
|
318
|
+
if (!this.initialized) return;
|
|
319
|
+
if (!this.particles.length) return;
|
|
320
|
+
|
|
321
|
+
/** @type {import('./manager.js').default} */
|
|
322
|
+
const rm = this.renderManager;
|
|
323
|
+
/** @type {WebGL2RenderingContext} */
|
|
324
|
+
const gl = this.engine.gl;
|
|
325
|
+
/** @type {WebGLProgram} */
|
|
326
|
+
const shader = rm.particleShaderProgram;
|
|
327
|
+
if (!shader) return;
|
|
328
|
+
|
|
329
|
+
// Check for instanced rendering support (WebGL2)
|
|
330
|
+
const supportsInstancing = typeof gl.drawArraysInstanced === 'function';
|
|
331
|
+
|
|
332
|
+
if (supportsInstancing && this.useInstancing && shader.aInstancePosition !== undefined) {
|
|
333
|
+
this.renderInstanced();
|
|
334
|
+
} else {
|
|
335
|
+
this.renderNonInstanced();
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Renders particles using WebGL2 instanced rendering.
|
|
341
|
+
* Single draw call for all particles - massive performance improvement.
|
|
342
|
+
* @returns {void}
|
|
343
|
+
*/
|
|
344
|
+
renderInstanced = () => {
|
|
345
|
+
const rm = this.renderManager;
|
|
346
|
+
const gl = this.engine.gl;
|
|
347
|
+
const shader = rm.particleShaderProgram;
|
|
348
|
+
|
|
349
|
+
// Update instance buffers with current particle data
|
|
350
|
+
const instanceCount = this.updateInstanceBuffers();
|
|
351
|
+
if (instanceCount === 0) return;
|
|
352
|
+
|
|
353
|
+
// Reset vertex attrib arrays
|
|
354
|
+
for (let i = 0; i < 8; i++) {
|
|
355
|
+
gl.disableVertexAttribArray(i);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
gl.useProgram(shader);
|
|
359
|
+
|
|
360
|
+
// Enable blending for transparency - additive blending for glow
|
|
361
|
+
gl.enable(gl.BLEND);
|
|
362
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
|
363
|
+
gl.depthMask(false);
|
|
364
|
+
|
|
365
|
+
// Bind quad vertex data (shared for all instances)
|
|
366
|
+
gl.enableVertexAttribArray(shader.aVertexPosition);
|
|
367
|
+
rm.bindBuffer(this.vertexPosBuf, shader.aVertexPosition);
|
|
368
|
+
|
|
369
|
+
gl.enableVertexAttribArray(shader.aTextureCoord);
|
|
370
|
+
rm.bindBuffer(this.vertexTexBuf, shader.aTextureCoord);
|
|
371
|
+
|
|
372
|
+
// Bind instance position buffer
|
|
373
|
+
if (shader.aInstancePosition !== undefined) {
|
|
374
|
+
gl.enableVertexAttribArray(shader.aInstancePosition);
|
|
375
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancePositionBuf);
|
|
376
|
+
gl.vertexAttribPointer(shader.aInstancePosition, 3, gl.FLOAT, false, 0, 0);
|
|
377
|
+
gl.vertexAttribDivisor(shader.aInstancePosition, 1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Bind instance color buffer
|
|
381
|
+
if (shader.aInstanceColor !== undefined) {
|
|
382
|
+
gl.enableVertexAttribArray(shader.aInstanceColor);
|
|
383
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceColorBuf);
|
|
384
|
+
gl.vertexAttribPointer(shader.aInstanceColor, 4, gl.FLOAT, false, 0, 0);
|
|
385
|
+
gl.vertexAttribDivisor(shader.aInstanceColor, 1);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Bind instance size buffer
|
|
389
|
+
if (shader.aInstanceSize !== undefined) {
|
|
390
|
+
gl.enableVertexAttribArray(shader.aInstanceSize);
|
|
391
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceSizeBuf);
|
|
392
|
+
gl.vertexAttribPointer(shader.aInstanceSize, 1, gl.FLOAT, false, 0, 0);
|
|
393
|
+
gl.vertexAttribDivisor(shader.aInstanceSize, 1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Set projection/view matrices
|
|
397
|
+
if (shader.setMatrixUniforms) {
|
|
398
|
+
shader.setMatrixUniforms({});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Draw all particles in a single instanced call
|
|
402
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
|
403
|
+
|
|
404
|
+
// Reset divisors
|
|
405
|
+
if (shader.aInstancePosition !== undefined) {
|
|
406
|
+
gl.vertexAttribDivisor(shader.aInstancePosition, 0);
|
|
407
|
+
gl.disableVertexAttribArray(shader.aInstancePosition);
|
|
408
|
+
}
|
|
409
|
+
if (shader.aInstanceColor !== undefined) {
|
|
410
|
+
gl.vertexAttribDivisor(shader.aInstanceColor, 0);
|
|
411
|
+
gl.disableVertexAttribArray(shader.aInstanceColor);
|
|
412
|
+
}
|
|
413
|
+
if (shader.aInstanceSize !== undefined) {
|
|
414
|
+
gl.vertexAttribDivisor(shader.aInstanceSize, 0);
|
|
415
|
+
gl.disableVertexAttribArray(shader.aInstanceSize);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Cleanup
|
|
419
|
+
gl.depthMask(true);
|
|
420
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
421
|
+
gl.disableVertexAttribArray(shader.aVertexPosition);
|
|
422
|
+
gl.disableVertexAttribArray(shader.aTextureCoord);
|
|
423
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
424
|
+
gl.useProgram(null);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Renders particles without instancing (fallback for older systems).
|
|
429
|
+
* @returns {void}
|
|
430
|
+
*/
|
|
431
|
+
renderNonInstanced = () => {
|
|
432
|
+
const rm = this.renderManager;
|
|
433
|
+
const gl = this.engine.gl;
|
|
434
|
+
const shader = rm.particleShaderProgram;
|
|
435
|
+
|
|
436
|
+
// Reset all vertex attrib arrays to prevent errors from other shaders
|
|
437
|
+
for (let i = 0; i < 8; i++) {
|
|
438
|
+
gl.disableVertexAttribArray(i);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Use particle shader
|
|
442
|
+
gl.useProgram(shader);
|
|
443
|
+
|
|
444
|
+
// Enable blending for transparency - additive blending for glow effects
|
|
445
|
+
gl.enable(gl.BLEND);
|
|
446
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
|
447
|
+
|
|
448
|
+
// Disable depth writing (but keep depth test) for proper transparency
|
|
449
|
+
gl.depthMask(false);
|
|
450
|
+
|
|
451
|
+
// Enable vertex attributes for particle shader
|
|
452
|
+
gl.enableVertexAttribArray(shader.aVertexPosition);
|
|
453
|
+
gl.enableVertexAttribArray(shader.aTextureCoord);
|
|
454
|
+
|
|
455
|
+
// Sort particles back-to-front based on distance from camera
|
|
456
|
+
const cameraPos = rm.camera.cameraPosition;
|
|
457
|
+
const sortedParticles = [...this.particles].sort((a, b) => {
|
|
458
|
+
const distA = Math.pow(a.pos[0] - cameraPos.x, 2) +
|
|
459
|
+
Math.pow(a.pos[1] - cameraPos.y, 2) +
|
|
460
|
+
Math.pow(a.pos[2] - cameraPos.z, 2);
|
|
461
|
+
const distB = Math.pow(b.pos[0] - cameraPos.x, 2) +
|
|
462
|
+
Math.pow(b.pos[1] - cameraPos.y, 2) +
|
|
463
|
+
Math.pow(b.pos[2] - cameraPos.z, 2);
|
|
464
|
+
return distB - distA; // Back to front
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
for (const p of sortedParticles) {
|
|
468
|
+
rm.mvPushMatrix();
|
|
469
|
+
|
|
470
|
+
// Set model matrix translation only (billboarding handled in shader)
|
|
471
|
+
const m = rm.uModelMat;
|
|
472
|
+
// Reset to identity
|
|
473
|
+
for (let i = 0; i < 16; i++) m[i] = (i % 5 === 0) ? 1 : 0;
|
|
474
|
+
// Set translation
|
|
475
|
+
m[12] = p.pos[0];
|
|
476
|
+
m[13] = p.pos[1];
|
|
477
|
+
m[14] = p.pos[2];
|
|
478
|
+
|
|
479
|
+
// Calculate alpha based on particle age (fade out towards end of life)
|
|
480
|
+
const lifeRatio = p.age / p.life;
|
|
481
|
+
const alpha = Math.max(0, 1.0 - lifeRatio * lifeRatio); // Quadratic fade
|
|
482
|
+
|
|
483
|
+
// Set scale and matrix uniforms with alpha
|
|
484
|
+
const scaleVec = new Vector(p.size, p.size, p.size);
|
|
485
|
+
shader.setMatrixUniforms({ scale: scaleVec, color: p.color, alpha: alpha });
|
|
486
|
+
|
|
487
|
+
// Bind buffers and draw
|
|
488
|
+
rm.bindBuffer(this.vertexPosBuf, shader.aVertexPosition);
|
|
489
|
+
rm.bindBuffer(this.vertexTexBuf, shader.aTextureCoord);
|
|
490
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.vertexPosBuf.numItems);
|
|
491
|
+
|
|
492
|
+
rm.mvPopMatrix();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Restore depth mask and blending
|
|
496
|
+
gl.depthMask(true);
|
|
497
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
498
|
+
|
|
499
|
+
// Disable vertex attrib arrays to prevent WebGL state issues
|
|
500
|
+
gl.disableVertexAttribArray(shader.aVertexPosition);
|
|
501
|
+
gl.disableVertexAttribArray(shader.aTextureCoord);
|
|
502
|
+
|
|
503
|
+
// cleanup
|
|
504
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
505
|
+
gl.useProgram(null);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Gets particle count for debugging/stats.
|
|
510
|
+
* @returns {number} Current particle count.
|
|
511
|
+
*/
|
|
512
|
+
getParticleCount = () => this.particles.length;
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Clears all particles.
|
|
516
|
+
* @returns {void}
|
|
517
|
+
*/
|
|
518
|
+
clear = () => {
|
|
519
|
+
this.particles = [];
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Sets whether to use instanced rendering.
|
|
524
|
+
* @param {boolean} enabled - Whether to use instancing.
|
|
525
|
+
*/
|
|
526
|
+
setInstancingEnabled = (enabled) => {
|
|
527
|
+
this.useInstancing = enabled;
|
|
528
|
+
};
|
|
529
|
+
}
|