diff options
author | Luca Rood <dev@lucarood.com> | 2017-05-19 18:13:48 +0300 |
---|---|---|
committer | Luca Rood <dev@lucarood.com> | 2017-05-19 18:23:19 +0300 |
commit | 42804d49b54a4522423849c402b6d55521b18512 (patch) | |
tree | 2d9bd93457b0defd65204a6189abab900db73178 /source | |
parent | c62bec8d99bc17c861353cccf50e8b05ab0af973 (diff) |
Implement particle drawing with draw manager
This still has a couple of issues:
* Instancing is not working when multiple particle systems use the same
primitive. Only the last particle system to be drawn with a particular
primitive shows up.
* Because of colors being passed as uniforms with static variables, the
color of the collection of the last object to be evauluated is used for
all particles being displayed.
Also, note that while this is being drawn in the clay engine, this might
be moved to the object mode later intead.
Part of T51378
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/draw/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/draw/engines/clay/clay_engine.c | 116 | ||||
-rw-r--r-- | source/blender/draw/engines/clay/shaders/particle_prim_frag.glsl | 19 | ||||
-rw-r--r-- | source/blender/draw/engines/clay/shaders/particle_prim_vert.glsl | 43 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache.c | 140 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache.h | 2 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache_impl.h | 1 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache_impl_particles.c | 49 |
8 files changed, 360 insertions, 12 deletions
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 45c52187f72..ac648fe1e5b 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -111,6 +111,8 @@ data_to_c_simple(engines/clay/shaders/ssao_alchemy.glsl SRC) data_to_c_simple(engines/clay/shaders/ssao_groundtruth.glsl SRC) data_to_c_simple(engines/clay/shaders/particle_vert.glsl SRC) data_to_c_simple(engines/clay/shaders/particle_strand_frag.glsl SRC) +data_to_c_simple(engines/clay/shaders/particle_prim_vert.glsl SRC) +data_to_c_simple(engines/clay/shaders/particle_prim_frag.glsl SRC) data_to_c_simple(engines/eevee/shaders/default_frag.glsl SRC) data_to_c_simple(engines/eevee/shaders/default_world_frag.glsl SRC) diff --git a/source/blender/draw/engines/clay/clay_engine.c b/source/blender/draw/engines/clay/clay_engine.c index 8e66b287fda..4bb8920a2f9 100644 --- a/source/blender/draw/engines/clay/clay_engine.c +++ b/source/blender/draw/engines/clay/clay_engine.c @@ -31,6 +31,8 @@ #include "BLI_dynstr.h" #include "BLI_rand.h" +#include "GPU_shader.h" + #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -56,6 +58,8 @@ extern char datatoc_clay_vert_glsl[]; extern char datatoc_ssao_alchemy_glsl[]; extern char datatoc_particle_vert_glsl[]; extern char datatoc_particle_strand_frag_glsl[]; +extern char datatoc_particle_prim_vert_glsl[]; +extern char datatoc_particle_prim_frag_glsl[]; /* *********** LISTS *********** */ @@ -128,6 +132,7 @@ typedef struct CLAY_PassList { struct DRWPass *clay_pass; struct DRWPass *clay_pass_flat; struct DRWPass *hair_pass; + struct DRWPass *part_pass; } CLAY_PassList; typedef struct CLAY_Data { @@ -147,6 +152,8 @@ static struct { struct GPUShader *clay_sh; struct GPUShader *clay_flat_sh; struct GPUShader *hair_sh; + struct GPUShader *part_dot_sh; + struct GPUShader *part_prim_sh; /* Matcap textures */ struct GPUTexture *matcap_array; @@ -175,7 +182,10 @@ typedef struct CLAY_PrivateData { DRWShadingGroup *depth_shgrp_cull; DRWShadingGroup *depth_shgrp_cull_select; DRWShadingGroup *depth_shgrp_cull_active; - DRWShadingGroup *hair; + DRWShadingGroup *part_dot_shgrp; + DRWShadingGroup *part_cross_shgrp; + DRWShadingGroup *part_circle_shgrp; + DRWShadingGroup *part_axis_shgrp; } CLAY_PrivateData; /* Transient data */ /* Functions */ @@ -364,6 +374,14 @@ static void CLAY_engine_init(void *vedata) e_data.hair_sh = DRW_shader_create(datatoc_particle_vert_glsl, NULL, datatoc_particle_strand_frag_glsl, "#define MAX_MATERIAL 512\n"); } + if (!e_data.part_prim_sh) { + e_data.part_prim_sh = DRW_shader_create(datatoc_particle_prim_vert_glsl, NULL, datatoc_particle_prim_frag_glsl, NULL); + } + + if (!e_data.part_dot_sh) { + e_data.part_dot_sh = GPU_shader_get_builtin_shader(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA); + } + if (!stl->storage) { stl->storage = MEM_callocN(sizeof(CLAY_Storage), "CLAY_Storage"); } @@ -712,6 +730,32 @@ static void CLAY_cache_init(void *vedata) stl->storage->hair_ubo_current_id = 0; memset(stl->storage->hair_shgrps, 0, sizeof(DRWShadingGroup *) * MAX_CLAY_MAT); } + + { + psl->part_pass = DRW_pass_create("Particle Pass", DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_POINT | DRW_STATE_BLEND); + + static int screen_space[2] = {0, 1}; + + stl->g_data->part_dot_shgrp = DRW_shgroup_create(e_data.part_dot_sh, psl->part_pass); + + stl->g_data->part_cross_shgrp = DRW_shgroup_instance_create(e_data.part_prim_sh, psl->part_pass, DRW_cache_particles_get_prim(PART_DRAW_CROSS)); + DRW_shgroup_uniform_int(stl->g_data->part_cross_shgrp, "screen_space", &screen_space[0], 1); + DRW_shgroup_uniform_float(stl->g_data->part_cross_shgrp, "pixel_size", DRW_viewport_pixelsize_get(), 1); + DRW_shgroup_attrib_float(stl->g_data->part_cross_shgrp, "pos", 3); + DRW_shgroup_attrib_float(stl->g_data->part_cross_shgrp, "rot", 4); + + stl->g_data->part_circle_shgrp = DRW_shgroup_instance_create(e_data.part_prim_sh, psl->part_pass, DRW_cache_particles_get_prim(PART_DRAW_CIRC)); + DRW_shgroup_uniform_int(stl->g_data->part_circle_shgrp, "screen_space", &screen_space[1], 1); + DRW_shgroup_uniform_float(stl->g_data->part_circle_shgrp, "pixel_size", DRW_viewport_pixelsize_get(), 1); + DRW_shgroup_attrib_float(stl->g_data->part_circle_shgrp, "pos", 3); + DRW_shgroup_attrib_float(stl->g_data->part_circle_shgrp, "rot", 4); + + stl->g_data->part_axis_shgrp = DRW_shgroup_instance_create(e_data.part_prim_sh, psl->part_pass, DRW_cache_particles_get_prim(PART_DRAW_AXIS)); + DRW_shgroup_uniform_int(stl->g_data->part_axis_shgrp, "screen_space", &screen_space[0], 1); + DRW_shgroup_uniform_float(stl->g_data->part_axis_shgrp, "pixel_size", DRW_viewport_pixelsize_get(), 1); + DRW_shgroup_attrib_float(stl->g_data->part_axis_shgrp, "pos", 3); + DRW_shgroup_attrib_float(stl->g_data->part_axis_shgrp, "rot", 4); + } } static void CLAY_cache_populate(void *vedata, Object *ob) @@ -780,21 +824,67 @@ static void CLAY_cache_populate(void *vedata, Object *ob) draw_as = PART_DRAW_DOT; } - switch (draw_as) { - case PART_DRAW_PATH: - geom = DRW_cache_particles_get_hair(psys); - break; - default: - geom = NULL; - break; - } + static float mat[4][4]; + unit_m4(mat); - if (geom) { - static float mat[4][4]; - unit_m4(mat); + if (draw_as == PART_DRAW_PATH) { + geom = DRW_cache_particles_get_hair(psys); hair_shgrp = CLAY_hair_shgrp_get(ob, stl, psl); DRW_shgroup_call_add(hair_shgrp, geom, mat); } + else { + IDProperty *props = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_CLAY); + int matcap_icon = BKE_collection_engine_property_value_get_int(props, "matcap_icon"); + float matcap_hue = BKE_collection_engine_property_value_get_float(props, "matcap_hue"); + float matcap_sat = BKE_collection_engine_property_value_get_float(props, "matcap_saturation"); + float matcap_val = BKE_collection_engine_property_value_get_float(props, "matcap_value"); + static float size; + static float axis_size; + static float col[4]; + float hsv[3]; + + size = (float)part->draw_size; + axis_size = size * 2.0f; + + copy_v3_v3(col, e_data.matcap_colors[matcap_to_index(matcap_icon)]); + rgb_to_hsv_v(col, hsv); + + hsv[0] = fmod(hsv[0] + matcap_hue - 0.5f, 1.0f); + hsv[1] *= matcap_sat * 2.0f; + CLAMP(hsv[1], 0.0f, 1.0f); + hsv[2] *= matcap_val * 2.0f; + CLAMP(hsv[2], 0.0f, 1.0f); + + hsv_to_rgb_v(hsv, col); + col[3] = 1.0f; + + geom = DRW_cache_particles_get_dots(psys); + + switch (draw_as) { + case PART_DRAW_DOT: + DRW_shgroup_uniform_vec4(stl->g_data->part_dot_shgrp, "color", col, 1); + DRW_shgroup_uniform_float(stl->g_data->part_dot_shgrp, "size", &size, 1); + DRW_shgroup_call_add(stl->g_data->part_dot_shgrp, geom, mat); + break; + case PART_DRAW_CROSS: + DRW_shgroup_uniform_vec4(stl->g_data->part_cross_shgrp, "color", col, 1); + DRW_shgroup_uniform_float(stl->g_data->part_cross_shgrp, "draw_size", &size, 1); + DRW_shgroup_instance_batch(stl->g_data->part_cross_shgrp, geom); + break; + case PART_DRAW_CIRC: + DRW_shgroup_uniform_vec4(stl->g_data->part_circle_shgrp, "color", col, 1); + DRW_shgroup_uniform_float(stl->g_data->part_circle_shgrp, "draw_size", &size, 1); + DRW_shgroup_instance_batch(stl->g_data->part_circle_shgrp, geom); + break; + case PART_DRAW_AXIS: + DRW_shgroup_uniform_vec4(stl->g_data->part_axis_shgrp, "color", col, 1); + DRW_shgroup_uniform_float(stl->g_data->part_axis_shgrp, "draw_size", &axis_size, 1); + DRW_shgroup_instance_batch(stl->g_data->part_axis_shgrp, geom); + break; + default: + break; + } + } } } } @@ -839,6 +929,7 @@ static void CLAY_draw_scene(void *vedata) DRW_draw_pass(psl->clay_pass); DRW_draw_pass(psl->clay_pass_flat); DRW_draw_pass(psl->hair_pass); + DRW_draw_pass(psl->part_pass); } static void CLAY_layer_collection_settings_create(RenderEngine *UNUSED(engine), IDProperty *props) @@ -874,6 +965,7 @@ static void CLAY_engine_free(void) DRW_SHADER_FREE_SAFE(e_data.clay_sh); DRW_SHADER_FREE_SAFE(e_data.clay_flat_sh); DRW_SHADER_FREE_SAFE(e_data.hair_sh); + DRW_SHADER_FREE_SAFE(e_data.part_prim_sh); DRW_TEXTURE_FREE_SAFE(e_data.matcap_array); DRW_TEXTURE_FREE_SAFE(e_data.jitter_tx); DRW_TEXTURE_FREE_SAFE(e_data.sampling_tx); diff --git a/source/blender/draw/engines/clay/shaders/particle_prim_frag.glsl b/source/blender/draw/engines/clay/shaders/particle_prim_frag.glsl new file mode 100644 index 00000000000..50572f293d9 --- /dev/null +++ b/source/blender/draw/engines/clay/shaders/particle_prim_frag.glsl @@ -0,0 +1,19 @@ + +uniform vec4 color; + +flat in int finalAxis; + +out vec4 fragColor; + +void main() +{ + if (finalAxis == -1) { + fragColor = color; + } + else { + vec4 col = vec4(0.0); + col[finalAxis] = 1.0; + col.a = 1.0; + fragColor = col; + } +} diff --git a/source/blender/draw/engines/clay/shaders/particle_prim_vert.glsl b/source/blender/draw/engines/clay/shaders/particle_prim_vert.glsl new file mode 100644 index 00000000000..2483ca5b2d2 --- /dev/null +++ b/source/blender/draw/engines/clay/shaders/particle_prim_vert.glsl @@ -0,0 +1,43 @@ + +uniform mat4 ModelViewProjectionMatrix; +uniform mat4 ViewProjectionMatrix; +uniform mat4 ModelViewMatrix; +uniform mat4 ProjectionMatrix; +uniform int screen_space; +uniform float pixel_size; +uniform float draw_size; + +in vec3 pos; +in vec4 rot; +in vec3 inst_pos; +in int axis; + +flat out int finalAxis; + +vec3 rotate(vec3 vec, vec4 quat) +{ + /* The quaternion representation here stores the w component in the first index */ + return vec + 2.0 * cross(quat.yzw, cross(quat.yzw, vec) + quat.x * vec); +} + +float mul_project_m4_v3_zfac(in vec3 co) +{ + return (ViewProjectionMatrix[0][3] * co.x) + + (ViewProjectionMatrix[1][3] * co.y) + + (ViewProjectionMatrix[2][3] * co.z) + ViewProjectionMatrix[3][3]; +} + +void main() +{ + float pix_size = mul_project_m4_v3_zfac(pos) * pixel_size; + + if (screen_space == 1) { + gl_Position = ModelViewMatrix * vec4(pos, 1.0) + vec4(inst_pos * pix_size * draw_size, 0.0); + gl_Position = ProjectionMatrix * gl_Position; + } + else { + gl_Position = ModelViewProjectionMatrix * vec4(pos + rotate(inst_pos * pix_size * draw_size, rot), 1.0); + } + + finalAxis = axis; +} diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 79b4d391fa4..db1565c8e96 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -80,6 +80,9 @@ static struct DRWShapeCache { Batch *drw_camera; Batch *drw_camera_tria; Batch *drw_camera_focus; + Batch *drw_particle_cross; + Batch *drw_particle_circle; + Batch *drw_particle_axis; } SHC = {NULL}; void DRW_shape_cache_free(void) @@ -124,6 +127,9 @@ void DRW_shape_cache_free(void) BATCH_DISCARD_ALL_SAFE(SHC.drw_camera); BATCH_DISCARD_ALL_SAFE(SHC.drw_camera_tria); BATCH_DISCARD_ALL_SAFE(SHC.drw_camera_focus); + BATCH_DISCARD_ALL_SAFE(SHC.drw_particle_cross); + BATCH_DISCARD_ALL_SAFE(SHC.drw_particle_circle); + BATCH_DISCARD_ALL_SAFE(SHC.drw_particle_axis); } @@ -2206,3 +2212,137 @@ Batch *DRW_cache_particles_get_hair(ParticleSystem *psys) { return DRW_particles_batch_cache_get_hair(psys); } + +Batch *DRW_cache_particles_get_dots(ParticleSystem *psys) +{ + return DRW_particles_batch_cache_get_dots(psys); +} + +Batch *DRW_cache_particles_get_prim(int type) +{ + switch (type) { + case PART_DRAW_CROSS: + if (!SHC.drw_particle_cross) { + static VertexFormat format = { 0 }; + static unsigned pos_id, axis_id; + + if (format.attrib_ct == 0) { + pos_id = VertexFormat_add_attrib(&format, "inst_pos", COMP_F32, 3, KEEP_FLOAT); + axis_id = VertexFormat_add_attrib(&format, "axis", COMP_I32, 1, KEEP_INT); + } + + VertexBuffer *vbo = VertexBuffer_create_with_format(&format); + VertexBuffer_allocate_data(vbo, 6); + + /* X axis */ + float co[3] = {-1.0f, 0.0f, 0.0f}; + int axis = -1; + VertexBuffer_set_attrib(vbo, pos_id, 0, co); + + co[0] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 1, co); + VertexBuffer_set_attrib(vbo, axis_id, 1, &axis); + + /* Y axis */ + co[0] = 0.0f; + co[1] = -1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 2, co); + + co[1] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 3, co); + VertexBuffer_set_attrib(vbo, axis_id, 3, &axis); + + /* Z axis */ + co[1] = 0.0f; + co[2] = -1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 4, co); + + co[2] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 5, co); + VertexBuffer_set_attrib(vbo, axis_id, 5, &axis); + + SHC.drw_particle_cross = Batch_create(PRIM_LINES, vbo, NULL); + } + + return SHC.drw_particle_cross; + case PART_DRAW_AXIS: + if (!SHC.drw_particle_axis) { + static VertexFormat format = { 0 }; + static unsigned pos_id, axis_id; + + if (format.attrib_ct == 0) { + pos_id = VertexFormat_add_attrib(&format, "inst_pos", COMP_F32, 3, KEEP_FLOAT); + axis_id = VertexFormat_add_attrib(&format, "axis", COMP_I32, 1, KEEP_INT); + } + + VertexBuffer *vbo = VertexBuffer_create_with_format(&format); + VertexBuffer_allocate_data(vbo, 6); + + /* X axis */ + float co[3] = {0.0f, 0.0f, 0.0f}; + int axis = 0; + VertexBuffer_set_attrib(vbo, pos_id, 0, co); + + co[0] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 1, co); + VertexBuffer_set_attrib(vbo, axis_id, 1, &axis); + + /* Y axis */ + co[0] = 0.0f; + axis = 1; + VertexBuffer_set_attrib(vbo, pos_id, 2, co); + + co[1] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 3, co); + VertexBuffer_set_attrib(vbo, axis_id, 3, &axis); + + /* Z axis */ + co[1] = 0.0f; + axis = 2; + VertexBuffer_set_attrib(vbo, pos_id, 4, co); + + co[2] = 1.0f; + VertexBuffer_set_attrib(vbo, pos_id, 5, co); + VertexBuffer_set_attrib(vbo, axis_id, 5, &axis); + + SHC.drw_particle_axis = Batch_create(PRIM_LINES, vbo, NULL); + } + + return SHC.drw_particle_axis; + case PART_DRAW_CIRC: +#define CIRCLE_RESOL 32 + if (!SHC.drw_particle_circle) { + float v[3] = {0.0f, 0.0f, 0.0f}; + int axis = -1; + + static VertexFormat format = { 0 }; + static unsigned pos_id, axis_id; + + if (format.attrib_ct == 0) { + pos_id = VertexFormat_add_attrib(&format, "inst_pos", COMP_F32, 3, KEEP_FLOAT); + axis_id = VertexFormat_add_attrib(&format, "axis", COMP_I32, 1, KEEP_INT); + } + + VertexBuffer *vbo = VertexBuffer_create_with_format(&format); + VertexBuffer_allocate_data(vbo, CIRCLE_RESOL); + + for (int a = 0; a < CIRCLE_RESOL; a++) { + v[0] = sinf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL)); + v[1] = cosf((2.0f * M_PI * a) / ((float)CIRCLE_RESOL)); + v[2] = 0.0f; + VertexBuffer_set_attrib(vbo, pos_id, a, v); + VertexBuffer_set_attrib(vbo, axis_id, a, &axis); + } + + SHC.drw_particle_circle = Batch_create(PRIM_LINE_LOOP, vbo, NULL); + } + + return SHC.drw_particle_circle; +#undef CIRCLE_RESOL + default: + BLI_assert(false); + break; + } + + return NULL; +} diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index b6805cd6477..a37b9998ff6 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -137,5 +137,7 @@ struct Batch *DRW_cache_lattice_vert_overlay_get(struct Object *ob); /* Particles */ struct Batch *DRW_cache_particles_get_hair(struct ParticleSystem *psys); +struct Batch *DRW_cache_particles_get_dots(struct ParticleSystem *psys); +struct Batch *DRW_cache_particles_get_prim(int type); #endif /* __DRAW_CACHE_H__ */ diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h index 3dd6b07dd71..812c873c153 100644 --- a/source/blender/draw/intern/draw_cache_impl.h +++ b/source/blender/draw/intern/draw_cache_impl.h @@ -93,5 +93,6 @@ struct Batch *DRW_mesh_batch_cache_get_overlay_facedots(struct Mesh *me); /* Particles */ struct Batch *DRW_particles_batch_cache_get_hair(struct ParticleSystem *psys); +struct Batch *DRW_particles_batch_cache_get_dots(struct ParticleSystem *psys); #endif /* __DRAW_CACHE_IMPL_H__ */ diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index d4247a0e365..e51873ca9a5 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -268,6 +268,43 @@ static void particle_batch_cache_ensure_pos_and_seg(ParticleSystem *psys, Partic } } +static void particle_batch_cache_ensure_pos(ParticleSystem *psys, ParticleBatchCache *cache) +{ + if (cache->pos == NULL) { + static VertexFormat format = { 0 }; + static unsigned pos_id, rot_id; + int i, curr_point; + ParticleData *pa; + + VERTEXBUFFER_DISCARD_SAFE(cache->pos); + ELEMENTLIST_DISCARD_SAFE(cache->segments); + + if (format.attrib_ct == 0) { + /* initialize vertex format */ + pos_id = VertexFormat_add_attrib(&format, "pos", COMP_F32, 3, KEEP_FLOAT); + rot_id = VertexFormat_add_attrib(&format, "rot", COMP_F32, 4, KEEP_FLOAT); + } + + cache->pos = VertexBuffer_create_with_format(&format); + VertexBuffer_allocate_data(cache->pos, psys->totpart); + + for (curr_point = 0, i = 0, pa = psys->particles; i < psys->totpart; i++, pa++) { + if (pa->state.time >= pa->time && pa->state.time < pa->dietime && + !(pa->flag & (PARS_NO_DISP | PARS_UNEXIST))) + { + VertexBuffer_set_attrib(cache->pos, pos_id, curr_point, pa->state.co); + VertexBuffer_set_attrib(cache->pos, rot_id, curr_point, pa->state.rot); + + curr_point++; + } + } + + if (curr_point != psys->totpart) { + VertexBuffer_resize_data(cache->pos, curr_point); + } + } +} + Batch *DRW_particles_batch_cache_get_hair(ParticleSystem *psys) { ParticleBatchCache *cache = particle_batch_cache_get(psys); @@ -280,3 +317,15 @@ Batch *DRW_particles_batch_cache_get_hair(ParticleSystem *psys) return cache->hairs; } + +Batch *DRW_particles_batch_cache_get_dots(ParticleSystem *psys) +{ + ParticleBatchCache *cache = particle_batch_cache_get(psys); + + if (cache->hairs == NULL) { + particle_batch_cache_ensure_pos(psys, cache); + cache->hairs = Batch_create(PRIM_POINTS, cache->pos, NULL); + } + + return cache->hairs; +} |