diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 896efd0..9915ffb 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -26,5 +26,3 @@ jobs: python -m pip install wheel python -m pip install pygame-ce python -m pip install . - - name: Run Tests - run: python -m unittest \ No newline at end of file diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1be199c..7c7c0ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -28,5 +28,3 @@ jobs: python -m pip install wheel python -m pip install pygame-ce python -m pip install . - - name: Run Tests - run: python -m unittest \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index c15bd1a..8e672b4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,5 +21,3 @@ jobs: py -${{ matrix.python-version }} -m pip install wheel py -${{ matrix.python-version }} -m pip install pygame-ce py -${{ matrix.python-version }} -m pip install . - - name: Run Tests - run: py -${{ matrix.python-version }} -m unittest diff --git a/meson.build b/meson.build index b2991e2..a75d01c 100644 --- a/meson.build +++ b/meson.build @@ -178,6 +178,7 @@ src_files = [ 'src/particle_manager.c', 'src/emitter.c', 'src/particle_effect.c', + 'src/option.c', ] py.extension_module( diff --git a/src/data_block.c b/src/data_block.c index c115c9c..51b2f84 100644 --- a/src/data_block.c +++ b/src/data_block.c @@ -5,6 +5,7 @@ #include "include/data_block.h" #include "include/simd_common.h" +#include "include/option.h" /* ====================| Public facing DataBlock functions |==================== */ int @@ -19,8 +20,8 @@ init_data_block(DataBlock *block, Emitter *emitter, vec2 position) /* Conditionally allocate memory for the arrays based on emitter properties */ if (!alloc_and_init_positions(block, emitter, position) || - !alloc_and_init_velocities(block, emitter) || !alloc_and_init_accelerations(block, emitter) || + !alloc_and_init_velocities(block, emitter) || !alloc_and_init_lifetimes(block, emitter) || !alloc_and_init_animation_indices(block, emitter) || !init_fragmentation_map(block)) @@ -47,11 +48,34 @@ dealloc_data_block(DataBlock *block) dealloc_fragmentation_map(&block->frag_map); } +void +update_data_block(DataBlock *block, float dt) +{ + block->updater(block, dt); + + recalculate_particle_count(block); +} + +int +draw_data_block(DataBlock *block, pgSurfaceObject *dest, const int blend_flag) +{ + update_indices(block); + + if (!calculate_fragmentation_map(dest, block)) + return 0; + + blit_fragments(dest, &block->frag_map, block, blend_flag); + + return 1; +} + +/* ====================| Internal DataBlock functions |==================== */ + void choose_and_set_update_function(DataBlock *block, Emitter *emitter) { - const bool use_x_acc = emitter->acceleration_x.in_use; - const bool use_y_acc = emitter->acceleration_y.in_use; + const int use_x_acc = OPTION_IS_SET(emitter->acceleration_x); + const int use_y_acc = OPTION_IS_SET(emitter->acceleration_y); #if !defined(__EMSCRIPTEN__) if (_Has_AVX2()) { @@ -93,29 +117,6 @@ choose_and_set_update_function(DataBlock *block, Emitter *emitter) block->updater = update_with_acceleration; } -void -update_data_block(DataBlock *block, float dt) -{ - block->updater(block, dt); - - recalculate_particle_count(block); -} - -int -draw_data_block(DataBlock *block, pgSurfaceObject *dest, const int blend_flag) -{ - update_indices(block); - - if (!calculate_fragmentation_map(dest, block)) - return 0; - - blit_fragments(dest, &block->frag_map, block, blend_flag); - - return 1; -} - -/* ====================| Internal DataBlock functions |==================== */ - void calculate_surface_index_occurrences(DataBlock *block) { @@ -543,23 +544,268 @@ alloc_and_init_positions(DataBlock *block, Emitter *emitter, vec2 position) int alloc_and_init_velocities(DataBlock *block, Emitter *emitter) { - /* Check if the emitter has no speed */ - if (emitter->speed_x.min == 0.0f && emitter->speed_x.max == 0.0f && - emitter->speed_y.min == 0.0f && emitter->speed_y.max == 0.0f) + const int x_set = OPTION_IS_SET(emitter->speed_x); + const int y_set = OPTION_IS_SET(emitter->speed_y); + + /* Acceleration flags are already set when this is called */ + int flags = block->update_flags; + + if (!x_set && !y_set) { + /* In this case, based on the acceleration flags, we either allocate an array + * of zeroes or set a single value to zero */ + if (flags & NO_ACCELERATION) { + block->update_flags |= NO_VELOCITY; + } + else if (flags & ACCEL_X_SINGLE && flags & ACCEL_Y_SINGLE) { + block->update_flags |= VELOCITY_X_SINGLE | VELOCITY_Y_SINGLE; + block->s_velocity_x = 0.0f; + block->s_velocity_y = 0.0f; + } + else if (flags & ACCEL_X_ARRAY && flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_X_ARRAY | VELOCITY_Y_ARRAY; + + float_array *x = &block->velocities_x; + float_array *y = &block->velocities_y; + + /* Allocate memory for the velocities array ( 0 initialized ) */ + if (!float_array_calloc(x, emitter->emission_number) || + !float_array_calloc(y, emitter->emission_number)) + return 0; + } + else if (flags & ACCEL_X_SINGLE && flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_X_SINGLE | VELOCITY_Y_ARRAY; + block->s_velocity_x = 0.0f; + + float_array *y = &block->velocities_y; + + /* Allocate memory for the velocities array ( 0 initialized ) */ + if (!float_array_calloc(y, emitter->emission_number)) + return 0; + } + else if (flags & ACCEL_X_ARRAY && flags & ACCEL_Y_SINGLE) { + block->update_flags |= VELOCITY_X_ARRAY | VELOCITY_Y_SINGLE; + block->s_velocity_y = 0.0f; + + float_array *x = &block->velocities_x; + + /* Allocate memory for the velocities array ( 0 initialized ) */ + if (!float_array_calloc(x, emitter->emission_number)) + return 0; + } + return 1; + } + else if (x_set && y_set) { + const int x_is_value = OPTION_IS_VALUE(emitter->speed_x); + const int y_is_value = OPTION_IS_VALUE(emitter->speed_y); + + if (x_is_value && y_is_value) { + /* Both velocities are set to a single constant value, the update + * function will just store a single value each */ + if (flags & NO_ACCELERATION || + (flags & ACCEL_X_SINGLE && flags & ACCEL_Y_SINGLE)) { + block->update_flags |= VELOCITY_X_SINGLE | VELOCITY_Y_SINGLE; + + block->s_velocity_x = option_get_value(&emitter->speed_x); + block->s_velocity_y = option_get_value(&emitter->speed_y); + } + else if (flags & ACCEL_X_ARRAY && flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_X_ARRAY | VELOCITY_Y_ARRAY; - float_array *x = &block->velocities_x; - float_array *y = &block->velocities_y; + float_array *x = &block->velocities_x; + float_array *y = &block->velocities_y; - /* Allocate memory for the velocities array */ - if (!float_array_alloc(x, emitter->emission_number) || - !float_array_alloc(y, emitter->emission_number)) - return 0; + /* Allocate memory for the velocities array */ + if (!float_array_alloc(x, emitter->emission_number) || + !float_array_alloc(y, emitter->emission_number)) + return 0; + + /* Initialize the velocities array */ + for (int i = 0; i < emitter->emission_number; i++) { + x->data[i] = option_get_value(&emitter->speed_x); + y->data[i] = option_get_value(&emitter->speed_y); + } + } + else if (flags & ACCEL_X_SINGLE && flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_X_SINGLE | VELOCITY_Y_ARRAY; + + block->s_velocity_x = option_get_value(&emitter->speed_x); + + float_array *y = &block->velocities_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->speed_y); + } + else if (flags & ACCEL_X_ARRAY && flags & ACCEL_Y_SINGLE) { + block->update_flags |= VELOCITY_X_ARRAY | VELOCITY_Y_SINGLE; + + block->s_velocity_y = option_get_value(&emitter->speed_y); + + float_array *x = &block->velocities_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->speed_x); + } + + return 1; + } + else if (!x_is_value && !y_is_value) { + /* Both velocities are randomized, the update function will store + * a random value for each particle */ + block->update_flags |= VELOCITY_X_ARRAY | VELOCITY_Y_ARRAY; + + float_array *x = &block->velocities_x; + float_array *y = &block->velocities_y; + + /* Allocate memory for the velocities array */ + if (!float_array_alloc(x, emitter->emission_number) || + !float_array_alloc(y, emitter->emission_number)) + return 0; + + /* Initialize the velocities array */ + for (int i = 0; i < emitter->emission_number; i++) { + x->data[i] = option_get_value(&emitter->speed_x); + y->data[i] = option_get_value(&emitter->speed_y); + } + } + else if (x_is_value && !y_is_value) { + /* Velocity x is set to a single constant value, velocity y + * is randomized */ + if (flags & ACCEL_X_SINGLE || flags & NO_ACCELERATION_X) { + block->update_flags |= VELOCITY_X_SINGLE; + block->s_velocity_x = option_get_value(&emitter->speed_x); + } + else if (flags & ACCEL_X_ARRAY) { + block->update_flags |= VELOCITY_X_ARRAY; + + float_array *x = &block->velocities_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->speed_x); + } + + block->update_flags |= VELOCITY_Y_ARRAY; + + float_array *y = &block->velocities_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->speed_y); + } + else if (!x_is_value && y_is_value) { + /* Velocity y is set to a single constant value, velocity x + * is randomized */ + if (flags & ACCEL_Y_SINGLE || flags & NO_ACCELERATION_Y) { + block->update_flags |= VELOCITY_Y_SINGLE; + block->s_velocity_y = option_get_value(&emitter->speed_y); + } + else if (flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_Y_ARRAY; + + float_array *y = &block->velocities_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->speed_y); + } + + block->update_flags |= VELOCITY_X_ARRAY; - /* Initialize the velocities array */ - for (int i = 0; i < emitter->emission_number; i++) { - x->data[i] = genrand_from(&emitter->speed_x); - y->data[i] = genrand_from(&emitter->speed_y); + float_array *x = &block->velocities_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->speed_x); + } + } + else if (!x_set && y_set) { + if (flags & ACCEL_X_SINGLE) { + block->update_flags |= VELOCITY_X_SINGLE; + block->s_velocity_x = 0.0f; + } + else if (flags & ACCEL_X_ARRAY) { + block->update_flags |= VELOCITY_X_ARRAY; + + float_array *x = &block->velocities_x; + if (!float_array_calloc(x, emitter->emission_number)) + return 0; + } + + if (OPTION_IS_VALUE(emitter->speed_y)) { + if (flags & ACCEL_Y_SINGLE) { + block->update_flags |= VELOCITY_Y_SINGLE; + block->s_velocity_y = option_get_value(&emitter->speed_y); + } + else if (flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_Y_ARRAY; + + float_array *y = &block->velocities_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->speed_y); + } + } + else { + block->update_flags |= VELOCITY_Y_ARRAY; + + float_array *y = &block->velocities_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->speed_y); + } + } + else if (x_set && !y_set) { + if (flags & ACCEL_Y_SINGLE) { + block->update_flags |= VELOCITY_Y_SINGLE; + block->s_velocity_y = 0.0f; + } + else if (flags & ACCEL_Y_ARRAY) { + block->update_flags |= VELOCITY_Y_ARRAY; + + float_array *y = &block->velocities_y; + if (!float_array_calloc(y, emitter->emission_number)) + return 0; + } + + if (OPTION_IS_VALUE(emitter->speed_x)) { + if (flags & ACCEL_X_SINGLE) { + block->update_flags |= VELOCITY_X_SINGLE; + block->s_velocity_x = option_get_value(&emitter->speed_x); + } + else if (flags & ACCEL_X_ARRAY) { + block->update_flags |= VELOCITY_X_ARRAY; + + float_array *x = &block->velocities_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->speed_x); + } + } + else { + block->update_flags |= VELOCITY_X_ARRAY; + + float_array *x = &block->velocities_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->speed_x); + } } return 1; @@ -568,29 +814,111 @@ alloc_and_init_velocities(DataBlock *block, Emitter *emitter) int alloc_and_init_accelerations(DataBlock *block, Emitter *emitter) { - /* Check if the emitter has no acceleration */ - const int alloc_x = emitter->acceleration_x.in_use; - const int alloc_y = emitter->acceleration_y.in_use; - - if (!alloc_x && !alloc_y) + const int x_set = OPTION_IS_SET(emitter->acceleration_x); + const int y_set = OPTION_IS_SET(emitter->acceleration_y); + + if (!x_set && !y_set) { + /* If both accelerations are not set, hust return. + * Both s_acceleration_x and s_acceleration_y are already 0 */ + block->update_flags |= + NO_ACCELERATION | NO_ACCELERATION_X | NO_ACCELERATION_Y; return 1; + } + else if (x_set && y_set) { + const int x_is_value = OPTION_IS_VALUE(emitter->acceleration_x); + const int y_is_value = OPTION_IS_VALUE(emitter->acceleration_y); - float_array *x = &block->accelerations_x; - float_array *y = &block->accelerations_y; + if (x_is_value && y_is_value) { + /* Both accelerations are set to a single constant value, the update + * function will just store a single value each */ + block->update_flags |= ACCEL_X_SINGLE | ACCEL_Y_SINGLE; - /* Allocate memory for the accelerations arrays */ - if (alloc_x && !float_array_alloc(x, emitter->emission_number)) - return 0; + block->s_acceleration_x = option_get_value(&emitter->acceleration_x); + block->s_acceleration_y = option_get_value(&emitter->acceleration_y); - if (alloc_y && !float_array_alloc(y, emitter->emission_number)) - return 0; + return 1; + } + else if (!x_is_value && !y_is_value) { + /* Both accelerations are randomized, the update function will store + * a random value for each particle */ + block->update_flags |= ACCEL_X_ARRAY | ACCEL_Y_ARRAY; + + float_array *x = &block->accelerations_x; + float_array *y = &block->accelerations_y; + + /* Allocate memory for the accelerations arrays */ + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + /* Initialize the accelerations arrays */ + for (int i = 0; i < emitter->emission_number; i++) { + x->data[i] = option_get_value(&emitter->acceleration_x); + y->data[i] = option_get_value(&emitter->acceleration_y); + } + } + else if (x_is_value && !y_is_value) { + /* Acceleration x is set to a single constant value, acceleration y + * is randomized */ + block->update_flags |= ACCEL_X_SINGLE | ACCEL_Y_ARRAY; + + block->s_acceleration_x = option_get_value(&emitter->acceleration_x); + + float_array *y = &block->accelerations_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->acceleration_y); + } + else if (!x_is_value && y_is_value) { + /* Acceleration y is set to a single constant value, acceleration x + * is randomized */ + block->update_flags |= ACCEL_X_ARRAY | ACCEL_Y_SINGLE; + + block->s_acceleration_y = option_get_value(&emitter->acceleration_y); + + float_array *x = &block->accelerations_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->acceleration_x); + } + } + else if (!x_set && y_set) { + block->update_flags |= NO_ACCELERATION_X; + if (OPTION_IS_VALUE(emitter->acceleration_y)) { + block->update_flags |= ACCEL_Y_SINGLE; + block->s_acceleration_y = option_get_value(&emitter->acceleration_y); + return 1; + } + block->update_flags |= ACCEL_Y_ARRAY; + + float_array *y = &block->accelerations_y; + if (!float_array_alloc(y, emitter->emission_number)) + return 0; + + for (int i = 0; i < emitter->emission_number; i++) + y->data[i] = option_get_value(&emitter->acceleration_y); + } + else if (x_set && !y_set) { + block->update_flags |= NO_ACCELERATION_Y; + if (OPTION_IS_VALUE(emitter->acceleration_x)) { + block->update_flags |= ACCEL_X_SINGLE; + block->s_acceleration_x = option_get_value(&emitter->acceleration_x); + return 1; + } + block->update_flags |= ACCEL_X_ARRAY; + + float_array *x = &block->accelerations_x; + if (!float_array_alloc(x, emitter->emission_number)) + return 0; - /* Initialize the accelerations arrays */ - for (int i = 0; i < emitter->emission_number; i++) { - if (alloc_x) - x->data[i] = genrand_from(&emitter->acceleration_x); - if (alloc_y) - y->data[i] = genrand_from(&emitter->acceleration_y); + for (int i = 0; i < emitter->emission_number; i++) + x->data[i] = option_get_value(&emitter->acceleration_x); } return 1; @@ -609,7 +937,7 @@ alloc_and_init_lifetimes(DataBlock *block, Emitter *emitter) return 0; for (int i = 0; i < num_particles; i++) - lifetimes->data[i] = genrand_from(&emitter->lifetime); + lifetimes->data[i] = option_get_value(&emitter->lifetime); /* Sort lifetimes in descending order */ qsort(lifetimes->data, num_particles, sizeof(float), _compare_desc); diff --git a/src/emitter.c b/src/emitter.c index c7d6924..a5d7966 100644 --- a/src/emitter.c +++ b/src/emitter.c @@ -1,5 +1,6 @@ #include "include/emitter.h" +/* ==================| Public facing EmitterObject functions |================== */ PyObject * emitter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -14,218 +15,46 @@ emitter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)self; } -static FORCEINLINE int -initGen_FromObj(PyObject *obj, generator *gen) -{ - /* Tries to extract either one or two floats from an object. - * If the object is a tuple, it must have one or two floats. - * If the object is a float, it is used as the minimum value. */ - - if (PyFloat_Check(obj)) { - gen->min = (float)PyFloat_AS_DOUBLE(obj); - gen->in_use = true; - - if (PyErr_Occurred()) { - PyErr_Clear(); - return 0; - } - - return 1; - } - else if (PyLong_Check(obj)) { - gen->min = (float)PyLong_AsDouble(obj); - gen->in_use = true; - - if (PyErr_Occurred()) { - PyErr_Clear(); - return 0; - } - - return 1; - } - - Py_ssize_t size; - - if (PyTuple_Check(obj)) { - size = PyTuple_GET_SIZE(obj); - if (size < 1 || size > 2) - return 0; - - if (!FloatFromObj(PyTuple_GET_ITEM(obj, 0), &gen->min)) - return 0; - - if (size == 2) { - if (!FloatFromObj(PyTuple_GET_ITEM(obj, 1), &gen->max)) - return 0; - - gen->randomize = 1; - } - } - else if (PyList_Check(obj)) { - size = PyList_GET_SIZE(obj); - if (size < 1 || size > 2) - return 0; - - if (!FloatFromObj(PyList_GET_ITEM(obj, 0), &gen->min)) - return 0; - - if (size == 2) { - if (!FloatFromObj(PyList_GET_ITEM(obj, 1), &gen->max)) - return 0; - gen->randomize = 1; - } - } - else if (PySequence_Check(obj)) { - size = PySequence_Length(obj); - if (size < 1 || size > 2) - return 0; - - if (!_FloatFromObjIndex(obj, 0, &gen->min)) - return 0; - - if (size == 2) { - if (!_FloatFromObjIndex(obj, 1, &gen->max)) - return 0; - gen->randomize = 1; - } - } - else { - return 0; - } - - if (gen->min > gen->max) { - float tmp = gen->min; - gen->min = gen->max; - gen->max = tmp; - } - - gen->in_use = true; - - return 1; -} - int emitter_init(EmitterObject *self, PyObject *args, PyObject *kwds) { - Emitter *emitter = &self->emitter; - static char *kwlist[] = { "emit_shape", "emit_number", "animation", "particle_lifetime", "speed_x", "speed_y", "acceleration_x", "acceleration_y", "blend_mode", NULL}; - PyObject *animation = NULL; - PyObject *lifetime_obj = NULL, *speedx_obj = NULL, *speedy_obj = NULL, - *accx_obj = NULL, *accy_obj = NULL; + PyObject *py_animation = NULL, *py_lifetime = NULL, *py_speed_x = NULL, + *py_speed_y = NULL, *py_acceleration_x = NULL, + *py_acceleration_y = NULL; + Emitter *emitter = &self->emitter; if (!PyArg_ParseTupleAndKeywords( args, kwds, "iiOO|OOOOi", kwlist, &emitter->spawn_shape, - &emitter->emission_number, &animation, &lifetime_obj, &speedx_obj, - &speedy_obj, &accx_obj, &accy_obj, &emitter->blend_mode)) { - return -1; - } - - switch (emitter->spawn_shape) { - case _POINT: - break; - default: - PyErr_SetString(PyExc_ValueError, "Invalid emitter spawn area shape"); - return -1; - } - - switch (emitter->blend_mode) { - case 0: - case 1: - break; - default: - PyErr_SetString(PyExc_ValueError, - "Invalid blend mode, supported blend modes are:" - " pygame.BLEND_ADD and pygame.BLENDMODE_NONE"); - return -1; - } - - if (PyTuple_Check(animation)) { - int len = PyTuple_GET_SIZE(animation); - if (len == 0) { - PyErr_SetString(PyExc_ValueError, "animation tuple is empty"); - return -1; - } - - emitter->num_frames = len; - - Py_INCREF(animation); - emitter->animation = animation; - - PyObject **items = PySequence_Fast_ITEMS(animation); - for (int i = 0; i < len; i++) { - PyObject *img = items[i]; - if (!pgSurface_Check(img)) { - PyErr_SetString( - PyExc_TypeError, - "Invalid image in animation, must be a pygame.Surface"); - return -1; - } - - pgSurfaceObject *surf_obj = (pgSurfaceObject *)img; - if (!surf_obj->surf) { - PyErr_SetString(PyExc_RuntimeError, "Surface is not initialized"); - return -1; - } - - SDL_Surface *surf = surf_obj->surf; - Uint8 alpha; - - /* Rule out unsupported image formats and flags */ - if (surf->format->BytesPerPixel != 4 || SDL_HasColorKey(surf) || - SDL_HasSurfaceRLE(surf) || (surf->flags & SDL_RLEACCEL) || - SDL_ISPIXELFORMAT_ALPHA(surf->format->format) || - (SDL_GetSurfaceAlphaMod(surf, &alpha) == 0 && alpha != 255)) { - PyErr_SetString( - PyExc_ValueError, - "Image must be 32-bit, non-RLE, non-alpha-modulated"); - return -1; - } - } - } - else { - PyErr_SetString( - PyExc_TypeError, - "Invalid animation argument, must be a tuple of pygame.Surface objects"); - return -1; - } - - if (lifetime_obj && !initGen_FromObj(lifetime_obj, &emitter->lifetime)) { - PyErr_SetString(PyExc_TypeError, "Invalid lifetime argument"); + &emitter->emission_number, &py_animation, &py_lifetime, &py_speed_x, + &py_speed_y, &py_acceleration_x, &py_acceleration_y, + &emitter->blend_mode)) { return -1; } - else { - emitter->lifetime.min = 60.0f; - } - if (speedx_obj && !initGen_FromObj(speedx_obj, &emitter->speed_x)) { - PyErr_SetString(PyExc_TypeError, "Invalid speed_x argument"); + if (!validate_emitter_shape(emitter->spawn_shape) || + !validate_blend_mode(emitter->blend_mode) || + !validate_animation(py_animation, emitter)) { return -1; } - if (speedy_obj && !initGen_FromObj(speedy_obj, &emitter->speed_y)) { - PyErr_SetString(PyExc_TypeError, "Invalid speed_y argument"); - return -1; - } + PyObject *py_options[] = {py_lifetime, py_speed_x, py_speed_y, py_acceleration_x, + py_acceleration_y}; - if (accx_obj && !initGen_FromObj(accx_obj, &emitter->acceleration_x)) { - PyErr_SetString(PyExc_TypeError, "Invalid acceleration_x argument"); - return -1; - } + Option *options[] = {&emitter->lifetime, &emitter->speed_x, &emitter->speed_y, + &emitter->acceleration_x, &emitter->acceleration_y}; - if (accy_obj && !initGen_FromObj(accy_obj, &emitter->acceleration_y)) { - PyErr_SetString(PyExc_TypeError, "Invalid acceleration_y argument"); + if (!init_emitter_options(py_options, options, 5)) return -1; - } return 0; } -#define TF(x) ((x) ? "Yes" : "No") +#define YN(x) (((x).state == OPTION_RANDOMIZED) ? "Yes" : "No") #define CREATE_PYFLOAT(var, value) \ var = PyFloat_FromDouble(value); \ if (PyErr_Occurred()) \ @@ -247,16 +76,22 @@ emitter_str(EmitterObject *self) PyObject *acceleration_y_min = NULL; PyObject *acceleration_y_max = NULL; - CREATE_PYFLOAT(lifetime_min, e->lifetime.min); - CREATE_PYFLOAT(lifetime_max, e->lifetime.max); - CREATE_PYFLOAT(speed_x_min, e->speed_x.min); - CREATE_PYFLOAT(speed_x_max, e->speed_x.max); - CREATE_PYFLOAT(speed_y_min, e->speed_y.min); - CREATE_PYFLOAT(speed_y_max, e->speed_y.max); - CREATE_PYFLOAT(acceleration_x_min, e->acceleration_x.min); - CREATE_PYFLOAT(acceleration_x_max, e->acceleration_x.max); - CREATE_PYFLOAT(acceleration_y_min, e->acceleration_y.min); - CREATE_PYFLOAT(acceleration_y_max, e->acceleration_y.max); + Option lifetime = e->lifetime; + Option speed_x = e->speed_x; + Option speed_y = e->speed_y; + Option acceleration_x = e->acceleration_x; + Option acceleration_y = e->acceleration_y; + + CREATE_PYFLOAT(lifetime_min, lifetime.min); + CREATE_PYFLOAT(lifetime_max, lifetime.max); + CREATE_PYFLOAT(speed_x_min, speed_x.min); + CREATE_PYFLOAT(speed_x_max, speed_x.max); + CREATE_PYFLOAT(speed_y_min, speed_y.min); + CREATE_PYFLOAT(speed_y_max, speed_y.max); + CREATE_PYFLOAT(acceleration_x_min, acceleration_x.min); + CREATE_PYFLOAT(acceleration_x_max, acceleration_x.max); + CREATE_PYFLOAT(acceleration_y_min, acceleration_y.min); + CREATE_PYFLOAT(acceleration_y_max, acceleration_y.max); char *spawn_shape_str; switch (e->spawn_shape) { @@ -268,12 +103,6 @@ emitter_str(EmitterObject *self) break; } - generator *lifetime = &e->lifetime; - generator *speed_x = &e->speed_x; - generator *speed_y = &e->speed_y; - generator *accel_x = &e->acceleration_x; - generator *accel_y = &e->acceleration_y; - PyObject *str = PyUnicode_FromFormat( "Emitter(" "\n spawn_shape: %s" @@ -286,10 +115,10 @@ emitter_str(EmitterObject *self) "\n acceleration_y: %R to %R rng: %s" "\n)", spawn_shape_str, e->emission_number, e->num_frames, lifetime_min, - lifetime_max, TF(lifetime->randomize), speed_x_min, speed_x_max, - TF(speed_x->randomize), speed_y_min, speed_y_max, TF(speed_y->randomize), - acceleration_x_min, acceleration_x_max, TF(accel_x->randomize), - acceleration_y_min, acceleration_y_max, TF(accel_y->randomize)); + lifetime_max, YN(lifetime), speed_x_min, speed_x_max, YN(speed_x), + speed_y_min, speed_y_max, YN(speed_y), acceleration_x_min, + acceleration_x_max, YN(acceleration_x), acceleration_y_min, + acceleration_y_max, YN(acceleration_y)); Py_DECREF(lifetime_min); Py_DECREF(lifetime_max); @@ -319,8 +148,8 @@ emitter_str(EmitterObject *self) return NULL; } -#undef TF #undef CREATE_PYFLOAT +#undef YN void emitter_dealloc(EmitterObject *self) @@ -329,3 +158,86 @@ emitter_dealloc(EmitterObject *self) Py_TYPE(self)->tp_free((PyObject *)self); } + +/* ====================| Internal EmitterObject functions |==================== */ +int +validate_emitter_shape(int shape) +{ + if (shape == _POINT) + return 1; + + PyErr_SetString(PyExc_ValueError, "Invalid emitter spawn area shape"); + return 0; +} + +int +validate_blend_mode(int mode) +{ + if (mode == 0 || mode == 1) + return 1; + + PyErr_SetString(PyExc_ValueError, + "Invalid blend mode, supported modes are: " + "pygame.BLEND_ADD and pygame.BLENDMODE_NONE"); + return 0; +} + +int +validate_animation(PyObject *py_animation, Emitter *emitter) +{ + if (!PyTuple_Check(py_animation)) { + PyErr_SetString( + PyExc_TypeError, + "Invalid animation argument, must be a tuple of pygame.Surface objects"); + return 0; + } + + int len = PyTuple_GET_SIZE(py_animation); + if (len == 0) { + PyErr_SetString(PyExc_ValueError, "Animation tuple is empty"); + return 0; + } + + Py_INCREF(py_animation); + emitter->animation = py_animation; + emitter->num_frames = len; + + PyObject **items = PySequence_Fast_ITEMS(py_animation); + for (int i = 0; i < len; i++) { + PyObject *img = items[i]; + if (!pgSurface_Check(img)) { + PyErr_SetString(PyExc_TypeError, + "Invalid image in animation, must be a pygame.Surface"); + return 0; + } + + pgSurfaceObject *surf_obj = (pgSurfaceObject *)img; + if (!surf_obj->surf) { + PyErr_SetString(PyExc_RuntimeError, "Surface is not initialized"); + return 0; + } + + SDL_Surface *surf = surf_obj->surf; + Uint8 alpha; + + if (surf->format->BytesPerPixel != 4 || SDL_HasColorKey(surf) || + SDL_HasSurfaceRLE(surf) || (surf->flags & SDL_RLEACCEL) || + SDL_ISPIXELFORMAT_ALPHA(surf->format->format) || + (SDL_GetSurfaceAlphaMod(surf, &alpha) == 0 && alpha != 255)) { + PyErr_SetString(PyExc_ValueError, + "Image must be 32-bit, non-RLE, non-alpha-modulated"); + return 0; + } + } + return 1; +} + +int +init_emitter_options(PyObject *py_objs[], Option *options[], int count) +{ + for (int i = 0; i < count; i++) + if (!init_option_from_pyobj(py_objs[i], options[i])) + return 0; + + return 1; +} diff --git a/src/include/MT19937.h b/src/include/MT19937.h index 097bb05..11f0a32 100644 --- a/src/include/MT19937.h +++ b/src/include/MT19937.h @@ -110,17 +110,4 @@ rand_int_between(int lo, int hi) { /* Returns a random integer in the range [lo, hi] */ return (int)(lo + (genrand_int32() % (hi - lo + 1))); -} - -typedef struct { - float min; - float max; - int randomize; - bool in_use; -} generator; - -static float -genrand_from(const generator *g) -{ - return g->randomize ? rand_between(g->min, g->max) : g->min; -} +} \ No newline at end of file diff --git a/src/include/data_block.h b/src/include/data_block.h index 4109af0..f04f9ed 100644 --- a/src/include/data_block.h +++ b/src/include/data_block.h @@ -1,7 +1,6 @@ #pragma once #include "float_array.h" -#include "MT19937.h" #include "emitter.h" #define UNROLL_2(x) \ @@ -19,6 +18,25 @@ x; \ x; +typedef enum { + // Velocity flags + VELOCITY_X_SINGLE = 1 << 0, // Single s_velocity_x + VELOCITY_Y_SINGLE = 1 << 1, // Single s_velocity_y + VELOCITY_X_ARRAY = 1 << 2, // Velocity x array + VELOCITY_Y_ARRAY = 1 << 3, // Velocity y array + + // Acceleration flags + ACCEL_X_SINGLE = 1 << 4, // Single s_acceleration_x + ACCEL_Y_SINGLE = 1 << 5, // Single s_acceleration_y + ACCEL_X_ARRAY = 1 << 6, // Acceleration x array + ACCEL_Y_ARRAY = 1 << 7, // Acceleration y array + + NO_ACCELERATION = 1 << 8, // No acceleration + NO_ACCELERATION_X = 1 << 9, // No acceleration x + NO_ACCELERATION_Y = 1 << 10, // No acceleration y + NO_VELOCITY = 1 << 11, // No velocity +} UpdateFunctionFlags; + typedef struct { int animation_index; int length; @@ -48,6 +66,12 @@ typedef struct DataBlock { float_array max_lifetimes; int *animation_indices; + float s_velocity_x; + float s_velocity_y; + float s_acceleration_x; + float s_acceleration_y; + float s_lifetime; + int num_frames; PyObject *animation; FragmentationMap frag_map; @@ -56,6 +80,7 @@ typedef struct DataBlock { bool ended; int particles_count; + int update_flags; void (*updater)(struct DataBlock *, float); } DataBlock; diff --git a/src/include/effect_instance.h b/src/include/effect_instance.h index 5c8e7c9..88b77c7 100644 --- a/src/include/effect_instance.h +++ b/src/include/effect_instance.h @@ -2,7 +2,6 @@ #include "base.h" #include "float_array.h" -#include "MT19937.h" #include "data_block.h" #include "particle_effect.h" diff --git a/src/include/emitter.h b/src/include/emitter.h index 4b9d145..ab19b8b 100644 --- a/src/include/emitter.h +++ b/src/include/emitter.h @@ -1,8 +1,8 @@ #pragma once #include -#include "MT19937.h" #include "base.h" +#include "option.h" typedef enum { _POINT, @@ -13,17 +13,17 @@ typedef struct { EmitterSpawnShape spawn_shape; /* Core emitter settings */ - int emission_number; /* number of particles to emit */ + int emission_number; /* Core particle settings */ PyObject *animation; /* python tuple containing animation frames */ - int num_frames; /* animation frames number */ + int num_frames; - generator lifetime; - generator speed_x; - generator speed_y; - generator acceleration_x; - generator acceleration_y; + Option lifetime; + Option speed_x; + Option speed_y; + Option acceleration_x; + Option acceleration_y; /* Additional particle settings */ int blend_mode; @@ -37,6 +37,8 @@ extern PyTypeObject Emitter_Type; #define Emitter_Check(o) (Py_TYPE(o) == &Emitter_Type) +/* ==================| Public facing EmitterObject functions |================== */ + PyObject * emitter_new(PyTypeObject *type, PyObject *args, PyObject *kwds); @@ -48,3 +50,17 @@ emitter_str(EmitterObject *self); void emitter_dealloc(EmitterObject *self); + +/* ====================| Internal EmitterObject functions |==================== */ + +int +validate_emitter_shape(int shape); + +int +validate_blend_mode(int mode); + +int +validate_animation(PyObject *py_animation, Emitter *emitter); + +int +init_emitter_options(PyObject *py_objs[], Option *options[], int count); \ No newline at end of file diff --git a/src/include/option.h b/src/include/option.h new file mode 100644 index 0000000..0098b83 --- /dev/null +++ b/src/include/option.h @@ -0,0 +1,34 @@ +#pragma once +#include "base.h" +#include "MT19937.h" + +typedef enum { OPTION_UNSET = 0, OPTION_VALUE, OPTION_RANDOMIZED } OptionState; + +typedef struct { + float value; + float min; + float max; + OptionState state; +} Option; + +#define OPTION_IS_SET(opt) ((opt).state != OPTION_UNSET) +#define OPTION_IS_RANDOMIZED(opt) ((opt).state == OPTION_RANDOMIZED) +#define OPTION_IS_VALUE(opt) ((opt).state == OPTION_VALUE) + +float FORCEINLINE +option_get_value(const Option *opt) +{ + switch (opt->state) { + case OPTION_UNSET: + return 0.0f; + case OPTION_VALUE: + return opt->value; + case OPTION_RANDOMIZED: + return rand_between(opt->min, opt->max); + default: + return 0.0f; + } +} + +int +init_option_from_pyobj(PyObject *obj, Option *opt); \ No newline at end of file diff --git a/src/include/particle_manager.h b/src/include/particle_manager.h index 0285962..09480d1 100644 --- a/src/include/particle_manager.h +++ b/src/include/particle_manager.h @@ -2,7 +2,6 @@ #include "particle_effect.h" #include "effect_instance.h" -#include "MT19937.h" #include #define PM_BASE_BLOCK_SIZE 10 diff --git a/src/option.c b/src/option.c new file mode 100644 index 0000000..24e1ab5 --- /dev/null +++ b/src/option.c @@ -0,0 +1,79 @@ +#include "include/option.h" + +int +init_option_from_pyobj(PyObject *obj, Option *opt) +{ + if (!obj || obj == Py_None) { + opt->state = OPTION_UNSET; + return 1; + } + + if (PyFloat_Check(obj)) { + opt->value = (float)PyFloat_AS_DOUBLE(obj); + opt->state = OPTION_VALUE; + return 1; + } + else if (PyLong_Check(obj)) { + opt->value = (float)PyLong_AsDouble(obj); + opt->state = OPTION_VALUE; + + return PyErr_Occurred() ? 0 : 1; + } + + Py_ssize_t size; + + if (PyTuple_Check(obj)) { + size = PyTuple_GET_SIZE(obj); + if (size != 2) { + PyErr_SetString(PyExc_ValueError, "Tuple must have exactly 2 elements"); + return 0; + } + + if (!FloatFromObj(PyTuple_GET_ITEM(obj, 0), &opt->min) || + !FloatFromObj(PyTuple_GET_ITEM(obj, 1), &opt->max)) { + PyErr_SetString(PyExc_ValueError, "Invalid float value in tuple"); + return 0; + } + } + else if (PyList_Check(obj)) { + size = PyList_GET_SIZE(obj); + if (size != 2) { + PyErr_SetString(PyExc_ValueError, "List must have exactly 2 elements"); + return 0; + } + + if (!FloatFromObj(PyList_GET_ITEM(obj, 0), &opt->min) || + !FloatFromObj(PyList_GET_ITEM(obj, 1), &opt->max)) { + PyErr_SetString(PyExc_ValueError, "Invalid float value in list"); + return 0; + } + } + else if (PySequence_Check(obj)) { + size = PySequence_Length(obj); + if (size != 2) { + PyErr_SetString(PyExc_ValueError, + "Sequence must have exactly 2 elements"); + return 0; + } + + if (!_FloatFromObjIndex(obj, 0, &opt->min) || + !_FloatFromObjIndex(obj, 1, &opt->max)) { + PyErr_SetString(PyExc_ValueError, "Invalid float value in sequence"); + return 0; + } + } + else { + PyErr_SetString(PyExc_TypeError, "Invalid type for option"); + return 0; + } + + if (opt->min > opt->max) { + float tmp = opt->min; + opt->min = opt->max; + opt->max = tmp; + } + + opt->state = OPTION_RANDOMIZED; + + return 1; +} diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_particle_manager.py b/test/test_particle_manager.py deleted file mode 100644 index beea402..0000000 --- a/test/test_particle_manager.py +++ /dev/null @@ -1,11 +0,0 @@ -import unittest -from itz_particle_manager import ParticleManager - - -class TestParticleManager(unittest.TestCase): - def test_init(self): - pm = ParticleManager() - - -if __name__ == "__main__": - unittest.main()