Viewport smoke: add support to render the volume using a color ramp.

This is yet another debug option that allows to render an arbitrary
simulation field by using a color ramp to inspect its voxel values.
Note that when using this, fire rendering is turned off.

Reviewers: plasmasolutions, gottfried

Differential Revision: https://developer.blender.org/D1733
This commit is contained in:
Kévin Dietrich 2016-10-30 12:29:05 +01:00
parent 4e68f48227
commit b6d35e1fa7
12 changed files with 236 additions and 82 deletions

View File

@ -397,6 +397,14 @@ class PHYSICS_PT_smoke_display_settings(PhysicButtonsPanel, Panel):
col.prop(domain, "vector_draw_type")
col.prop(domain, "vector_scale")
layout.separator()
layout.label(text="Color Mapping:")
layout.prop(domain, "use_color_ramp")
col = layout.column();
col.enabled = domain.use_color_ramp
col.prop(domain, "coba_field")
col.template_color_ramp(domain, "color_ramp", expand=True)
if __name__ == "__main__": # only for live edit.
bpy.utils.register_module(__name__)

View File

@ -360,6 +360,10 @@ static void smokeModifier_freeDomain(SmokeModifierData *smd)
BKE_ptcache_free_list(&(smd->domain->ptcaches[0]));
smd->domain->point_cache[0] = NULL;
if (smd->domain->coba) {
MEM_freeN(smd->domain->coba);
}
MEM_freeN(smd->domain);
smd->domain = NULL;
}
@ -544,6 +548,9 @@ void smokeModifier_createType(struct SmokeModifierData *smd)
smd->domain->slice_depth = 0.5f;
smd->domain->slice_axis = 0;
smd->domain->vector_scale = 1.0f;
smd->domain->coba = NULL;
smd->domain->coba_field = FLUID_FIELD_DENSITY;
}
else if (smd->type & MOD_SMOKE_TYPE_FLOW)
{
@ -646,6 +653,10 @@ void smokeModifier_copy(struct SmokeModifierData *smd, struct SmokeModifierData
tsmd->domain->draw_velocity = smd->domain->draw_velocity;
tsmd->domain->vector_draw_type = smd->domain->vector_draw_type;
tsmd->domain->vector_scale = smd->domain->vector_scale;
if (smd->domain->coba) {
tsmd->domain->coba = MEM_dupallocN(smd->domain->coba);
}
}
else if (tsmd->flow) {
tsmd->flow->psys = smd->flow->psys;

View File

@ -5091,6 +5091,7 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
smd->domain->tex = NULL;
smd->domain->tex_shadow = NULL;
smd->domain->tex_wt = NULL;
smd->domain->coba = newdataadr(fd, smd->domain->coba);
smd->domain->effector_weights = newdataadr(fd, smd->domain->effector_weights);
if (!smd->domain->effector_weights)

View File

@ -1728,6 +1728,10 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
smd->domain->point_cache[1]->step = 1;
write_pointcaches(wd, &(smd->domain->ptcaches[1]));
if (smd->domain->coba) {
writestruct(wd, DATA, ColorBand, 1, smd->domain->coba);
}
}
writestruct(wd, DATA, SmokeDomainSettings, 1, smd->domain);

View File

@ -7915,10 +7915,6 @@ void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short
if (!render_override && sds->draw_velocity) {
draw_smoke_velocity(sds, viewnormal);
}
#ifdef SMOKE_DEBUG_HEAT
draw_smoke_heat(smd->domain, ob);
#endif
}
}

View File

@ -1,4 +1,4 @@
/*
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
@ -41,6 +41,7 @@
#include "BLI_math.h"
#include "BKE_DerivedMesh.h"
#include "BKE_texture.h"
#include "BKE_particle.h"
#include "smoke_API.h"
@ -62,28 +63,33 @@ struct GPUTexture;
# include "PIL_time_utildefines.h"
#endif
static GPUTexture *create_flame_spectrum_texture(void)
/* *************************** Transfer functions *************************** */
enum {
TFUNC_FLAME_SPECTRUM = 0,
TFUNC_COLOR_RAMP = 1,
};
#define TFUNC_WIDTH 256
static void create_flame_spectrum_texture(float *data)
{
#define SPEC_WIDTH 256
#define FIRE_THRESH 7
#define MAX_FIRE_ALPHA 0.06f
#define FULL_ON_FIRE 100
GPUTexture *tex;
int i, j, k;
float *spec_data = MEM_mallocN(SPEC_WIDTH * 4 * sizeof(float), "spec_data");
float *spec_pixels = MEM_mallocN(SPEC_WIDTH * 4 * 16 * 16 * sizeof(float), "spec_pixels");
float *spec_pixels = MEM_mallocN(TFUNC_WIDTH * 4 * 16 * 16 * sizeof(float), "spec_pixels");
blackbody_temperature_to_rgb_table(spec_data, SPEC_WIDTH, 1500, 3000);
blackbody_temperature_to_rgb_table(data, TFUNC_WIDTH, 1500, 3000);
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
for (k = 0; k < SPEC_WIDTH; k++) {
int index = (j * SPEC_WIDTH * 16 + i * SPEC_WIDTH + k) * 4;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
for (int k = 0; k < TFUNC_WIDTH; k++) {
int index = (j * TFUNC_WIDTH * 16 + i * TFUNC_WIDTH + k) * 4;
if (k >= FIRE_THRESH) {
spec_pixels[index] = (spec_data[k * 4]);
spec_pixels[index + 1] = (spec_data[k * 4 + 1]);
spec_pixels[index + 2] = (spec_data[k * 4 + 2]);
spec_pixels[index] = (data[k * 4]);
spec_pixels[index + 1] = (data[k * 4 + 1]);
spec_pixels[index + 2] = (data[k * 4 + 2]);
spec_pixels[index + 3] = MAX_FIRE_ALPHA * (
(k > FULL_ON_FIRE) ? 1.0f : (k - FIRE_THRESH) / ((float)FULL_ON_FIRE - FIRE_THRESH));
}
@ -94,19 +100,69 @@ static GPUTexture *create_flame_spectrum_texture(void)
}
}
tex = GPU_texture_create_1D(SPEC_WIDTH, spec_pixels, NULL);
memcpy(data, spec_pixels, sizeof(float) * 4 * TFUNC_WIDTH);
MEM_freeN(spec_data);
MEM_freeN(spec_pixels);
#undef SPEC_WIDTH
#undef FIRE_THRESH
#undef MAX_FIRE_ALPHA
#undef FULL_ON_FIRE
}
static void create_color_ramp(const ColorBand *coba, float *data)
{
for (int i = 0; i < TFUNC_WIDTH; i++) {
do_colorband(coba, (float)i / TFUNC_WIDTH, &data[i * 4]);
}
}
static GPUTexture *create_transfer_function(int type, const ColorBand *coba)
{
float *data = MEM_mallocN(sizeof(float) * 4 * TFUNC_WIDTH, __func__);
switch (type) {
case TFUNC_FLAME_SPECTRUM:
create_flame_spectrum_texture(data);
break;
case TFUNC_COLOR_RAMP:
create_color_ramp(coba, data);
break;
}
GPUTexture *tex = GPU_texture_create_1D(TFUNC_WIDTH, data, NULL);
MEM_freeN(data);
return tex;
}
static GPUTexture *create_field_texture(SmokeDomainSettings *sds)
{
float *field = NULL;
switch (sds->coba_field) {
#ifdef WITH_SMOKE
case FLUID_FIELD_DENSITY: field = smoke_get_density(sds->fluid); break;
case FLUID_FIELD_HEAT: field = smoke_get_heat(sds->fluid); break;
case FLUID_FIELD_FUEL: field = smoke_get_fuel(sds->fluid); break;
case FLUID_FIELD_REACT: field = smoke_get_react(sds->fluid); break;
case FLUID_FIELD_FLAME: field = smoke_get_flame(sds->fluid); break;
case FLUID_FIELD_VELOCITY_X: field = smoke_get_velocity_x(sds->fluid); break;
case FLUID_FIELD_VELOCITY_Y: field = smoke_get_velocity_y(sds->fluid); break;
case FLUID_FIELD_VELOCITY_Z: field = smoke_get_velocity_z(sds->fluid); break;
case FLUID_FIELD_COLOR_R: field = smoke_get_color_r(sds->fluid); break;
case FLUID_FIELD_COLOR_G: field = smoke_get_color_g(sds->fluid); break;
case FLUID_FIELD_COLOR_B: field = smoke_get_color_b(sds->fluid); break;
case FLUID_FIELD_FORCE_X: field = smoke_get_force_x(sds->fluid); break;
case FLUID_FIELD_FORCE_Y: field = smoke_get_force_y(sds->fluid); break;
case FLUID_FIELD_FORCE_Z: field = smoke_get_force_z(sds->fluid); break;
#endif
default: return NULL;
}
return GPU_texture_create_3D(sds->res[0], sds->res[1], sds->res[2], 1, field);
}
typedef struct VolumeSlicer {
float size[3];
float min[3];
@ -347,6 +403,7 @@ static int create_view_aligned_slices(VolumeSlicer *slicer,
}
static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture *tex_spec,
GPUTexture *tex_tfunc, GPUTexture *tex_coba,
bool use_fire, const float min[3],
const float ob_sizei[3], const float invsize[3])
{
@ -359,6 +416,8 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture
int densityscale_location;
int spec_location, flame_location;
int shadow_location, actcol_location;
int tfunc_location = 0;
int coba_location = 0;
if (use_fire) {
spec_location = GPU_shader_get_uniform(shader, "spectrum_texture");
@ -370,6 +429,11 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture
soot_location = GPU_shader_get_uniform(shader, "soot_texture");
stepsize_location = GPU_shader_get_uniform(shader, "step_size");
densityscale_location = GPU_shader_get_uniform(shader, "density_scale");
if (sds->use_coba) {
tfunc_location = GPU_shader_get_uniform(shader, "transfer_texture");
coba_location = GPU_shader_get_uniform(shader, "color_band_texture");
}
}
GPU_shader_bind(shader);
@ -397,6 +461,14 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture
if ((sds->active_fields & SM_ACTIVE_COLORS) == 0)
mul_v3_v3(active_color, sds->active_color);
GPU_shader_uniform_vector(shader, actcol_location, 3, 1, active_color);
if (sds->use_coba) {
GPU_texture_bind(tex_tfunc, 4);
GPU_shader_uniform_texture(shader, tfunc_location, tex_tfunc);
GPU_texture_bind(tex_coba, 5);
GPU_shader_uniform_texture(shader, coba_location, tex_coba);
}
}
GPU_shader_uniform_vector(shader, min_location, 3, 1, min);
@ -404,7 +476,8 @@ static void bind_shader(SmokeDomainSettings *sds, GPUShader *shader, GPUTexture
GPU_shader_uniform_vector(shader, invsize_location, 3, 1, invsize);
}
static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec, bool use_fire)
static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec,
GPUTexture *tex_tfunc, GPUTexture *tex_coba, bool use_fire)
{
GPU_shader_unbind();
@ -417,20 +490,30 @@ static void unbind_shader(SmokeDomainSettings *sds, GPUTexture *tex_spec, bool u
}
else {
GPU_texture_unbind(sds->tex_shadow);
if (sds->use_coba) {
GPU_texture_unbind(tex_tfunc);
GPU_texture_free(tex_tfunc);
GPU_texture_unbind(tex_coba);
GPU_texture_free(tex_coba);
}
}
}
static void draw_buffer(SmokeDomainSettings *sds, GPUShader *shader, const VolumeSlicer *slicer,
const float ob_sizei[3], const float invsize[3], const int num_points, const bool do_fire)
{
GPUTexture *tex_spec = (do_fire) ? create_flame_spectrum_texture() : NULL;
GPUTexture *tex_spec = (do_fire) ? create_transfer_function(TFUNC_FLAME_SPECTRUM, NULL) : NULL;
GPUTexture *tex_tfunc = (sds->use_coba) ? create_transfer_function(TFUNC_COLOR_RAMP, sds->coba) : NULL;
GPUTexture *tex_coba = (sds->use_coba) ? create_field_texture(sds) : NULL;
GLuint vertex_buffer;
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * num_points, &slicer->verts[0][0], GL_STATIC_DRAW);
bind_shader(sds, shader, tex_spec, do_fire, slicer->min, ob_sizei, invsize);
bind_shader(sds, shader, tex_spec, tex_tfunc, tex_coba, do_fire, slicer->min, ob_sizei, invsize);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, NULL);
@ -439,7 +522,7 @@ static void draw_buffer(SmokeDomainSettings *sds, GPUShader *shader, const Volum
glDisableClientState(GL_VERTEX_ARRAY);
unbind_shader(sds, tex_spec, do_fire);
unbind_shader(sds, tex_spec, tex_tfunc, tex_coba, do_fire);
/* cleanup */
@ -459,7 +542,16 @@ void draw_smoke_volume(SmokeDomainSettings *sds, Object *ob,
const bool use_fire = (sds->active_fields & SM_ACTIVE_FIRE) && sds->tex_flame;
GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_SMOKE);
GPUBuiltinShader builtin_shader;
if (sds->use_coba) {
builtin_shader = GPU_SHADER_SMOKE_COBA;
}
else {
builtin_shader = GPU_SHADER_SMOKE;
}
GPUShader *shader = GPU_shader_get_builtin_shader(builtin_shader);
if (!shader) {
fprintf(stderr, "Unable to create GLSL smoke shader.\n");
@ -549,7 +641,7 @@ void draw_smoke_volume(SmokeDomainSettings *sds, Object *ob,
draw_buffer(sds, shader, &slicer, ob_sizei, invsize, num_points, false);
/* Draw fire separately (T47639). */
if (use_fire) {
if (use_fire && !sds->use_coba) {
glBlendFunc(GL_ONE, GL_ONE);
draw_buffer(sds, fire_shader, &slicer, ob_sizei, invsize, num_points, true);
}
@ -759,50 +851,3 @@ void draw_smoke_velocity(SmokeDomainSettings *domain, float viewnormal[3])
UNUSED_VARS(domain, viewnormal);
#endif
}
#ifdef SMOKE_DEBUG_HEAT
void draw_smoke_heat(SmokeDomainSettings *domain, Object *ob)
{
float x, y, z;
float x0, y0, z0;
int *base_res = domain->base_res;
int *res = domain->res;
int *res_min = domain->res_min;
int *res_max = domain->res_max;
float *heat = smoke_get_heat(domain->fluid);
float min[3];
float *cell_size = domain->cell_size;
float step_size = ((float)max_iii(base_res[0], base_res[1], base_res[2])) / 16.f;
float vf = domain->scale / 16.f * 2.f; /* velocity factor */
/* set first position so that it doesn't jump when domain moves */
x0 = res_min[0] + fmod(-(float)domain->shift[0] + res_min[0], step_size);
y0 = res_min[1] + fmod(-(float)domain->shift[1] + res_min[1], step_size);
z0 = res_min[2] + fmod(-(float)domain->shift[2] + res_min[2], step_size);
if (x0 < res_min[0]) x0 += step_size;
if (y0 < res_min[1]) y0 += step_size;
if (z0 < res_min[2]) z0 += step_size;
add_v3_v3v3(min, domain->p0, domain->obj_shift_f);
for (x = floor(x0); x < res_max[0]; x += step_size)
for (y = floor(y0); y < res_max[1]; y += step_size)
for (z = floor(z0); z < res_max[2]; z += step_size) {
int index = (floor(x) - res_min[0]) + (floor(y) - res_min[1]) * res[0] + (floor(z) - res_min[2]) * res[0] * res[1];
float pos[3] = {min[0] + ((float)x + 0.5f) * cell_size[0], min[1] + ((float)y + 0.5f) * cell_size[1], min[2] + ((float)z + 0.5f) * cell_size[2]};
/* draw heat as different sized points */
if (heat[index] >= 0.01f) {
float col_gb = 1.0f - heat[index];
CLAMP(col_gb, 0.0f, 1.0f);
glColor3f(1.0f, col_gb, col_gb);
glPointSize(24.0f * heat[index]);
glBegin(GL_POINTS);
glVertex3f(pos[0], pos[1], pos[2]);
glEnd();
}
}
}
#endif

View File

@ -296,14 +296,8 @@ void draw_smoke_volume(struct SmokeDomainSettings *sds, struct Object *ob,
const float min[3], const float max[3],
const float viewnormal[3]);
//#define SMOKE_DEBUG_HEAT
void draw_smoke_velocity(struct SmokeDomainSettings *domain, float viewnormal[3]);
#ifdef SMOKE_DEBUG_HEAT
void draw_smoke_heat(struct SmokeDomainSettings *domain, struct Object *ob);
#endif
/* workaround for trivial but noticeable camera bug caused by imprecision
* between view border calculation in 2D/3D space, workaround for bug [#28037].
* without this define we get the old behavior which is to try and align them

View File

@ -89,6 +89,7 @@ typedef enum GPUBuiltinShader {
GPU_SHADER_SEP_GAUSSIAN_BLUR = 1,
GPU_SHADER_SMOKE = 2,
GPU_SHADER_SMOKE_FIRE = 3,
GPU_SHADER_SMOKE_COBA = 4,
} GPUBuiltinShader;
GPUShader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader);

View File

@ -68,6 +68,7 @@ static struct GPUShadersGlobal {
GPUShader *sep_gaussian_blur;
GPUShader *smoke;
GPUShader *smoke_fire;
GPUShader *smoke_coba;
/* cache for shader fx. Those can exist in combinations so store them here */
GPUShader *fx_shaders[MAX_FX_SHADERS * 2];
} shaders;
@ -623,6 +624,13 @@ GPUShader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader)
NULL, NULL, NULL, 0, 0, 0);
retval = GG.shaders.smoke_fire;
break;
case GPU_SHADER_SMOKE_COBA:
if (!GG.shaders.smoke_coba)
GG.shaders.smoke_coba = GPU_shader_create(
datatoc_gpu_shader_smoke_vert_glsl, datatoc_gpu_shader_smoke_frag_glsl,
NULL, NULL, "#define USE_COBA;\n", 0, 0, 0);
retval = GG.shaders.smoke_coba;
break;
}
if (retval == NULL)
@ -734,6 +742,11 @@ void GPU_shader_free_builtin_shaders(void)
GG.shaders.smoke_fire = NULL;
}
if (GG.shaders.smoke_coba) {
GPU_shader_free(GG.shaders.smoke_coba);
GG.shaders.smoke_coba = NULL;
}
for (i = 0; i < 2 * MAX_FX_SHADERS; ++i) {
if (GG.shaders.fx_shaders[i]) {
GPU_shader_free(GG.shaders.fx_shaders[i]);

View File

@ -8,10 +8,17 @@ uniform float density_scale;
uniform sampler3D soot_texture;
uniform sampler3D shadow_texture;
#ifdef USE_COBA
uniform sampler1D transfer_texture;
uniform sampler3D color_band_texture;
#endif
void main()
{
/* compute color and density from volume texture */
vec4 soot = texture3D(soot_texture, coords);
#ifndef USE_COBA
vec3 soot_color;
if (soot.a != 0) {
soot_color = active_color * soot.rgb / soot.a;
@ -31,6 +38,11 @@ void main()
/* premultiply alpha */
vec4 color = vec4(soot_alpha * soot_color, soot_alpha);
#else
float color_band = texture3D(color_band_texture, coords).r;
vec4 transfer_function = texture1D(transfer_texture, color_band);
vec4 color = transfer_function * density_scale;
#endif
gl_FragColor = color;
}

View File

@ -77,6 +77,23 @@ enum {
VECTOR_DRAW_STREAMLINE = 1,
};
enum {
FLUID_FIELD_DENSITY = 0,
FLUID_FIELD_HEAT = 1,
FLUID_FIELD_FUEL = 2,
FLUID_FIELD_REACT = 3,
FLUID_FIELD_FLAME = 4,
FLUID_FIELD_VELOCITY_X = 5,
FLUID_FIELD_VELOCITY_Y = 6,
FLUID_FIELD_VELOCITY_Z = 7,
FLUID_FIELD_COLOR_R = 8,
FLUID_FIELD_COLOR_G = 9,
FLUID_FIELD_COLOR_B = 10,
FLUID_FIELD_FORCE_X = 11,
FLUID_FIELD_FORCE_Y = 12,
FLUID_FIELD_FORCE_Z = 13,
};
/* cache compression */
#define SM_CACHE_LIGHT 0
#define SM_CACHE_HEAVY 1
@ -193,9 +210,13 @@ typedef struct SmokeDomainSettings {
float slice_per_voxel;
float slice_depth;
float display_thickness;
struct ColorBand *coba;
float vector_scale;
char vector_draw_type;
char pad2[3];
char use_coba;
char coba_field; /* simulation field used for the color mapping */
char pad2;
} SmokeDomainSettings;

View File

@ -30,6 +30,7 @@
#include <limits.h>
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "rna_internal.h"
@ -53,6 +54,7 @@
#include "BKE_context.h"
#include "BKE_depsgraph.h"
#include "BKE_particle.h"
#include "BKE_texture.h"
#include "smoke_API.h"
@ -383,6 +385,17 @@ static void rna_SmokeFlow_uvlayer_set(PointerRNA *ptr, const char *value)
rna_object_uvlayer_name_set(ptr, value, flow->uvlayer_name, sizeof(flow->uvlayer_name));
}
static void rna_Smoke_use_color_ramp_set(PointerRNA *ptr, int value)
{
SmokeDomainSettings *sds = (SmokeDomainSettings *)ptr->data;
sds->use_coba = value;
if (value && sds->coba == NULL) {
sds->coba = add_colorband(false);
}
}
#else
static void rna_def_smoke_domain_settings(BlenderRNA *brna)
@ -805,6 +818,41 @@ static void rna_def_smoke_domain_settings(BlenderRNA *brna)
RNA_def_property_ui_range(prop, 0.0, 100.0, 0.1, 3);
RNA_def_property_ui_text(prop, "Scale", "Multiplier for scaling the vectors");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
/* --------- Color mapping. --------- */
prop = RNA_def_property(srna, "use_color_ramp", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_coba", 0);
RNA_def_property_boolean_funcs(prop, NULL, "rna_Smoke_use_color_ramp_set");
RNA_def_property_ui_text(prop, "Use Color Ramp",
"Render a simulation field while mapping its voxels values to the colors of a ramp");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
static EnumPropertyItem coba_field_items[] = {
{FLUID_FIELD_COLOR_R, "COLOR_R", 0, "Red", "Red component of the color field"},
{FLUID_FIELD_COLOR_G, "COLOR_G", 0, "Green", "Green component of the color field"},
{FLUID_FIELD_COLOR_B, "COLOR_B", 0, "Blue", "Blue component of the color field"},
{FLUID_FIELD_DENSITY, "DENSITY", 0, "Density", "Quantity of soot in the fluid"},
{FLUID_FIELD_FLAME, "FLAME", 0, "Flame", "Flame field"},
{FLUID_FIELD_FUEL, "FUEL", 0, "Fuel", "Fuel field"},
{FLUID_FIELD_HEAT, "HEAT", 0, "Heat", "Temperature of the fluid"},
{FLUID_FIELD_VELOCITY_X, "VELOCITY_X", 0, "X Velocity", "X component of the velocity field"},
{FLUID_FIELD_VELOCITY_Y, "VELOCITY_Y", 0, "Y Velocity", "Y component of the velocity field"},
{FLUID_FIELD_VELOCITY_Z, "VELOCITY_Z", 0, "Z Velocity", "Z component of the velocity field"},
{0, NULL, 0, NULL, NULL}
};
prop = RNA_def_property(srna, "coba_field", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "coba_field");
RNA_def_property_enum_items(prop, coba_field_items);
RNA_def_property_ui_text(prop, "Field", "Simulation field to color map");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
prop = RNA_def_property(srna, "color_ramp", PROP_POINTER, PROP_NEVER_NULL);
RNA_def_property_pointer_sdna(prop, NULL, "coba");
RNA_def_property_struct_type(prop, "ColorRamp");
RNA_def_property_ui_text(prop, "Color Ramp", "");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL);
}
static void rna_def_smoke_flow_settings(BlenderRNA *brna)