This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/fluid.c
Sebastián Barschkis b023c91118 Fluid: Use dynamic mode whenever active rigid bodies are in the scene
Required for collisions with moving rigid bodies. Otherwise the static optimization mode will be kept and the obstacles would be calculated only once at the beginning.
2020-03-29 21:31:20 +02:00

5217 lines
177 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_fileops.h"
#include "BLI_hash.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "DNA_fluid_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_rigidbody_types.h"
#include "BKE_effect.h"
#include "BKE_fluid.h"
#include "BKE_global.h"
#include "BKE_lib_id.h"
#include "BKE_modifier.h"
#include "BKE_pointcache.h"
#ifdef WITH_FLUID
# include <float.h>
# include <math.h>
# include <stdio.h>
# include <string.h> /* memset */
# include "DNA_customdata_types.h"
# include "DNA_light_types.h"
# include "DNA_mesh_types.h"
# include "DNA_meshdata_types.h"
# include "DNA_particle_types.h"
# include "DNA_scene_types.h"
# include "BLI_kdopbvh.h"
# include "BLI_kdtree.h"
# include "BLI_threads.h"
# include "BLI_voxel.h"
# include "BKE_bvhutils.h"
# include "BKE_collision.h"
# include "BKE_colortools.h"
# include "BKE_customdata.h"
# include "BKE_deform.h"
# include "BKE_mesh.h"
# include "BKE_mesh_runtime.h"
# include "BKE_object.h"
# include "BKE_particle.h"
# include "BKE_scene.h"
# include "BKE_texture.h"
# include "DEG_depsgraph.h"
# include "DEG_depsgraph_query.h"
# include "RE_shader_ext.h"
# include "manta_fluid_API.h"
#endif /* WITH_FLUID */
/** Time step default value for nice appearance. */
#define DT_DEFAULT 0.1f
/** Max value for phi initialization */
#define PHI_MAX 9999.0f
static void BKE_fluid_modifier_reset_ex(struct FluidModifierData *mmd, bool need_lock);
#ifdef WITH_FLUID
// #define DEBUG_PRINT
/* -------------------------------------------------------------------- */
/** \name Fluid API
* \{ */
static ThreadMutex object_update_lock = BLI_MUTEX_INITIALIZER;
struct FluidModifierData;
struct Mesh;
struct Object;
struct Scene;
# define ADD_IF_LOWER_POS(a, b) (min_ff((a) + (b), max_ff((a), (b))))
# define ADD_IF_LOWER_NEG(a, b) (max_ff((a) + (b), min_ff((a), (b))))
# define ADD_IF_LOWER(a, b) (((b) > 0) ? ADD_IF_LOWER_POS((a), (b)) : ADD_IF_LOWER_NEG((a), (b)))
bool BKE_fluid_reallocate_fluid(FluidDomainSettings *mds, int res[3], int free_old)
{
if (free_old && mds->fluid) {
manta_free(mds->fluid);
}
if (!min_iii(res[0], res[1], res[2])) {
mds->fluid = NULL;
}
else {
mds->fluid = manta_init(res, mds->mmd);
mds->res_noise[0] = res[0] * mds->noise_scale;
mds->res_noise[1] = res[1] * mds->noise_scale;
mds->res_noise[2] = res[2] * mds->noise_scale;
}
return (mds->fluid != NULL);
}
void BKE_fluid_reallocate_copy_fluid(FluidDomainSettings *mds,
int o_res[3],
int n_res[3],
int o_min[3],
int n_min[3],
int o_max[3],
int o_shift[3],
int n_shift[3])
{
int x, y, z;
struct MANTA *fluid_old = mds->fluid;
const int block_size = mds->noise_scale;
int new_shift[3] = {0};
sub_v3_v3v3_int(new_shift, n_shift, o_shift);
/* allocate new fluid data */
BKE_fluid_reallocate_fluid(mds, n_res, 0);
int o_total_cells = o_res[0] * o_res[1] * o_res[2];
int n_total_cells = n_res[0] * n_res[1] * n_res[2];
/* boundary cells will be skipped when copying data */
int bwidth = mds->boundary_width;
/* copy values from old fluid to new */
if (o_total_cells > 1 && n_total_cells > 1) {
/* base smoke */
float *o_dens, *o_react, *o_flame, *o_fuel, *o_heat, *o_vx, *o_vy, *o_vz, *o_r, *o_g, *o_b;
float *n_dens, *n_react, *n_flame, *n_fuel, *n_heat, *n_vx, *n_vy, *n_vz, *n_r, *n_g, *n_b;
float dummy, *dummy_s;
int *dummy_p;
/* noise smoke */
int wt_res_old[3];
float *o_wt_dens, *o_wt_react, *o_wt_flame, *o_wt_fuel, *o_wt_tcu, *o_wt_tcv, *o_wt_tcw,
*o_wt_tcu2, *o_wt_tcv2, *o_wt_tcw2, *o_wt_r, *o_wt_g, *o_wt_b;
float *n_wt_dens, *n_wt_react, *n_wt_flame, *n_wt_fuel, *n_wt_tcu, *n_wt_tcv, *n_wt_tcw,
*n_wt_tcu2, *n_wt_tcv2, *n_wt_tcw2, *n_wt_r, *n_wt_g, *n_wt_b;
if (mds->flags & FLUID_DOMAIN_USE_NOISE) {
manta_smoke_turbulence_export(fluid_old,
&o_wt_dens,
&o_wt_react,
&o_wt_flame,
&o_wt_fuel,
&o_wt_r,
&o_wt_g,
&o_wt_b,
&o_wt_tcu,
&o_wt_tcv,
&o_wt_tcw,
&o_wt_tcu2,
&o_wt_tcv2,
&o_wt_tcw2);
manta_smoke_turbulence_get_res(fluid_old, wt_res_old);
manta_smoke_turbulence_export(mds->fluid,
&n_wt_dens,
&n_wt_react,
&n_wt_flame,
&n_wt_fuel,
&n_wt_r,
&n_wt_g,
&n_wt_b,
&n_wt_tcu,
&n_wt_tcv,
&n_wt_tcw,
&n_wt_tcu2,
&n_wt_tcv2,
&n_wt_tcw2);
}
manta_smoke_export(fluid_old,
&dummy,
&dummy,
&o_dens,
&o_react,
&o_flame,
&o_fuel,
&o_heat,
&o_vx,
&o_vy,
&o_vz,
&o_r,
&o_g,
&o_b,
&dummy_p,
&dummy_s);
manta_smoke_export(mds->fluid,
&dummy,
&dummy,
&n_dens,
&n_react,
&n_flame,
&n_fuel,
&n_heat,
&n_vx,
&n_vy,
&n_vz,
&n_r,
&n_g,
&n_b,
&dummy_p,
&dummy_s);
for (x = o_min[0]; x < o_max[0]; x++) {
for (y = o_min[1]; y < o_max[1]; y++) {
for (z = o_min[2]; z < o_max[2]; z++) {
/* old grid index */
int xo = x - o_min[0];
int yo = y - o_min[1];
int zo = z - o_min[2];
int index_old = manta_get_index(xo, o_res[0], yo, o_res[1], zo);
/* new grid index */
int xn = x - n_min[0] - new_shift[0];
int yn = y - n_min[1] - new_shift[1];
int zn = z - n_min[2] - new_shift[2];
int index_new = manta_get_index(xn, n_res[0], yn, n_res[1], zn);
/* skip if outside new domain */
if (xn < 0 || xn >= n_res[0] || yn < 0 || yn >= n_res[1] || zn < 0 || zn >= n_res[2]) {
continue;
}
/* skip if trying to copy from old boundary cell */
if (xo < bwidth || yo < bwidth || zo < bwidth || xo >= o_res[0] - bwidth ||
yo >= o_res[1] - bwidth || zo >= o_res[2] - bwidth) {
continue;
}
/* skip if trying to copy into new boundary cell */
if (xn < bwidth || yn < bwidth || zn < bwidth || xn >= n_res[0] - bwidth ||
yn >= n_res[1] - bwidth || zn >= n_res[2] - bwidth) {
continue;
}
/* copy data */
if (mds->flags & FLUID_DOMAIN_USE_NOISE) {
int i, j, k;
/* old grid index */
int xx_o = xo * block_size;
int yy_o = yo * block_size;
int zz_o = zo * block_size;
/* new grid index */
int xx_n = xn * block_size;
int yy_n = yn * block_size;
int zz_n = zn * block_size;
/* insert old texture values into new texture grids */
n_wt_tcu[index_new] = o_wt_tcu[index_old];
n_wt_tcv[index_new] = o_wt_tcv[index_old];
n_wt_tcw[index_new] = o_wt_tcw[index_old];
n_wt_tcu2[index_new] = o_wt_tcu2[index_old];
n_wt_tcv2[index_new] = o_wt_tcv2[index_old];
n_wt_tcw2[index_new] = o_wt_tcw2[index_old];
for (i = 0; i < block_size; i++) {
for (j = 0; j < block_size; j++) {
for (k = 0; k < block_size; k++) {
int big_index_old = manta_get_index(
xx_o + i, wt_res_old[0], yy_o + j, wt_res_old[1], zz_o + k);
int big_index_new = manta_get_index(
xx_n + i, mds->res_noise[0], yy_n + j, mds->res_noise[1], zz_n + k);
/* copy data */
n_wt_dens[big_index_new] = o_wt_dens[big_index_old];
if (n_wt_flame && o_wt_flame) {
n_wt_flame[big_index_new] = o_wt_flame[big_index_old];
n_wt_fuel[big_index_new] = o_wt_fuel[big_index_old];
n_wt_react[big_index_new] = o_wt_react[big_index_old];
}
if (n_wt_r && o_wt_r) {
n_wt_r[big_index_new] = o_wt_r[big_index_old];
n_wt_g[big_index_new] = o_wt_g[big_index_old];
n_wt_b[big_index_new] = o_wt_b[big_index_old];
}
}
}
}
}
n_dens[index_new] = o_dens[index_old];
/* heat */
if (n_heat && o_heat) {
n_heat[index_new] = o_heat[index_old];
}
/* fuel */
if (n_fuel && o_fuel) {
n_flame[index_new] = o_flame[index_old];
n_fuel[index_new] = o_fuel[index_old];
n_react[index_new] = o_react[index_old];
}
/* color */
if (o_r && n_r) {
n_r[index_new] = o_r[index_old];
n_g[index_new] = o_g[index_old];
n_b[index_new] = o_b[index_old];
}
n_vx[index_new] = o_vx[index_old];
n_vy[index_new] = o_vy[index_old];
n_vz[index_new] = o_vz[index_old];
}
}
}
}
manta_free(fluid_old);
}
void BKE_fluid_cache_free(FluidDomainSettings *mds, Object *ob, int cache_map)
{
char temp_dir[FILE_MAX];
int flags = mds->cache_flag;
const char *relbase = modifier_path_relbase_from_global(ob);
if (cache_map & FLUID_DOMAIN_OUTDATED_DATA) {
flags &= ~(FLUID_DOMAIN_BAKING_DATA | FLUID_DOMAIN_BAKED_DATA | FLUID_DOMAIN_OUTDATED_DATA);
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_CONFIG, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_DATA, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_SCRIPT, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
mds->cache_frame_pause_data = 0;
}
if (cache_map & FLUID_DOMAIN_OUTDATED_NOISE) {
flags &= ~(FLUID_DOMAIN_BAKING_NOISE | FLUID_DOMAIN_BAKED_NOISE | FLUID_DOMAIN_OUTDATED_NOISE);
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_NOISE, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
mds->cache_frame_pause_noise = 0;
}
if (cache_map & FLUID_DOMAIN_OUTDATED_MESH) {
flags &= ~(FLUID_DOMAIN_BAKING_MESH | FLUID_DOMAIN_BAKED_MESH | FLUID_DOMAIN_OUTDATED_MESH);
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_MESH, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
mds->cache_frame_pause_mesh = 0;
}
if (cache_map & FLUID_DOMAIN_OUTDATED_PARTICLES) {
flags &= ~(FLUID_DOMAIN_BAKING_PARTICLES | FLUID_DOMAIN_BAKED_PARTICLES |
FLUID_DOMAIN_OUTDATED_PARTICLES);
BLI_path_join(
temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_PARTICLES, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
mds->cache_frame_pause_particles = 0;
}
if (cache_map & FLUID_DOMAIN_OUTDATED_GUIDE) {
flags &= ~(FLUID_DOMAIN_BAKING_GUIDE | FLUID_DOMAIN_BAKED_GUIDE | FLUID_DOMAIN_OUTDATED_GUIDE);
BLI_path_join(temp_dir, sizeof(temp_dir), mds->cache_directory, FLUID_DOMAIN_DIR_GUIDE, NULL);
BLI_path_abs(temp_dir, relbase);
BLI_delete(temp_dir, true, true); /* BLI_exists(filepath) is implicit */
mds->cache_frame_pause_guide = 0;
}
mds->cache_flag = flags;
}
/* convert global position to domain cell space */
static void manta_pos_to_cell(FluidDomainSettings *mds, float pos[3])
{
mul_m4_v3(mds->imat, pos);
sub_v3_v3(pos, mds->p0);
pos[0] *= 1.0f / mds->cell_size[0];
pos[1] *= 1.0f / mds->cell_size[1];
pos[2] *= 1.0f / mds->cell_size[2];
}
/* Set domain transformations and base resolution from object mesh. */
static void manta_set_domain_from_mesh(FluidDomainSettings *mds,
Object *ob,
Mesh *me,
bool init_resolution)
{
size_t i;
float min[3] = {FLT_MAX, FLT_MAX, FLT_MAX}, max[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
float size[3];
MVert *verts = me->mvert;
float scale = 0.0;
int res;
res = mds->maxres;
/* Set minimum and maximum coordinates of BB. */
for (i = 0; i < me->totvert; i++) {
minmax_v3v3_v3(min, max, verts[i].co);
}
/* Set domain bounds. */
copy_v3_v3(mds->p0, min);
copy_v3_v3(mds->p1, max);
mds->dx = 1.0f / res;
/* Calculate domain dimensions. */
sub_v3_v3v3(size, max, min);
if (init_resolution) {
zero_v3_int(mds->base_res);
copy_v3_v3(mds->cell_size, size);
}
/* Apply object scale. */
for (i = 0; i < 3; i++) {
size[i] = fabsf(size[i] * ob->scale[i]);
}
copy_v3_v3(mds->global_size, size);
copy_v3_v3(mds->dp0, min);
invert_m4_m4(mds->imat, ob->obmat);
/* Prevent crash when initializing a plane as domain. */
if (!init_resolution || (size[0] < FLT_EPSILON) || (size[1] < FLT_EPSILON) ||
(size[2] < FLT_EPSILON)) {
return;
}
/* Define grid resolutions from longest domain side. */
if (size[0] >= MAX2(size[1], size[2])) {
scale = res / size[0];
mds->scale = size[0] / fabsf(ob->scale[0]);
mds->base_res[0] = res;
mds->base_res[1] = max_ii((int)(size[1] * scale + 0.5f), 4);
mds->base_res[2] = max_ii((int)(size[2] * scale + 0.5f), 4);
}
else if (size[1] >= MAX2(size[0], size[2])) {
scale = res / size[1];
mds->scale = size[1] / fabsf(ob->scale[1]);
mds->base_res[0] = max_ii((int)(size[0] * scale + 0.5f), 4);
mds->base_res[1] = res;
mds->base_res[2] = max_ii((int)(size[2] * scale + 0.5f), 4);
}
else {
scale = res / size[2];
mds->scale = size[2] / fabsf(ob->scale[2]);
mds->base_res[0] = max_ii((int)(size[0] * scale + 0.5f), 4);
mds->base_res[1] = max_ii((int)(size[1] * scale + 0.5f), 4);
mds->base_res[2] = res;
}
/* Set cell size. */
mds->cell_size[0] /= (float)mds->base_res[0];
mds->cell_size[1] /= (float)mds->base_res[1];
mds->cell_size[2] /= (float)mds->base_res[2];
}
static void manta_set_domain_gravity(Scene *scene, FluidDomainSettings *mds)
{
float gravity[3] = {0.0f, 0.0f, -1.0f};
float gravity_mag;
/* Use global gravity if enabled. */
if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) {
copy_v3_v3(gravity, scene->physics_settings.gravity);
/* Map default value to 1.0. */
mul_v3_fl(gravity, 1.0f / 9.810f);
/* Convert gravity to domain space. */
gravity_mag = len_v3(gravity);
mul_mat3_m4_v3(mds->imat, gravity);
normalize_v3(gravity);
mul_v3_fl(gravity, gravity_mag);
copy_v3_v3(mds->gravity, gravity);
}
mul_v3_fl(mds->gravity, mds->effector_weights->global_gravity);
}
static bool BKE_fluid_modifier_init(
FluidModifierData *mmd, Depsgraph *depsgraph, Object *ob, Scene *scene, Mesh *me)
{
int scene_framenr = (int)DEG_get_ctime(depsgraph);
if ((mmd->type & MOD_FLUID_TYPE_DOMAIN) && mmd->domain && !mmd->domain->fluid) {
FluidDomainSettings *mds = mmd->domain;
int res[3];
/* Set domain dimensions from mesh. */
manta_set_domain_from_mesh(mds, ob, me, true);
/* Set domain gravity. */
manta_set_domain_gravity(scene, mds);
/* Reset domain values. */
zero_v3_int(mds->shift);
zero_v3(mds->shift_f);
add_v3_fl(mds->shift_f, 0.5f);
zero_v3(mds->prev_loc);
mul_m4_v3(ob->obmat, mds->prev_loc);
copy_m4_m4(mds->obmat, ob->obmat);
/* Set resolutions. */
if (mmd->domain->type == FLUID_DOMAIN_TYPE_GAS &&
mmd->domain->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
res[0] = res[1] = res[2] = 1; /* Use minimum res for adaptive init. */
}
else {
copy_v3_v3_int(res, mds->base_res);
}
copy_v3_v3_int(mds->res, res);
mds->total_cells = mds->res[0] * mds->res[1] * mds->res[2];
mds->res_min[0] = mds->res_min[1] = mds->res_min[2] = 0;
copy_v3_v3_int(mds->res_max, res);
/* Set time, frame length = 0.1 is at 25fps. */
float fps = scene->r.frs_sec / scene->r.frs_sec_base;
mds->frame_length = DT_DEFAULT * (25.0f / fps) * mds->time_scale;
/* Initially dt is equal to frame length (dt can change with adaptive-time stepping though). */
mds->dt = mds->frame_length;
mds->time_per_frame = 0;
mds->time_total = abs(scene_framenr - mds->cache_frame_start) * mds->frame_length;
mmd->time = scene_framenr;
/* Allocate fluid. */
return BKE_fluid_reallocate_fluid(mds, mds->res, 0);
}
else if (mmd->type & MOD_FLUID_TYPE_FLOW) {
if (!mmd->flow) {
BKE_fluid_modifier_create_type_data(mmd);
}
mmd->time = scene_framenr;
return true;
}
else if (mmd->type & MOD_FLUID_TYPE_EFFEC) {
if (!mmd->effector) {
BKE_fluid_modifier_create_type_data(mmd);
}
mmd->time = scene_framenr;
return true;
}
return false;
}
// forward declaration
static void manta_smoke_calc_transparency(FluidDomainSettings *mds, ViewLayer *view_layer);
static float calc_voxel_transp(
float *result, float *input, int res[3], int *pixel, float *t_ray, float correct);
static void update_distances(int index,
float *mesh_distances,
BVHTreeFromMesh *tree_data,
const float ray_start[3],
float surface_thickness,
bool use_plane_init);
static int get_light(ViewLayer *view_layer, float *light)
{
Base *base_tmp = NULL;
int found_light = 0;
/* Try to find a lamp, preferably local. */
for (base_tmp = FIRSTBASE(view_layer); base_tmp; base_tmp = base_tmp->next) {
if (base_tmp->object->type == OB_LAMP) {
Light *la = base_tmp->object->data;
if (la->type == LA_LOCAL) {
copy_v3_v3(light, base_tmp->object->obmat[3]);
return 1;
}
else if (!found_light) {
copy_v3_v3(light, base_tmp->object->obmat[3]);
found_light = 1;
}
}
}
return found_light;
}
static void clamp_bounds_in_domain(FluidDomainSettings *mds,
int min[3],
int max[3],
float *min_vel,
float *max_vel,
int margin,
float dt)
{
int i;
for (i = 0; i < 3; i++) {
int adapt = (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) ? mds->adapt_res : 0;
/* Add some margin. */
min[i] -= margin;
max[i] += margin;
/* Adapt to velocity. */
if (min_vel && min_vel[i] < 0.0f) {
min[i] += (int)floor(min_vel[i] * dt);
}
if (max_vel && max_vel[i] > 0.0f) {
max[i] += (int)ceil(max_vel[i] * dt);
}
/* Clamp within domain max size. */
CLAMP(min[i], -adapt, mds->base_res[i] + adapt);
CLAMP(max[i], -adapt, mds->base_res[i] + adapt);
}
}
static bool is_static_object(Object *ob)
{
/* Check if the object has modifiers that might make the object "dynamic". */
ModifierData *md = ob->modifiers.first;
for (; md; md = md->next) {
if (ELEM(md->type,
eModifierType_Cloth,
eModifierType_DynamicPaint,
eModifierType_Explode,
eModifierType_Ocean,
eModifierType_ShapeKey,
eModifierType_Softbody)) {
return false;
}
}
/* Active rigid body objects considered to be dynamic fluid objects. */
if (ob->rigidbody_object && ob->rigidbody_object->type == RBO_TYPE_ACTIVE) {
return false;
}
/* Finally, check if the object has animation data. If so, it is considered dynamic. */
return !BKE_object_moves_in_time(ob, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bounding Box
* \{ */
typedef struct FluidObjectBB {
float *influence;
float *velocity;
float *distances;
float *numobjs;
int min[3], max[3], res[3];
int hmin[3], hmax[3], hres[3];
int total_cells, valid;
} FluidObjectBB;
static void bb_boundInsert(FluidObjectBB *bb, float point[3])
{
int i = 0;
if (!bb->valid) {
for (; i < 3; i++) {
bb->min[i] = (int)floor(point[i]);
bb->max[i] = (int)ceil(point[i]);
}
bb->valid = 1;
}
else {
for (; i < 3; i++) {
if (point[i] < bb->min[i]) {
bb->min[i] = (int)floor(point[i]);
}
if (point[i] > bb->max[i]) {
bb->max[i] = (int)ceil(point[i]);
}
}
}
}
static void bb_allocateData(FluidObjectBB *bb, bool use_velocity, bool use_influence)
{
int i, res[3];
for (i = 0; i < 3; i++) {
res[i] = bb->max[i] - bb->min[i];
if (res[i] <= 0) {
return;
}
}
bb->total_cells = res[0] * res[1] * res[2];
copy_v3_v3_int(bb->res, res);
bb->numobjs = MEM_calloc_arrayN(bb->total_cells, sizeof(float), "fluid_bb_numobjs");
if (use_influence) {
bb->influence = MEM_calloc_arrayN(bb->total_cells, sizeof(float), "fluid_bb_influence");
}
if (use_velocity) {
bb->velocity = MEM_calloc_arrayN(bb->total_cells * 3, sizeof(float), "fluid_bb_velocity");
}
bb->distances = MEM_malloc_arrayN(bb->total_cells, sizeof(float), "fluid_bb_distances");
copy_vn_fl(bb->distances, bb->total_cells, FLT_MAX);
bb->valid = true;
}
static void bb_freeData(FluidObjectBB *bb)
{
if (bb->numobjs) {
MEM_freeN(bb->numobjs);
}
if (bb->influence) {
MEM_freeN(bb->influence);
}
if (bb->velocity) {
MEM_freeN(bb->velocity);
}
if (bb->distances) {
MEM_freeN(bb->distances);
}
}
static void bb_combineMaps(FluidObjectBB *output,
FluidObjectBB *bb2,
int additive,
float sample_size)
{
int i, x, y, z;
/* Copyfill input 1 struct and clear output for new allocation. */
FluidObjectBB bb1;
memcpy(&bb1, output, sizeof(FluidObjectBB));
memset(output, 0, sizeof(FluidObjectBB));
for (i = 0; i < 3; i++) {
if (bb1.valid) {
output->min[i] = MIN2(bb1.min[i], bb2->min[i]);
output->max[i] = MAX2(bb1.max[i], bb2->max[i]);
}
else {
output->min[i] = bb2->min[i];
output->max[i] = bb2->max[i];
}
}
/* Allocate output map. */
bb_allocateData(output, (bb1.velocity || bb2->velocity), (bb1.influence || bb2->influence));
/* Low through bounding box */
for (x = output->min[0]; x < output->max[0]; x++) {
for (y = output->min[1]; y < output->max[1]; y++) {
for (z = output->min[2]; z < output->max[2]; z++) {
int index_out = manta_get_index(x - output->min[0],
output->res[0],
y - output->min[1],
output->res[1],
z - output->min[2]);
/* Initialize with first input if in range. */
if (x >= bb1.min[0] && x < bb1.max[0] && y >= bb1.min[1] && y < bb1.max[1] &&
z >= bb1.min[2] && z < bb1.max[2]) {
int index_in = manta_get_index(
x - bb1.min[0], bb1.res[0], y - bb1.min[1], bb1.res[1], z - bb1.min[2]);
/* Values. */
output->numobjs[index_out] = bb1.numobjs[index_in];
if (output->influence && bb1.influence) {
output->influence[index_out] = bb1.influence[index_in];
}
output->distances[index_out] = bb1.distances[index_in];
if (output->velocity && bb1.velocity) {
copy_v3_v3(&output->velocity[index_out * 3], &bb1.velocity[index_in * 3]);
}
}
/* Apply second input if in range. */
if (x >= bb2->min[0] && x < bb2->max[0] && y >= bb2->min[1] && y < bb2->max[1] &&
z >= bb2->min[2] && z < bb2->max[2]) {
int index_in = manta_get_index(
x - bb2->min[0], bb2->res[0], y - bb2->min[1], bb2->res[1], z - bb2->min[2]);
/* Values. */
output->numobjs[index_out] = MAX2(bb2->numobjs[index_in], output->numobjs[index_out]);
if (output->influence && bb2->influence) {
if (additive) {
output->influence[index_out] += bb2->influence[index_in] * sample_size;
}
else {
output->influence[index_out] = MAX2(bb2->influence[index_in],
output->influence[index_out]);
}
}
output->distances[index_out] = MIN2(bb2->distances[index_in],
output->distances[index_out]);
if (output->velocity && bb2->velocity) {
/* Last sample replaces the velocity. */
output->velocity[index_out * 3] = ADD_IF_LOWER(output->velocity[index_out * 3],
bb2->velocity[index_in * 3]);
output->velocity[index_out * 3 + 1] = ADD_IF_LOWER(output->velocity[index_out * 3 + 1],
bb2->velocity[index_in * 3 + 1]);
output->velocity[index_out * 3 + 2] = ADD_IF_LOWER(output->velocity[index_out * 3 + 2],
bb2->velocity[index_in * 3 + 2]);
}
}
} /* Low res loop. */
}
}
/* Free original data. */
bb_freeData(&bb1);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Effectors
* \{ */
BLI_INLINE void apply_effector_fields(FluidEffectorSettings *UNUSED(mes),
int index,
float distance_value,
float *phi_in,
float numobjs_value,
float *numobjs,
float vel_x_value,
float *vel_x,
float vel_y_value,
float *vel_y,
float vel_z_value,
float *vel_z)
{
/* Ensure that distance value is "joined" into the levelset. */
if (phi_in) {
phi_in[index] = MIN2(distance_value, phi_in[index]);
}
/* Accumulate effector object count (important once effector object overlap). */
if (numobjs && numobjs_value > 0) {
numobjs[index] += 1;
}
if (vel_x) {
vel_x[index] = vel_x_value;
vel_y[index] = vel_y_value;
vel_z[index] = vel_z_value;
}
}
static void sample_effector(FluidEffectorSettings *mes,
const MVert *mvert,
const MLoop *mloop,
const MLoopTri *mlooptri,
float *velocity_map,
int index,
BVHTreeFromMesh *tree_data,
const float ray_start[3],
const float *vert_vel,
bool has_velocity)
{
BVHTreeNearest nearest = {0};
nearest.index = -1;
/* Distance between two opposing vertices in a unit cube.
* I.e. the unit cube diagonal or sqrt(3).
* This value is our nearest neighbor search distance. */
const float surface_distance = 1.732;
nearest.dist_sq = surface_distance * surface_distance; /* find_nearest uses squared distance */
/* Find the nearest point on the mesh. */
if (BLI_bvhtree_find_nearest(
tree_data->tree, ray_start, &nearest, tree_data->nearest_callback, tree_data) != -1) {
float weights[3];
int v1, v2, v3, f_index = nearest.index;
/* Calculate barycentric weights for nearest point. */
v1 = mloop[mlooptri[f_index].tri[0]].v;
v2 = mloop[mlooptri[f_index].tri[1]].v;
v3 = mloop[mlooptri[f_index].tri[2]].v;
interp_weights_tri_v3(weights, mvert[v1].co, mvert[v2].co, mvert[v3].co, nearest.co);
if (has_velocity) {
/* Apply object velocity. */
float hit_vel[3];
interp_v3_v3v3v3(hit_vel, &vert_vel[v1 * 3], &vert_vel[v2 * 3], &vert_vel[v3 * 3], weights);
/* Guiding has additional velocity multiplier */
if (mes->type == FLUID_EFFECTOR_TYPE_GUIDE) {
mul_v3_fl(hit_vel, mes->vel_multi);
switch (mes->guide_mode) {
case FLUID_EFFECTOR_GUIDE_AVERAGED:
velocity_map[index * 3] = (velocity_map[index * 3] + hit_vel[0]) * 0.5f;
velocity_map[index * 3 + 1] = (velocity_map[index * 3 + 1] + hit_vel[1]) * 0.5f;
velocity_map[index * 3 + 2] = (velocity_map[index * 3 + 2] + hit_vel[2]) * 0.5f;
break;
case FLUID_EFFECTOR_GUIDE_OVERRIDE:
velocity_map[index * 3] = hit_vel[0];
velocity_map[index * 3 + 1] = hit_vel[1];
velocity_map[index * 3 + 2] = hit_vel[2];
break;
case FLUID_EFFECTOR_GUIDE_MIN:
velocity_map[index * 3] = MIN2(fabsf(hit_vel[0]), fabsf(velocity_map[index * 3]));
velocity_map[index * 3 + 1] = MIN2(fabsf(hit_vel[1]),
fabsf(velocity_map[index * 3 + 1]));
velocity_map[index * 3 + 2] = MIN2(fabsf(hit_vel[2]),
fabsf(velocity_map[index * 3 + 2]));
break;
case FLUID_EFFECTOR_GUIDE_MAX:
default:
velocity_map[index * 3] = MAX2(fabsf(hit_vel[0]), fabsf(velocity_map[index * 3]));
velocity_map[index * 3 + 1] = MAX2(fabsf(hit_vel[1]),
fabsf(velocity_map[index * 3 + 1]));
velocity_map[index * 3 + 2] = MAX2(fabsf(hit_vel[2]),
fabsf(velocity_map[index * 3 + 2]));
break;
}
}
else {
/* Apply (i.e. add) effector object velocity */
velocity_map[index * 3] += hit_vel[0];
velocity_map[index * 3 + 1] += hit_vel[1];
velocity_map[index * 3 + 2] += hit_vel[2];
# ifdef DEBUG_PRINT
/* Debugging: Print object velocities. */
printf("adding effector object vel: [%f, %f, %f], dx is: %f\n",
hit_vel[0],
hit_vel[1],
hit_vel[2],
mds->dx);
# endif
}
}
}
}
typedef struct ObstaclesFromDMData {
FluidEffectorSettings *mes;
const MVert *mvert;
const MLoop *mloop;
const MLoopTri *mlooptri;
BVHTreeFromMesh *tree;
FluidObjectBB *bb;
bool has_velocity;
float *vert_vel;
int *min, *max, *res;
} ObstaclesFromDMData;
static void obstacles_from_mesh_task_cb(void *__restrict userdata,
const int z,
const TaskParallelTLS *__restrict UNUSED(tls))
{
ObstaclesFromDMData *data = userdata;
FluidObjectBB *bb = data->bb;
for (int x = data->min[0]; x < data->max[0]; x++) {
for (int y = data->min[1]; y < data->max[1]; y++) {
const int index = manta_get_index(
x - bb->min[0], bb->res[0], y - bb->min[1], bb->res[1], z - bb->min[2]);
float ray_start[3] = {(float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f};
/* Calculate object velocities. Result in bb->velocity. */
sample_effector(data->mes,
data->mvert,
data->mloop,
data->mlooptri,
bb->velocity,
index,
data->tree,
ray_start,
data->vert_vel,
data->has_velocity);
/* Calculate levelset values from meshes. Result in bb->distances. */
update_distances(index,
bb->distances,
data->tree,
ray_start,
data->mes->surface_distance,
data->mes->flags & FLUID_EFFECTOR_USE_PLANE_INIT);
/* Ensure that num objects are also counted inside object.
* But don't count twice (see object inc for nearest point). */
if (bb->distances[index] < 0) {
bb->numobjs[index]++;
}
}
}
}
static void obstacles_from_mesh(Object *coll_ob,
FluidDomainSettings *mds,
FluidEffectorSettings *mes,
FluidObjectBB *bb,
float dt)
{
if (mes->mesh) {
Mesh *me = NULL;
MVert *mvert = NULL;
const MLoopTri *looptri;
const MLoop *mloop;
BVHTreeFromMesh tree_data = {NULL};
int numverts, i;
float *vert_vel = NULL;
bool has_velocity = false;
me = BKE_mesh_copy_for_eval(mes->mesh, true);
int min[3], max[3], res[3];
/* Duplicate vertices to modify. */
if (me->mvert) {
me->mvert = MEM_dupallocN(me->mvert);
CustomData_set_layer(&me->vdata, CD_MVERT, me->mvert);
}
BKE_mesh_ensure_normals(me);
mvert = me->mvert;
mloop = me->mloop;
looptri = BKE_mesh_runtime_looptri_ensure(me);
numverts = me->totvert;
/* TODO (sebbas): Make initialization of vertex velocities optional? */
{
vert_vel = MEM_callocN(sizeof(float) * numverts * 3, "manta_obs_velocity");
if (mes->numverts != numverts || !mes->verts_old) {
if (mes->verts_old) {
MEM_freeN(mes->verts_old);
}
mes->verts_old = MEM_callocN(sizeof(float) * numverts * 3, "manta_obs_verts_old");
mes->numverts = numverts;
}
else {
has_velocity = true;
}
}
/* Transform mesh vertices to domain grid space for fast lookups */
for (i = 0; i < numverts; i++) {
float n[3];
float co[3];
/* Vertex position. */
mul_m4_v3(coll_ob->obmat, mvert[i].co);
manta_pos_to_cell(mds, mvert[i].co);
/* Vertex normal. */
normal_short_to_float_v3(n, mvert[i].no);
mul_mat3_m4_v3(coll_ob->obmat, n);
mul_mat3_m4_v3(mds->imat, n);
normalize_v3(n);
normal_float_to_short_v3(mvert[i].no, n);
/* Vertex velocity. */
add_v3fl_v3fl_v3i(co, mvert[i].co, mds->shift);
if (has_velocity) {
sub_v3_v3v3(&vert_vel[i * 3], co, &mes->verts_old[i * 3]);
mul_v3_fl(&vert_vel[i * 3], mds->dx / dt);
}
copy_v3_v3(&mes->verts_old[i * 3], co);
/* Calculate emission map bounds. */
bb_boundInsert(bb, mvert[i].co);
}
/* Set emission map.
* Use 3 cell diagonals as margin (3 * 1.732 = 5.196). */
int bounds_margin = (int)ceil(5.196);
clamp_bounds_in_domain(mds, bb->min, bb->max, NULL, NULL, bounds_margin, dt);
bb_allocateData(bb, true, false);
/* Setup loop bounds. */
for (i = 0; i < 3; i++) {
min[i] = bb->min[i];
max[i] = bb->max[i];
res[i] = bb->res[i];
}
if (BKE_bvhtree_from_mesh_get(&tree_data, me, BVHTREE_FROM_LOOPTRI, 4)) {
ObstaclesFromDMData data = {
.mes = mes,
.mvert = mvert,
.mloop = mloop,
.mlooptri = looptri,
.tree = &tree_data,
.bb = bb,
.has_velocity = has_velocity,
.vert_vel = vert_vel,
.min = min,
.max = max,
.res = res,
};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 2;
BLI_task_parallel_range(min[2], max[2], &data, obstacles_from_mesh_task_cb, &settings);
}
/* Free bvh tree. */
free_bvhtree_from_mesh(&tree_data);
if (vert_vel) {
MEM_freeN(vert_vel);
}
if (me->mvert) {
MEM_freeN(me->mvert);
}
BKE_id_free(NULL, me);
}
}
static void update_obstacleflags(FluidDomainSettings *mds,
Object **coll_ob_array,
int coll_ob_array_len)
{
int active_fields = mds->active_fields;
uint coll_index;
/* First, remove all flags that we want to update. */
int prev_flags = (FLUID_DOMAIN_ACTIVE_OBSTACLE | FLUID_DOMAIN_ACTIVE_GUIDE);
active_fields &= ~prev_flags;
/* Monitor active fields based on flow settings */
for (coll_index = 0; coll_index < coll_ob_array_len; coll_index++) {
Object *coll_ob = coll_ob_array[coll_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(coll_ob,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
if ((mmd2->type & MOD_FLUID_TYPE_EFFEC) && mmd2->effector) {
FluidEffectorSettings *mes = mmd2->effector;
if (!mes) {
break;
}
if (mes->type == FLUID_EFFECTOR_TYPE_COLLISION) {
active_fields |= FLUID_DOMAIN_ACTIVE_OBSTACLE;
}
if (mes->type == FLUID_EFFECTOR_TYPE_GUIDE) {
active_fields |= FLUID_DOMAIN_ACTIVE_GUIDE;
}
}
}
/* Finally, initialize new data fields if any */
if (active_fields & FLUID_DOMAIN_ACTIVE_OBSTACLE) {
manta_ensure_obstacle(mds->fluid, mds->mmd);
}
if (active_fields & FLUID_DOMAIN_ACTIVE_GUIDE) {
manta_ensure_guiding(mds->fluid, mds->mmd);
}
mds->active_fields = active_fields;
}
static void update_obstacles(Depsgraph *depsgraph,
Scene *scene,
Object *ob,
FluidDomainSettings *mds,
float time_per_frame,
float frame_length,
int frame,
float dt)
{
FluidObjectBB *bb_maps = NULL;
Object **effecobjs = NULL;
uint numeffecobjs = 0, effec_index = 0;
bool is_first_frame = (frame == mds->cache_frame_start);
effecobjs = BKE_collision_objects_create(
depsgraph, ob, mds->effector_group, &numeffecobjs, eModifierType_Fluid);
/* Update all effector related flags and ensure that corresponding grids get initialized. */
update_obstacleflags(mds, effecobjs, numeffecobjs);
/* Initialize effector maps for each flow. */
bb_maps = MEM_callocN(sizeof(struct FluidObjectBB) * numeffecobjs, "fluid_effector_bb_maps");
/* Prepare effector maps. */
for (effec_index = 0; effec_index < numeffecobjs; effec_index++) {
Object *effecobj = effecobjs[effec_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(effecobj,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
/* Check for initialized effector object. */
if ((mmd2->type & MOD_FLUID_TYPE_EFFEC) && mmd2->effector) {
FluidEffectorSettings *mes = mmd2->effector;
int subframes = mes->subframes;
FluidObjectBB *bb = &bb_maps[effec_index];
bool is_static = is_static_object(effecobj);
/* Cannot use static mode with adaptive domain.
* The adaptive domain might expand and only later in the simulations discover the static
* object. */
if (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
is_static = false;
}
/* Optimization: Static objects don't need emission computation after first frame. */
if (is_static && !is_first_frame) {
continue;
}
/* Optimization: Skip effector objects with disabled effec flag. */
if ((mes->flags & FLUID_EFFECTOR_USE_EFFEC) == 0) {
continue;
}
/* Length of one adaptive frame. If using adaptive stepping, length is smaller than actual
* frame length */
float adaptframe_length = time_per_frame / frame_length;
/* Adaptive frame length as percentage */
CLAMP(adaptframe_length, 0.0f, 1.0f);
/* More splitting because of emission subframe: If no subframes present, sample_size is 1. */
float sample_size = 1.0f / (float)(subframes + 1);
/* First frame cannot have any subframes because there is (obviously) no previous frame from
* where subframes could come from. */
if (is_first_frame) {
subframes = 0;
}
int subframe;
float subframe_dt = dt * sample_size;
/* Emission loop. When not using subframes this will loop only once. */
for (subframe = subframes; subframe >= 0; subframe--) {
/* Temporary emission map used when subframes are enabled, i.e. at least one subframe. */
FluidObjectBB bb_temp = {NULL};
/* Set scene time */
/* Handle emission subframe */
if (subframe > 0 && !is_first_frame) {
scene->r.subframe = adaptframe_length -
sample_size * (float)(subframe) * (dt / frame_length);
scene->r.cfra = frame - 1;
}
/* Last frame in this loop (subframe == suframes). Can be real end frame or in between
* frames (adaptive frame). */
else {
/* Handle adaptive subframe (ie has subframe fraction). Need to set according scene
* subframe parameter. */
if (time_per_frame < frame_length) {
scene->r.subframe = adaptframe_length;
scene->r.cfra = frame - 1;
}
/* Handle absolute endframe (ie no subframe fraction). Need to set the scene subframe
* parameter to 0 and advance current scene frame. */
else {
scene->r.subframe = 0.0f;
scene->r.cfra = frame;
}
}
/* Sanity check: subframe portion must be between 0 and 1. */
CLAMP(scene->r.subframe, 0.0f, 1.0f);
# ifdef DEBUG_PRINT
/* Debugging: Print subframe information. */
printf(
"effector: frame (is first: %d): %d // scene current frame: %d // scene current "
"subframe: "
"%f\n",
is_first_frame,
frame,
scene->r.cfra,
scene->r.subframe);
# endif
/* Update frame time, this is considering current subframe fraction
* BLI_mutex_lock() called in manta_step(), so safe to update subframe here
* TODO (sebbas): Using BKE_scene_frame_get(scene) instead of new DEG_get_ctime(depsgraph)
* as subframes don't work with the latter yet. */
BKE_object_modifier_update_subframe(
depsgraph, scene, effecobj, true, 5, BKE_scene_frame_get(scene), eModifierType_Fluid);
if (subframes) {
obstacles_from_mesh(effecobj, mds, mes, &bb_temp, subframe_dt);
}
else {
obstacles_from_mesh(effecobj, mds, mes, bb, subframe_dt);
}
/* If this we emitted with temp emission map in this loop (subframe emission), we combine
* the temp map with the original emission map. */
if (subframes) {
/* Combine emission maps. */
bb_combineMaps(bb, &bb_temp, 0, 0.0f);
bb_freeData(&bb_temp);
}
}
}
}
float *vel_x = manta_get_ob_velocity_x(mds->fluid);
float *vel_y = manta_get_ob_velocity_y(mds->fluid);
float *vel_z = manta_get_ob_velocity_z(mds->fluid);
float *vel_x_guide = manta_get_guide_velocity_x(mds->fluid);
float *vel_y_guide = manta_get_guide_velocity_y(mds->fluid);
float *vel_z_guide = manta_get_guide_velocity_z(mds->fluid);
float *phi_obs_in = manta_get_phiobs_in(mds->fluid);
float *phi_obsstatic_in = manta_get_phiobsstatic_in(mds->fluid);
float *phi_guide_in = manta_get_phiguide_in(mds->fluid);
float *num_obstacles = manta_get_num_obstacle(mds->fluid);
float *num_guides = manta_get_num_guide(mds->fluid);
uint z;
bool use_adaptivedomain = (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN);
/* Grid reset before writing again. */
for (z = 0; z < mds->res[0] * mds->res[1] * mds->res[2]; z++) {
/* Use big value that's not inf to initialize levelset grids. */
if (phi_obs_in) {
phi_obs_in[z] = PHI_MAX;
}
/* Only reset static effectors on first frame. Only use static effectors without adaptive
* domains. */
if (phi_obsstatic_in && (is_first_frame || use_adaptivedomain)) {
phi_obsstatic_in[z] = PHI_MAX;
}
if (phi_guide_in) {
phi_guide_in[z] = PHI_MAX;
}
if (num_obstacles) {
num_obstacles[z] = 0;
}
if (num_guides) {
num_guides[z] = 0;
}
if (vel_x && vel_y && vel_z) {
vel_x[z] = 0.0f;
vel_y[z] = 0.0f;
vel_z[z] = 0.0f;
}
if (vel_x_guide && vel_y_guide && vel_z_guide) {
vel_x_guide[z] = 0.0f;
vel_y_guide[z] = 0.0f;
vel_z_guide[z] = 0.0f;
}
}
/* Prepare grids from effector objects. */
for (effec_index = 0; effec_index < numeffecobjs; effec_index++) {
Object *effecobj = effecobjs[effec_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(effecobj,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
bool is_static = is_static_object(effecobj);
/* Cannot use static mode with adaptive domain.
* The adaptive domain might expand and only later in the simulations discover the static
* object. */
if (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
is_static = false;
}
/* Optimization: Static objects don't need emission application after first frame. */
if (is_static && !is_first_frame) {
continue;
}
/* Check for initialized effector object. */
if ((mmd2->type & MOD_FLUID_TYPE_EFFEC) && mmd2->effector) {
FluidEffectorSettings *mes = mmd2->effector;
/* Optimization: Skip effector objects with disabled effec flag. */
if ((mes->flags & FLUID_EFFECTOR_USE_EFFEC) == 0) {
continue;
}
FluidObjectBB *bb = &bb_maps[effec_index];
float *velocity_map = bb->velocity;
float *numobjs_map = bb->numobjs;
float *distance_map = bb->distances;
int gx, gy, gz, ex, ey, ez, dx, dy, dz;
size_t e_index, d_index;
/* Loop through every emission map cell. */
for (gx = bb->min[0]; gx < bb->max[0]; gx++) {
for (gy = bb->min[1]; gy < bb->max[1]; gy++) {
for (gz = bb->min[2]; gz < bb->max[2]; gz++) {
/* Compute emission map index. */
ex = gx - bb->min[0];
ey = gy - bb->min[1];
ez = gz - bb->min[2];
e_index = manta_get_index(ex, bb->res[0], ey, bb->res[1], ez);
/* Get domain index. */
dx = gx - mds->res_min[0];
dy = gy - mds->res_min[1];
dz = gz - mds->res_min[2];
d_index = manta_get_index(dx, mds->res[0], dy, mds->res[1], dz);
/* Make sure emission cell is inside the new domain boundary. */
if (dx < 0 || dy < 0 || dz < 0 || dx >= mds->res[0] || dy >= mds->res[1] ||
dz >= mds->res[2]) {
continue;
}
/* Apply static effectors to obstacle grid. */
if (is_static && is_first_frame) {
if (mes->type == FLUID_EFFECTOR_TYPE_COLLISION) {
apply_effector_fields(mes,
d_index,
distance_map[e_index],
phi_obsstatic_in,
numobjs_map[e_index],
num_obstacles,
0.0f,
NULL,
0.0f,
NULL,
0.0f,
NULL);
}
}
/* Apply moving effectors to obstacle grid. */
else if (!is_static) {
if (mes->type == FLUID_EFFECTOR_TYPE_COLLISION) {
apply_effector_fields(mes,
d_index,
distance_map[e_index],
phi_obs_in,
numobjs_map[e_index],
num_obstacles,
velocity_map[e_index * 3],
vel_x,
velocity_map[e_index * 3 + 1],
vel_y,
velocity_map[e_index * 3 + 2],
vel_z);
}
if (mes->type == FLUID_EFFECTOR_TYPE_GUIDE) {
apply_effector_fields(mes,
d_index,
distance_map[e_index],
phi_guide_in,
numobjs_map[e_index],
num_guides,
velocity_map[e_index * 3],
vel_x_guide,
velocity_map[e_index * 3 + 1],
vel_y_guide,
velocity_map[e_index * 3 + 2],
vel_z_guide);
}
}
}
}
} /* End of effector map loop. */
bb_freeData(bb);
} /* End of effector object loop. */
}
BKE_collision_objects_free(effecobjs);
if (bb_maps) {
MEM_freeN(bb_maps);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Flow
* \{ */
typedef struct EmitFromParticlesData {
FluidFlowSettings *mfs;
KDTree_3d *tree;
FluidObjectBB *bb;
float *particle_vel;
int *min, *max, *res;
float solid;
float smooth;
} EmitFromParticlesData;
static void emit_from_particles_task_cb(void *__restrict userdata,
const int z,
const TaskParallelTLS *__restrict UNUSED(tls))
{
EmitFromParticlesData *data = userdata;
FluidFlowSettings *mfs = data->mfs;
FluidObjectBB *bb = data->bb;
for (int x = data->min[0]; x < data->max[0]; x++) {
for (int y = data->min[1]; y < data->max[1]; y++) {
const int index = manta_get_index(
x - bb->min[0], bb->res[0], y - bb->min[1], bb->res[1], z - bb->min[2]);
const float ray_start[3] = {((float)x) + 0.5f, ((float)y) + 0.5f, ((float)z) + 0.5f};
/* Find particle distance from the kdtree. */
KDTreeNearest_3d nearest;
const float range = data->solid + data->smooth;
BLI_kdtree_3d_find_nearest(data->tree, ray_start, &nearest);
if (nearest.dist < range) {
bb->influence[index] = (nearest.dist < data->solid) ?
1.0f :
(1.0f - (nearest.dist - data->solid) / data->smooth);
/* Uses particle velocity as initial velocity for smoke. */
if (mfs->flags & FLUID_FLOW_INITVELOCITY && (mfs->psys->part->phystype != PART_PHYS_NO)) {
madd_v3_v3fl(
&bb->velocity[index * 3], &data->particle_vel[nearest.index * 3], mfs->vel_multi);
}
}
}
}
}
static void emit_from_particles(Object *flow_ob,
FluidDomainSettings *mds,
FluidFlowSettings *mfs,
FluidObjectBB *bb,
Depsgraph *depsgraph,
Scene *scene,
float dt)
{
if (mfs && mfs->psys && mfs->psys->part &&
ELEM(mfs->psys->part->type, PART_EMITTER, PART_FLUID)) // is particle system selected
{
ParticleSimulationData sim;
ParticleSystem *psys = mfs->psys;
float *particle_pos;
float *particle_vel;
int totpart = psys->totpart, totchild;
int p = 0;
int valid_particles = 0;
int bounds_margin = 1;
/* radius based flow */
const float solid = mfs->particle_size * 0.5f;
const float smooth = 0.5f; /* add 0.5 cells of linear falloff to reduce aliasing */
KDTree_3d *tree = NULL;
sim.depsgraph = depsgraph;
sim.scene = scene;
sim.ob = flow_ob;
sim.psys = psys;
sim.psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
/* prepare curvemapping tables */
if ((psys->part->child_flag & PART_CHILD_USE_CLUMP_CURVE) && psys->part->clumpcurve) {
BKE_curvemapping_changed_all(psys->part->clumpcurve);
}
if ((psys->part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && psys->part->roughcurve) {
BKE_curvemapping_changed_all(psys->part->roughcurve);
}
if ((psys->part->child_flag & PART_CHILD_USE_TWIST_CURVE) && psys->part->twistcurve) {
BKE_curvemapping_changed_all(psys->part->twistcurve);
}
/* initialize particle cache */
if (psys->part->type == PART_HAIR) {
// TODO: PART_HAIR not supported whatsoever
totchild = 0;
}
else {
totchild = psys->totchild * psys->part->disp / 100;
}
particle_pos = MEM_callocN(sizeof(float) * (totpart + totchild) * 3,
"manta_flow_particles_pos");
particle_vel = MEM_callocN(sizeof(float) * (totpart + totchild) * 3,
"manta_flow_particles_vel");
/* setup particle radius emission if enabled */
if (mfs->flags & FLUID_FLOW_USE_PART_SIZE) {
tree = BLI_kdtree_3d_new(psys->totpart + psys->totchild);
bounds_margin = (int)ceil(solid + smooth);
}
/* calculate local position for each particle */
for (p = 0; p < totpart + totchild; p++) {
ParticleKey state;
float *pos, *vel;
if (p < totpart) {
if (psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
continue;
}
}
else {
/* handle child particle */
ChildParticle *cpa = &psys->child[p - totpart];
if (psys->particles[cpa->parent].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
continue;
}
}
state.time = BKE_scene_frame_get(
scene); /* DEG_get_ctime(depsgraph) does not give subframe time */
if (psys_get_particle_state(&sim, p, &state, 0) == 0) {
continue;
}
/* location */
pos = &particle_pos[valid_particles * 3];
copy_v3_v3(pos, state.co);
manta_pos_to_cell(mds, pos);
/* velocity */
vel = &particle_vel[valid_particles * 3];
copy_v3_v3(vel, state.vel);
mul_mat3_m4_v3(mds->imat, &particle_vel[valid_particles * 3]);
if (mfs->flags & FLUID_FLOW_USE_PART_SIZE) {
BLI_kdtree_3d_insert(tree, valid_particles, pos);
}
/* calculate emission map bounds */
bb_boundInsert(bb, pos);
valid_particles++;
}
/* set emission map */
clamp_bounds_in_domain(mds, bb->min, bb->max, NULL, NULL, bounds_margin, dt);
bb_allocateData(bb, mfs->flags & FLUID_FLOW_INITVELOCITY, true);
if (!(mfs->flags & FLUID_FLOW_USE_PART_SIZE)) {
for (p = 0; p < valid_particles; p++) {
int cell[3];
size_t i = 0;
size_t index = 0;
int badcell = 0;
/* 1. get corresponding cell */
cell[0] = floor(particle_pos[p * 3]) - bb->min[0];
cell[1] = floor(particle_pos[p * 3 + 1]) - bb->min[1];
cell[2] = floor(particle_pos[p * 3 + 2]) - bb->min[2];
/* check if cell is valid (in the domain boundary) */
for (i = 0; i < 3; i++) {
if ((cell[i] > bb->res[i] - 1) || (cell[i] < 0)) {
badcell = 1;
break;
}
}
if (badcell) {
continue;
}
/* get cell index */
index = manta_get_index(cell[0], bb->res[0], cell[1], bb->res[1], cell[2]);
/* Add influence to emission map */
bb->influence[index] = 1.0f;
/* Uses particle velocity as initial velocity for smoke */
if (mfs->flags & FLUID_FLOW_INITVELOCITY && (psys->part->phystype != PART_PHYS_NO)) {
madd_v3_v3fl(&bb->velocity[index * 3], &particle_vel[p * 3], mfs->vel_multi);
}
} // particles loop
}
else if (valid_particles > 0) { // FLUID_FLOW_USE_PART_SIZE
int min[3], max[3], res[3];
/* setup loop bounds */
for (int i = 0; i < 3; i++) {
min[i] = bb->min[i];
max[i] = bb->max[i];
res[i] = bb->res[i];
}
BLI_kdtree_3d_balance(tree);
EmitFromParticlesData data = {
.mfs = mfs,
.tree = tree,
.bb = bb,
.particle_vel = particle_vel,
.min = min,
.max = max,
.res = res,
.solid = solid,
.smooth = smooth,
};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 2;
BLI_task_parallel_range(min[2], max[2], &data, emit_from_particles_task_cb, &settings);
}
if (mfs->flags & FLUID_FLOW_USE_PART_SIZE) {
BLI_kdtree_3d_free(tree);
}
/* free data */
if (particle_pos) {
MEM_freeN(particle_pos);
}
if (particle_vel) {
MEM_freeN(particle_vel);
}
}
}
/* Calculate map of (minimum) distances to flow/obstacle surface. Distances outside mesh are
* positive, inside negative. */
static void update_distances(int index,
float *distance_map,
BVHTreeFromMesh *tree_data,
const float ray_start[3],
float surface_thickness,
bool use_plane_init)
{
float min_dist = PHI_MAX;
/* Planar initialization: Find nearest cells around mesh. */
if (use_plane_init) {
BVHTreeNearest nearest = {0};
nearest.index = -1;
/* Distance between two opposing vertices in a unit cube.
* I.e. the unit cube diagonal or sqrt(3).
* This value is our nearest neighbor search distance. */
const float surface_distance = 1.732;
nearest.dist_sq = surface_distance *
surface_distance; /* find_nearest uses squared distance. */
/* Subtract optional surface thickness value and virtually increase the object size. */
if (surface_thickness) {
nearest.dist_sq += surface_thickness;
}
if (BLI_bvhtree_find_nearest(
tree_data->tree, ray_start, &nearest, tree_data->nearest_callback, tree_data) != -1) {
float ray[3] = {0};
sub_v3_v3v3(ray, ray_start, nearest.co);
min_dist = len_v3(ray);
min_dist = (-1.0f) * fabsf(min_dist);
}
}
/* Volumetric initialization: Ray-casts around mesh object. */
else {
/* Ray-casts in 26 directions.
* (6 main axis + 12 quadrant diagonals (2D) + 8 octant diagonals (3D)). */
float ray_dirs[26][3] = {
{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f},
{0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {1.0f, 0.0f, -1.0f},
{-1.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 1.0f}, {0.0f, 1.0f, -1.0f},
{0.0f, -1.0f, 1.0f}, {0.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, -1.0f, 1.0f},
{-1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, {1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f},
{-1.0f, 1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}};
size_t ray_cnt = sizeof ray_dirs / sizeof ray_dirs[0];
/* Count ray mesh misses (i.e. no face hit) and cases where the ray direction matches the face
* normal direction. From this information it can be derived whether a cell is inside or
* outside the mesh. */
int miss_cnt = 0, dir_cnt = 0;
for (int i = 0; i < ray_cnt; i++) {
BVHTreeRayHit hit_tree = {0};
hit_tree.index = -1;
hit_tree.dist = PHI_MAX;
normalize_v3(ray_dirs[i]);
BLI_bvhtree_ray_cast(tree_data->tree,
ray_start,
ray_dirs[i],
0.0f,
&hit_tree,
tree_data->raycast_callback,
tree_data);
/* Ray did not hit mesh.
* Current point definitely not inside mesh. Inside mesh as all rays have to hit. */
if (hit_tree.index == -1) {
miss_cnt++;
/* Skip this ray since nothing was hit. */
continue;
}
/* Ray and normal are pointing in opposite directions. */
if (dot_v3v3(ray_dirs[i], hit_tree.no) <= 0) {
dir_cnt++;
}
if (hit_tree.dist < min_dist) {
min_dist = hit_tree.dist;
}
}
/* Point lies inside mesh. Use negative sign for distance value.
* This "if statement" has 2 conditions that can be true for points outside mesh. */
if (!(miss_cnt > 0 || dir_cnt == ray_cnt)) {
min_dist = (-1.0f) * fabsf(min_dist);
}
/* Subtract optional surface thickness value and virtually increase the object size. */
if (surface_thickness) {
min_dist -= surface_thickness;
}
}
/* Update global distance array but ensure that older entries are not overridden. */
distance_map[index] = MIN2(distance_map[index], min_dist);
/* Sanity check: Ensure that distances don't explode. */
CLAMP(distance_map[index], -PHI_MAX, PHI_MAX);
}
static void sample_mesh(FluidFlowSettings *mfs,
const MVert *mvert,
const MLoop *mloop,
const MLoopTri *mlooptri,
const MLoopUV *mloopuv,
float *influence_map,
float *velocity_map,
int index,
const int base_res[3],
float flow_center[3],
BVHTreeFromMesh *tree_data,
const float ray_start[3],
const float *vert_vel,
bool has_velocity,
int defgrp_index,
MDeformVert *dvert,
float x,
float y,
float z)
{
float ray_dir[3] = {1.0f, 0.0f, 0.0f};
BVHTreeRayHit hit = {0};
BVHTreeNearest nearest = {0};
float volume_factor = 0.0f;
hit.index = -1;
hit.dist = PHI_MAX;
nearest.index = -1;
/* Distance between two opposing vertices in a unit cube.
* I.e. the unit cube diagonal or sqrt(3).
* This value is our nearest neighbor search distance. */
const float surface_distance = 1.732;
nearest.dist_sq = surface_distance * surface_distance; /* find_nearest uses squared distance. */
bool is_gas_flow = (mfs->type == FLUID_FLOW_TYPE_SMOKE || mfs->type == FLUID_FLOW_TYPE_FIRE ||
mfs->type == FLUID_FLOW_TYPE_SMOKEFIRE);
/* Emission strength for gases will be computed below.
* For liquids it's not needed. Just set to non zero value
* to allow initial velocity computation. */
float emission_strength = (is_gas_flow) ? 0.0f : 1.0f;
/* Emission inside the flow object. */
if (is_gas_flow && mfs->volume_density) {
if (BLI_bvhtree_ray_cast(tree_data->tree,
ray_start,
ray_dir,
0.0f,
&hit,
tree_data->raycast_callback,
tree_data) != -1) {
float dot = ray_dir[0] * hit.no[0] + ray_dir[1] * hit.no[1] + ray_dir[2] * hit.no[2];
/* If ray and hit face normal are facing same direction hit point is inside a closed mesh. */
if (dot >= 0) {
/* Also cast a ray in opposite direction to make sure point is at least surrounded by two
* faces. */
negate_v3(ray_dir);
hit.index = -1;
hit.dist = PHI_MAX;
BLI_bvhtree_ray_cast(tree_data->tree,
ray_start,
ray_dir,
0.0f,
&hit,
tree_data->raycast_callback,
tree_data);
if (hit.index != -1) {
volume_factor = mfs->volume_density;
}
}
}
}
/* Find the nearest point on the mesh. */
if (BLI_bvhtree_find_nearest(
tree_data->tree, ray_start, &nearest, tree_data->nearest_callback, tree_data) != -1) {
float weights[3];
int v1, v2, v3, f_index = nearest.index;
float n1[3], n2[3], n3[3], hit_normal[3];
/* Calculate barycentric weights for nearest point. */
v1 = mloop[mlooptri[f_index].tri[0]].v;
v2 = mloop[mlooptri[f_index].tri[1]].v;
v3 = mloop[mlooptri[f_index].tri[2]].v;
interp_weights_tri_v3(weights, mvert[v1].co, mvert[v2].co, mvert[v3].co, nearest.co);
/* Compute emission strength for smoke flow. */
if (is_gas_flow) {
/* Emission from surface is based on UI configurable distance value. */
if (mfs->surface_distance) {
emission_strength = sqrtf(nearest.dist_sq) / mfs->surface_distance;
CLAMP(emission_strength, 0.0f, 1.0f);
emission_strength = pow(1.0f - emission_strength, 0.5f);
}
else {
emission_strength = 0.0f;
}
/* Apply vertex group influence if it is being used. */
if (defgrp_index != -1 && dvert) {
float weight_mask = BKE_defvert_find_weight(&dvert[v1], defgrp_index) * weights[0] +
BKE_defvert_find_weight(&dvert[v2], defgrp_index) * weights[1] +
BKE_defvert_find_weight(&dvert[v3], defgrp_index) * weights[2];
emission_strength *= weight_mask;
}
/* Apply emission texture. */
if ((mfs->flags & FLUID_FLOW_TEXTUREEMIT) && mfs->noise_texture) {
float tex_co[3] = {0};
TexResult texres;
if (mfs->texture_type == FLUID_FLOW_TEXTURE_MAP_AUTO) {
tex_co[0] = ((x - flow_center[0]) / base_res[0]) / mfs->texture_size;
tex_co[1] = ((y - flow_center[1]) / base_res[1]) / mfs->texture_size;
tex_co[2] = ((z - flow_center[2]) / base_res[2] - mfs->texture_offset) /
mfs->texture_size;
}
else if (mloopuv) {
const float *uv[3];
uv[0] = mloopuv[mlooptri[f_index].tri[0]].uv;
uv[1] = mloopuv[mlooptri[f_index].tri[1]].uv;
uv[2] = mloopuv[mlooptri[f_index].tri[2]].uv;
interp_v2_v2v2v2(tex_co, UNPACK3(uv), weights);
/* Map texure coord between -1.0f and 1.0f. */
tex_co[0] = tex_co[0] * 2.0f - 1.0f;
tex_co[1] = tex_co[1] * 2.0f - 1.0f;
tex_co[2] = mfs->texture_offset;
}
texres.nor = NULL;
BKE_texture_get_value(NULL, mfs->noise_texture, tex_co, &texres, false);
emission_strength *= texres.tin;
}
}
/* Initial velocity of flow object. Only compute velocity if emission is present. */
if (mfs->flags & FLUID_FLOW_INITVELOCITY && velocity_map && emission_strength != 0.0) {
/* Apply normal directional velocity. */
if (mfs->vel_normal) {
/* Interpolate vertex normal vectors to get nearest point normal. */
normal_short_to_float_v3(n1, mvert[v1].no);
normal_short_to_float_v3(n2, mvert[v2].no);
normal_short_to_float_v3(n3, mvert[v3].no);
interp_v3_v3v3v3(hit_normal, n1, n2, n3, weights);
normalize_v3(hit_normal);
/* Apply normal directional velocity. */
velocity_map[index * 3] += hit_normal[0] * mfs->vel_normal * 0.25f;
velocity_map[index * 3 + 1] += hit_normal[1] * mfs->vel_normal * 0.25f;
velocity_map[index * 3 + 2] += hit_normal[2] * mfs->vel_normal * 0.25f;
}
/* Apply object velocity. */
if (has_velocity && mfs->vel_multi) {
float hit_vel[3];
interp_v3_v3v3v3(
hit_vel, &vert_vel[v1 * 3], &vert_vel[v2 * 3], &vert_vel[v3 * 3], weights);
velocity_map[index * 3] += hit_vel[0] * mfs->vel_multi;
velocity_map[index * 3 + 1] += hit_vel[1] * mfs->vel_multi;
velocity_map[index * 3 + 2] += hit_vel[2] * mfs->vel_multi;
# ifdef DEBUG_PRINT
/* Debugging: Print flow object velocities. */
printf("adding flow object vel: [%f, %f, %f]\n", hit_vel[0], hit_vel[1], hit_vel[2]);
# endif
}
velocity_map[index * 3] += mfs->vel_coord[0];
velocity_map[index * 3 + 1] += mfs->vel_coord[1];
velocity_map[index * 3 + 2] += mfs->vel_coord[2];
}
}
/* Apply final influence value but also consider volume initialization factor. */
influence_map[index] = MAX2(volume_factor, emission_strength);
}
typedef struct EmitFromDMData {
FluidDomainSettings *mds;
FluidFlowSettings *mfs;
const MVert *mvert;
const MLoop *mloop;
const MLoopTri *mlooptri;
const MLoopUV *mloopuv;
MDeformVert *dvert;
int defgrp_index;
BVHTreeFromMesh *tree;
FluidObjectBB *bb;
bool has_velocity;
float *vert_vel;
float *flow_center;
int *min, *max, *res;
} EmitFromDMData;
static void emit_from_mesh_task_cb(void *__restrict userdata,
const int z,
const TaskParallelTLS *__restrict UNUSED(tls))
{
EmitFromDMData *data = userdata;
FluidObjectBB *bb = data->bb;
for (int x = data->min[0]; x < data->max[0]; x++) {
for (int y = data->min[1]; y < data->max[1]; y++) {
const int index = manta_get_index(
x - bb->min[0], bb->res[0], y - bb->min[1], bb->res[1], z - bb->min[2]);
const float ray_start[3] = {((float)x) + 0.5f, ((float)y) + 0.5f, ((float)z) + 0.5f};
/* Compute emission only for flow objects that produce fluid (i.e. skip outflow objects).
* Result in bb->influence. Also computes initial velocities. Result in bb->velocity. */
if ((data->mfs->behavior == FLUID_FLOW_BEHAVIOR_GEOMETRY) ||
(data->mfs->behavior == FLUID_FLOW_BEHAVIOR_INFLOW)) {
sample_mesh(data->mfs,
data->mvert,
data->mloop,
data->mlooptri,
data->mloopuv,
bb->influence,
bb->velocity,
index,
data->mds->base_res,
data->flow_center,
data->tree,
ray_start,
data->vert_vel,
data->has_velocity,
data->defgrp_index,
data->dvert,
(float)x,
(float)y,
(float)z);
}
/* Calculate levelset values from meshes. Result in bb->distances. */
update_distances(index,
bb->distances,
data->tree,
ray_start,
data->mfs->surface_distance,
data->mfs->flags & FLUID_FLOW_USE_PLANE_INIT);
}
}
}
static void emit_from_mesh(
Object *flow_ob, FluidDomainSettings *mds, FluidFlowSettings *mfs, FluidObjectBB *bb, float dt)
{
if (mfs->mesh) {
Mesh *me = NULL;
MVert *mvert = NULL;
const MLoopTri *mlooptri = NULL;
const MLoop *mloop = NULL;
const MLoopUV *mloopuv = NULL;
MDeformVert *dvert = NULL;
BVHTreeFromMesh tree_data = {NULL};
int numverts, i;
float *vert_vel = NULL;
bool has_velocity = false;
int defgrp_index = mfs->vgroup_density - 1;
float flow_center[3] = {0};
int min[3], max[3], res[3];
/* Copy mesh for thread safety as we modify it.
* Main issue is its VertArray being modified, then replaced and freed. */
me = BKE_mesh_copy_for_eval(mfs->mesh, true);
/* Duplicate vertices to modify. */
if (me->mvert) {
me->mvert = MEM_dupallocN(me->mvert);
CustomData_set_layer(&me->vdata, CD_MVERT, me->mvert);
}
BKE_mesh_ensure_normals(me);
mvert = me->mvert;
mloop = me->mloop;
mlooptri = BKE_mesh_runtime_looptri_ensure(me);
numverts = me->totvert;
dvert = CustomData_get_layer(&me->vdata, CD_MDEFORMVERT);
mloopuv = CustomData_get_layer_named(&me->ldata, CD_MLOOPUV, mfs->uvlayer_name);
if (mfs->flags & FLUID_FLOW_INITVELOCITY) {
vert_vel = MEM_callocN(sizeof(float) * numverts * 3, "manta_flow_velocity");
if (mfs->numverts != numverts || !mfs->verts_old) {
if (mfs->verts_old) {
MEM_freeN(mfs->verts_old);
}
mfs->verts_old = MEM_callocN(sizeof(float) * numverts * 3, "manta_flow_verts_old");
mfs->numverts = numverts;
}
else {
has_velocity = true;
}
}
/* Transform mesh vertices to domain grid space for fast lookups */
for (i = 0; i < numverts; i++) {
float n[3];
/* Vertex position. */
mul_m4_v3(flow_ob->obmat, mvert[i].co);
manta_pos_to_cell(mds, mvert[i].co);
/* Vertex normal. */
normal_short_to_float_v3(n, mvert[i].no);
mul_mat3_m4_v3(flow_ob->obmat, n);
mul_mat3_m4_v3(mds->imat, n);
normalize_v3(n);
normal_float_to_short_v3(mvert[i].no, n);
/* Vertex velocity. */
if (mfs->flags & FLUID_FLOW_INITVELOCITY) {
float co[3];
add_v3fl_v3fl_v3i(co, mvert[i].co, mds->shift);
if (has_velocity) {
sub_v3_v3v3(&vert_vel[i * 3], co, &mfs->verts_old[i * 3]);
mul_v3_fl(&vert_vel[i * 3], mds->dx / dt);
}
copy_v3_v3(&mfs->verts_old[i * 3], co);
}
/* Calculate emission map bounds. */
bb_boundInsert(bb, mvert[i].co);
}
mul_m4_v3(flow_ob->obmat, flow_center);
manta_pos_to_cell(mds, flow_center);
/* Set emission map.
* Use 3 cell diagonals as margin (3 * 1.732 = 5.196). */
int bounds_margin = (int)ceil(5.196);
clamp_bounds_in_domain(mds, bb->min, bb->max, NULL, NULL, bounds_margin, dt);
bb_allocateData(bb, mfs->flags & FLUID_FLOW_INITVELOCITY, true);
/* Setup loop bounds. */
for (i = 0; i < 3; i++) {
min[i] = bb->min[i];
max[i] = bb->max[i];
res[i] = bb->res[i];
}
if (BKE_bvhtree_from_mesh_get(&tree_data, me, BVHTREE_FROM_LOOPTRI, 4)) {
EmitFromDMData data = {
.mds = mds,
.mfs = mfs,
.mvert = mvert,
.mloop = mloop,
.mlooptri = mlooptri,
.mloopuv = mloopuv,
.dvert = dvert,
.defgrp_index = defgrp_index,
.tree = &tree_data,
.bb = bb,
.has_velocity = has_velocity,
.vert_vel = vert_vel,
.flow_center = flow_center,
.min = min,
.max = max,
.res = res,
};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 2;
BLI_task_parallel_range(min[2], max[2], &data, emit_from_mesh_task_cb, &settings);
}
/* Free bvh tree. */
free_bvhtree_from_mesh(&tree_data);
if (vert_vel) {
MEM_freeN(vert_vel);
}
if (me->mvert) {
MEM_freeN(me->mvert);
}
BKE_id_free(NULL, me);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Fluid Step
* \{ */
static void adaptive_domain_adjust(
FluidDomainSettings *mds, Object *ob, FluidObjectBB *bb_maps, uint numflowobj, float dt)
{
/* calculate domain shift for current frame */
int new_shift[3] = {0};
int total_shift[3];
float frame_shift_f[3];
float ob_loc[3] = {0};
mul_m4_v3(ob->obmat, ob_loc);
sub_v3_v3v3(frame_shift_f, ob_loc, mds->prev_loc);
copy_v3_v3(mds->prev_loc, ob_loc);
/* convert global space shift to local "cell" space */
mul_mat3_m4_v3(mds->imat, frame_shift_f);
frame_shift_f[0] = frame_shift_f[0] / mds->cell_size[0];
frame_shift_f[1] = frame_shift_f[1] / mds->cell_size[1];
frame_shift_f[2] = frame_shift_f[2] / mds->cell_size[2];
/* add to total shift */
add_v3_v3(mds->shift_f, frame_shift_f);
/* convert to integer */
total_shift[0] = (int)(floorf(mds->shift_f[0]));
total_shift[1] = (int)(floorf(mds->shift_f[1]));
total_shift[2] = (int)(floorf(mds->shift_f[2]));
int temp_shift[3];
copy_v3_v3_int(temp_shift, mds->shift);
sub_v3_v3v3_int(new_shift, total_shift, mds->shift);
copy_v3_v3_int(mds->shift, total_shift);
/* calculate new domain boundary points so that smoke doesn't slide on sub-cell movement */
mds->p0[0] = mds->dp0[0] - mds->cell_size[0] * (mds->shift_f[0] - total_shift[0] - 0.5f);
mds->p0[1] = mds->dp0[1] - mds->cell_size[1] * (mds->shift_f[1] - total_shift[1] - 0.5f);
mds->p0[2] = mds->dp0[2] - mds->cell_size[2] * (mds->shift_f[2] - total_shift[2] - 0.5f);
mds->p1[0] = mds->p0[0] + mds->cell_size[0] * mds->base_res[0];
mds->p1[1] = mds->p0[1] + mds->cell_size[1] * mds->base_res[1];
mds->p1[2] = mds->p0[2] + mds->cell_size[2] * mds->base_res[2];
/* adjust domain resolution */
const int block_size = mds->noise_scale;
int min[3] = {32767, 32767, 32767}, max[3] = {-32767, -32767, -32767}, res[3];
int total_cells = 1, res_changed = 0, shift_changed = 0;
float min_vel[3], max_vel[3];
int x, y, z;
float *density = manta_smoke_get_density(mds->fluid);
float *fuel = manta_smoke_get_fuel(mds->fluid);
float *bigdensity = manta_smoke_turbulence_get_density(mds->fluid);
float *bigfuel = manta_smoke_turbulence_get_fuel(mds->fluid);
float *vx = manta_get_velocity_x(mds->fluid);
float *vy = manta_get_velocity_y(mds->fluid);
float *vz = manta_get_velocity_z(mds->fluid);
int wt_res[3];
if (mds->flags & FLUID_DOMAIN_USE_NOISE && mds->fluid) {
manta_smoke_turbulence_get_res(mds->fluid, wt_res);
}
INIT_MINMAX(min_vel, max_vel);
/* Calculate bounds for current domain content */
for (x = mds->res_min[0]; x < mds->res_max[0]; x++) {
for (y = mds->res_min[1]; y < mds->res_max[1]; y++) {
for (z = mds->res_min[2]; z < mds->res_max[2]; z++) {
int xn = x - new_shift[0];
int yn = y - new_shift[1];
int zn = z - new_shift[2];
int index;
float max_den;
/* skip if cell already belongs to new area */
if (xn >= min[0] && xn <= max[0] && yn >= min[1] && yn <= max[1] && zn >= min[2] &&
zn <= max[2]) {
continue;
}
index = manta_get_index(x - mds->res_min[0],
mds->res[0],
y - mds->res_min[1],
mds->res[1],
z - mds->res_min[2]);
max_den = (fuel) ? MAX2(density[index], fuel[index]) : density[index];
/* check high resolution bounds if max density isnt already high enough */
if (max_den < mds->adapt_threshold && mds->flags & FLUID_DOMAIN_USE_NOISE && mds->fluid) {
int i, j, k;
/* high res grid index */
int xx = (x - mds->res_min[0]) * block_size;
int yy = (y - mds->res_min[1]) * block_size;
int zz = (z - mds->res_min[2]) * block_size;
for (i = 0; i < block_size; i++) {
for (j = 0; j < block_size; j++) {
for (k = 0; k < block_size; k++) {
int big_index = manta_get_index(xx + i, wt_res[0], yy + j, wt_res[1], zz + k);
float den = (bigfuel) ? MAX2(bigdensity[big_index], bigfuel[big_index]) :
bigdensity[big_index];
if (den > max_den) {
max_den = den;
}
}
}
}
}
/* content bounds (use shifted coordinates) */
if (max_den >= mds->adapt_threshold) {
if (min[0] > xn) {
min[0] = xn;
}
if (min[1] > yn) {
min[1] = yn;
}
if (min[2] > zn) {
min[2] = zn;
}
if (max[0] < xn) {
max[0] = xn;
}
if (max[1] < yn) {
max[1] = yn;
}
if (max[2] < zn) {
max[2] = zn;
}
}
/* velocity bounds */
if (min_vel[0] > vx[index]) {
min_vel[0] = vx[index];
}
if (min_vel[1] > vy[index]) {
min_vel[1] = vy[index];
}
if (min_vel[2] > vz[index]) {
min_vel[2] = vz[index];
}
if (max_vel[0] < vx[index]) {
max_vel[0] = vx[index];
}
if (max_vel[1] < vy[index]) {
max_vel[1] = vy[index];
}
if (max_vel[2] < vz[index]) {
max_vel[2] = vz[index];
}
}
}
}
/* also apply emission maps */
for (int i = 0; i < numflowobj; i++) {
FluidObjectBB *bb = &bb_maps[i];
for (x = bb->min[0]; x < bb->max[0]; x++) {
for (y = bb->min[1]; y < bb->max[1]; y++) {
for (z = bb->min[2]; z < bb->max[2]; z++) {
int index = manta_get_index(
x - bb->min[0], bb->res[0], y - bb->min[1], bb->res[1], z - bb->min[2]);
float max_den = bb->influence[index];
/* density bounds */
if (max_den >= mds->adapt_threshold) {
if (min[0] > x) {
min[0] = x;
}
if (min[1] > y) {
min[1] = y;
}
if (min[2] > z) {
min[2] = z;
}
if (max[0] < x) {
max[0] = x;
}
if (max[1] < y) {
max[1] = y;
}
if (max[2] < z) {
max[2] = z;
}
}
}
}
}
}
/* calculate new bounds based on these values */
clamp_bounds_in_domain(mds, min, max, min_vel, max_vel, mds->adapt_margin + 1, dt);
for (int i = 0; i < 3; i++) {
/* calculate new resolution */
res[i] = max[i] - min[i];
total_cells *= res[i];
if (new_shift[i]) {
shift_changed = 1;
}
/* if no content set minimum dimensions */
if (res[i] <= 0) {
int j;
for (j = 0; j < 3; j++) {
min[j] = 0;
max[j] = 1;
res[j] = 1;
}
res_changed = 1;
total_cells = 1;
break;
}
if (min[i] != mds->res_min[i] || max[i] != mds->res_max[i]) {
res_changed = 1;
}
}
if (res_changed || shift_changed) {
BKE_fluid_reallocate_copy_fluid(
mds, mds->res, res, mds->res_min, min, mds->res_max, temp_shift, total_shift);
/* set new domain dimensions */
copy_v3_v3_int(mds->res_min, min);
copy_v3_v3_int(mds->res_max, max);
copy_v3_v3_int(mds->res, res);
mds->total_cells = total_cells;
/* Redo adapt time step in manta to refresh solver state (ie time variables) */
manta_adapt_timestep(mds->fluid);
}
/* update global size field with new bbox size */
/* volume bounds */
float minf[3], maxf[3], size[3];
madd_v3fl_v3fl_v3fl_v3i(minf, mds->p0, mds->cell_size, mds->res_min);
madd_v3fl_v3fl_v3fl_v3i(maxf, mds->p0, mds->cell_size, mds->res_max);
/* calculate domain dimensions */
sub_v3_v3v3(size, maxf, minf);
/* apply object scale */
for (int i = 0; i < 3; i++) {
size[i] = fabsf(size[i] * ob->scale[i]);
}
copy_v3_v3(mds->global_size, size);
}
BLI_INLINE void apply_outflow_fields(int index,
float distance_value,
float *density,
float *heat,
float *fuel,
float *react,
float *color_r,
float *color_g,
float *color_b,
float *phiout)
{
/* determine outflow cells - phiout used in smoke and liquids */
if (phiout) {
phiout[index] = distance_value;
}
/* set smoke outflow */
if (density) {
density[index] = 0.0f;
}
if (heat) {
heat[index] = 0.0f;
}
if (fuel) {
fuel[index] = 0.0f;
react[index] = 0.0f;
}
if (color_r) {
color_r[index] = 0.0f;
color_g[index] = 0.0f;
color_b[index] = 0.0f;
}
}
BLI_INLINE void apply_inflow_fields(FluidFlowSettings *mfs,
float emission_value,
float distance_value,
int index,
float *density_in,
const float *density,
float *heat_in,
const float *heat,
float *fuel_in,
const float *fuel,
float *react_in,
const float *react,
float *color_r_in,
const float *color_r,
float *color_g_in,
const float *color_g,
float *color_b_in,
const float *color_b,
float *phi_in,
float *emission_in)
{
/* Set levelset value for liquid inflow.
* Ensure that distance value is "joined" into the levelset. */
if (phi_in) {
phi_in[index] = MIN2(distance_value, phi_in[index]);
}
/* Set emission value for smoke inflow.
* Ensure that emission value is "maximized". */
if (emission_in) {
emission_in[index] = MAX2(emission_value, emission_in[index]);
}
/* Set inflow for smoke from here on. */
int absolute_flow = (mfs->flags & FLUID_FLOW_ABSOLUTE);
float dens_old = (density) ? density[index] : 0.0;
// float fuel_old = (fuel) ? fuel[index] : 0.0f; /* UNUSED */
float dens_flow = (mfs->type == FLUID_FLOW_TYPE_FIRE) ? 0.0f : emission_value * mfs->density;
float fuel_flow = (fuel) ? emission_value * mfs->fuel_amount : 0.0f;
/* Set heat inflow. */
if (heat && heat_in) {
if (emission_value > 0.0f) {
heat_in[index] = ADD_IF_LOWER(heat[index], mfs->temperature);
}
}
/* Set density and fuel - absolute mode. */
if (absolute_flow) {
if (density && density_in) {
if (mfs->type != FLUID_FLOW_TYPE_FIRE && dens_flow > density[index]) {
/* Use MAX2 to preserve values from other emitters at this cell. */
density_in[index] = MAX2(dens_flow, density_in[index]);
}
}
if (fuel && fuel_in) {
if (mfs->type != FLUID_FLOW_TYPE_SMOKE && fuel_flow && fuel_flow > fuel[index]) {
/* Use MAX2 to preserve values from other emitters at this cell. */
fuel_in[index] = MAX2(fuel_flow, fuel_in[index]);
}
}
}
/* Set density and fuel - additive mode. */
else {
if (density && density_in) {
if (mfs->type != FLUID_FLOW_TYPE_FIRE) {
density_in[index] += dens_flow;
CLAMP(density_in[index], 0.0f, 1.0f);
}
}
if (fuel && fuel_in) {
if (mfs->type != FLUID_FLOW_TYPE_SMOKE && mfs->fuel_amount) {
fuel_in[index] += fuel_flow;
CLAMP(fuel_in[index], 0.0f, 10.0f);
}
}
}
/* Set color. */
if (color_r && color_r_in) {
if (dens_flow) {
float total_dens = density[index] / (dens_old + dens_flow);
color_r_in[index] = (color_r[index] + mfs->color[0] * dens_flow) * total_dens;
color_g_in[index] = (color_g[index] + mfs->color[1] * dens_flow) * total_dens;
color_b_in[index] = (color_b[index] + mfs->color[2] * dens_flow) * total_dens;
}
}
/* Set fire reaction coordinate. */
if (fuel && fuel_in) {
/* Instead of using 1.0 for all new fuel add slight falloff to reduce flow blocky-ness. */
float value = 1.0f - pow2f(1.0f - emission_value);
if (fuel_in[index] > FLT_EPSILON && value > react[index]) {
float f = fuel_flow / fuel_in[index];
react_in[index] = value * f + (1.0f - f) * react[index];
CLAMP(react_in[index], 0.0f, value);
}
}
}
static void update_flowsflags(FluidDomainSettings *mds, Object **flowobjs, int numflowobj)
{
int active_fields = mds->active_fields;
uint flow_index;
/* First, remove all flags that we want to update. */
int prev_flags = (FLUID_DOMAIN_ACTIVE_INVEL | FLUID_DOMAIN_ACTIVE_OUTFLOW |
FLUID_DOMAIN_ACTIVE_HEAT | FLUID_DOMAIN_ACTIVE_FIRE |
FLUID_DOMAIN_ACTIVE_COLOR_SET | FLUID_DOMAIN_ACTIVE_COLORS);
active_fields &= ~prev_flags;
/* Monitor active fields based on flow settings */
for (flow_index = 0; flow_index < numflowobj; flow_index++) {
Object *coll_ob = flowobjs[flow_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(coll_ob,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
if ((mmd2->type & MOD_FLUID_TYPE_FLOW) && mmd2->flow) {
FluidFlowSettings *mfs = mmd2->flow;
if (!mfs) {
break;
}
if (mfs->flags & FLUID_FLOW_INITVELOCITY) {
active_fields |= FLUID_DOMAIN_ACTIVE_INVEL;
}
if (mfs->behavior == FLUID_FLOW_BEHAVIOR_OUTFLOW) {
active_fields |= FLUID_DOMAIN_ACTIVE_OUTFLOW;
}
/* liquids done from here */
if (mds->type == FLUID_DOMAIN_TYPE_LIQUID) {
continue;
}
/* activate heat field if flow produces any heat */
if (mfs->temperature) {
active_fields |= FLUID_DOMAIN_ACTIVE_HEAT;
}
/* activate fuel field if flow adds any fuel */
if (mfs->fuel_amount &&
(mfs->type == FLUID_FLOW_TYPE_FIRE || mfs->type == FLUID_FLOW_TYPE_SMOKEFIRE)) {
active_fields |= FLUID_DOMAIN_ACTIVE_FIRE;
}
/* activate color field if flows add smoke with varying colors */
if (mfs->density &&
(mfs->type == FLUID_FLOW_TYPE_SMOKE || mfs->type == FLUID_FLOW_TYPE_SMOKEFIRE)) {
if (!(active_fields & FLUID_DOMAIN_ACTIVE_COLOR_SET)) {
copy_v3_v3(mds->active_color, mfs->color);
active_fields |= FLUID_DOMAIN_ACTIVE_COLOR_SET;
}
else if (!equals_v3v3(mds->active_color, mfs->color)) {
copy_v3_v3(mds->active_color, mfs->color);
active_fields |= FLUID_DOMAIN_ACTIVE_COLORS;
}
}
}
}
/* Monitor active fields based on domain settings */
if (mds->type == FLUID_DOMAIN_TYPE_GAS && active_fields & FLUID_DOMAIN_ACTIVE_FIRE) {
/* heat is always needed for fire */
active_fields |= FLUID_DOMAIN_ACTIVE_HEAT;
/* also activate colors if domain smoke color differs from active color */
if (!(active_fields & FLUID_DOMAIN_ACTIVE_COLOR_SET)) {
copy_v3_v3(mds->active_color, mds->flame_smoke_color);
active_fields |= FLUID_DOMAIN_ACTIVE_COLOR_SET;
}
else if (!equals_v3v3(mds->active_color, mds->flame_smoke_color)) {
copy_v3_v3(mds->active_color, mds->flame_smoke_color);
active_fields |= FLUID_DOMAIN_ACTIVE_COLORS;
}
}
/* Finally, initialize new data fields if any */
if (active_fields & FLUID_DOMAIN_ACTIVE_INVEL) {
manta_ensure_invelocity(mds->fluid, mds->mmd);
}
if (active_fields & FLUID_DOMAIN_ACTIVE_OUTFLOW) {
manta_ensure_outflow(mds->fluid, mds->mmd);
}
if (active_fields & FLUID_DOMAIN_ACTIVE_HEAT) {
manta_smoke_ensure_heat(mds->fluid, mds->mmd);
}
if (active_fields & FLUID_DOMAIN_ACTIVE_FIRE) {
manta_smoke_ensure_fire(mds->fluid, mds->mmd);
}
if (active_fields & FLUID_DOMAIN_ACTIVE_COLORS) {
/* initialize all smoke with "active_color" */
manta_smoke_ensure_colors(mds->fluid, mds->mmd);
}
if (mds->type == FLUID_DOMAIN_TYPE_LIQUID &&
(mds->particle_type & FLUID_DOMAIN_PARTICLE_SPRAY ||
mds->particle_type & FLUID_DOMAIN_PARTICLE_FOAM ||
mds->particle_type & FLUID_DOMAIN_PARTICLE_TRACER)) {
manta_liquid_ensure_sndparts(mds->fluid, mds->mmd);
}
mds->active_fields = active_fields;
}
static void update_flowsfluids(struct Depsgraph *depsgraph,
Scene *scene,
Object *ob,
FluidDomainSettings *mds,
float time_per_frame,
float frame_length,
int frame,
float dt)
{
FluidObjectBB *bb_maps = NULL;
Object **flowobjs = NULL;
uint numflowobj = 0, flow_index = 0;
bool is_first_frame = (frame == mds->cache_frame_start);
flowobjs = BKE_collision_objects_create(
depsgraph, ob, mds->fluid_group, &numflowobj, eModifierType_Fluid);
/* Update all flow related flags and ensure that corresponding grids get initialized. */
update_flowsflags(mds, flowobjs, numflowobj);
/* Initialize emission maps for each flow. */
bb_maps = MEM_callocN(sizeof(struct FluidObjectBB) * numflowobj, "fluid_flow_bb_maps");
/* Prepare flow emission maps. */
for (flow_index = 0; flow_index < numflowobj; flow_index++) {
Object *flowobj = flowobjs[flow_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(flowobj,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
/* Check for initialized flow object. */
if ((mmd2->type & MOD_FLUID_TYPE_FLOW) && mmd2->flow) {
FluidFlowSettings *mfs = mmd2->flow;
int subframes = mfs->subframes;
FluidObjectBB *bb = &bb_maps[flow_index];
bool use_velocity = mfs->flags & FLUID_FLOW_INITVELOCITY;
bool is_static = is_static_object(flowobj);
/* Cannot use static mode with adaptive domain.
* The adaptive domain might expand and only later in the simulations discover the static
* object. */
if (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
is_static = false;
}
/* Optimization: Skip flow objects with disabled inflow flag. */
if (mfs->behavior == FLUID_FLOW_BEHAVIOR_INFLOW &&
(mfs->flags & FLUID_FLOW_USE_INFLOW) == 0) {
continue;
}
/* Optimization: No need to compute emission value if it won't be applied. */
if (mfs->behavior == FLUID_FLOW_BEHAVIOR_GEOMETRY && !is_first_frame) {
continue;
}
/* Optimization: Skip flow object if it does not "belong" to this domain type. */
if (mfs->type == FLUID_FLOW_TYPE_LIQUID && mds->type == FLUID_DOMAIN_TYPE_GAS) {
continue;
}
if ((mfs->type == FLUID_FLOW_TYPE_SMOKE || mfs->type == FLUID_FLOW_TYPE_FIRE ||
mfs->type == FLUID_FLOW_TYPE_SMOKEFIRE) &&
mds->type == FLUID_DOMAIN_TYPE_LIQUID) {
continue;
}
/* Optimization: Static liquid flow objects don't need emission computation after first
* frame.
* TODO (sebbas): Also do not use static mode if initial velocities are enabled. */
if (mfs->type == FLUID_FLOW_TYPE_LIQUID && is_static && !is_first_frame && !use_velocity) {
continue;
}
/* Length of one adaptive frame. If using adaptive stepping, length is smaller than actual
* frame length */
float adaptframe_length = time_per_frame / frame_length;
/* Adaptive frame length as percentage */
CLAMP(adaptframe_length, 0.0f, 1.0f);
/* More splitting because of emission subframe: If no subframes present, sample_size is 1. */
float sample_size = 1.0f / (float)(subframes + 1);
/* First frame cannot have any subframes because there is (obviously) no previous frame from
* where subframes could come from. */
if (is_first_frame) {
subframes = 0;
}
int subframe;
float subframe_dt = dt * sample_size;
/* Emission loop. When not using subframes this will loop only once. */
for (subframe = subframes; subframe >= 0; subframe--) {
/* Temporary emission map used when subframes are enabled, i.e. at least one subframe. */
FluidObjectBB bb_temp = {NULL};
/* Set scene time */
/* Handle emission subframe */
if (subframe > 0 && !is_first_frame) {
scene->r.subframe = adaptframe_length -
sample_size * (float)(subframe) * (dt / frame_length);
scene->r.cfra = frame - 1;
}
/* Last frame in this loop (subframe == suframes). Can be real end frame or in between
* frames (adaptive frame). */
else {
/* Handle adaptive subframe (ie has subframe fraction). Need to set according scene
* subframe parameter. */
if (time_per_frame < frame_length) {
scene->r.subframe = adaptframe_length;
scene->r.cfra = frame - 1;
}
/* Handle absolute endframe (ie no subframe fraction). Need to set the scene subframe
* parameter to 0 and advance current scene frame. */
else {
scene->r.subframe = 0.0f;
scene->r.cfra = frame;
}
}
/* Sanity check: subframe portion must be between 0 and 1. */
CLAMP(scene->r.subframe, 0.0f, 1.0f);
# ifdef DEBUG_PRINT
/* Debugging: Print subframe information. */
printf(
"flow: frame (is first: %d): %d // scene current frame: %d // scene current subframe: "
"%f\n",
is_first_frame,
frame,
scene->r.cfra,
scene->r.subframe);
# endif
/* Update frame time, this is considering current subframe fraction
* BLI_mutex_lock() called in manta_step(), so safe to update subframe here
* TODO (sebbas): Using BKE_scene_frame_get(scene) instead of new DEG_get_ctime(depsgraph)
* as subframes don't work with the latter yet. */
BKE_object_modifier_update_subframe(
depsgraph, scene, flowobj, true, 5, BKE_scene_frame_get(scene), eModifierType_Fluid);
/* Emission from particles. */
if (mfs->source == FLUID_FLOW_SOURCE_PARTICLES) {
if (subframes) {
emit_from_particles(flowobj, mds, mfs, &bb_temp, depsgraph, scene, subframe_dt);
}
else {
emit_from_particles(flowobj, mds, mfs, bb, depsgraph, scene, subframe_dt);
}
}
/* Emission from mesh. */
else if (mfs->source == FLUID_FLOW_SOURCE_MESH) {
if (subframes) {
emit_from_mesh(flowobj, mds, mfs, &bb_temp, subframe_dt);
}
else {
emit_from_mesh(flowobj, mds, mfs, bb, subframe_dt);
}
}
else {
printf("Error: unknown flow emission source\n");
}
/* If this we emitted with temp emission map in this loop (subframe emission), we combine
* the temp map with the original emission map. */
if (subframes) {
/* Combine emission maps. */
bb_combineMaps(bb, &bb_temp, !(mfs->flags & FLUID_FLOW_ABSOLUTE), sample_size);
bb_freeData(&bb_temp);
}
}
}
}
# ifdef DEBUG_PRINT
/* Debugging: Print time information. */
printf("flow: frame: %d // time per frame: %f // frame length: %f // dt: %f\n",
frame,
time_per_frame,
frame_length,
dt);
# endif
/* Adjust domain size if needed. Only do this once for every frame. */
if (mds->type == FLUID_DOMAIN_TYPE_GAS && mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
adaptive_domain_adjust(mds, ob, bb_maps, numflowobj, dt);
}
float *phi_in = manta_get_phi_in(mds->fluid);
float *phistatic_in = manta_get_phistatic_in(mds->fluid);
float *phiout_in = manta_get_phiout_in(mds->fluid);
float *density = manta_smoke_get_density(mds->fluid);
float *color_r = manta_smoke_get_color_r(mds->fluid);
float *color_g = manta_smoke_get_color_g(mds->fluid);
float *color_b = manta_smoke_get_color_b(mds->fluid);
float *fuel = manta_smoke_get_fuel(mds->fluid);
float *heat = manta_smoke_get_heat(mds->fluid);
float *react = manta_smoke_get_react(mds->fluid);
float *density_in = manta_smoke_get_density_in(mds->fluid);
float *heat_in = manta_smoke_get_heat_in(mds->fluid);
float *color_r_in = manta_smoke_get_color_r_in(mds->fluid);
float *color_g_in = manta_smoke_get_color_g_in(mds->fluid);
float *color_b_in = manta_smoke_get_color_b_in(mds->fluid);
float *fuel_in = manta_smoke_get_fuel_in(mds->fluid);
float *react_in = manta_smoke_get_react_in(mds->fluid);
float *emission_in = manta_smoke_get_emission_in(mds->fluid);
float *velx_initial = manta_get_in_velocity_x(mds->fluid);
float *vely_initial = manta_get_in_velocity_y(mds->fluid);
float *velz_initial = manta_get_in_velocity_z(mds->fluid);
uint z;
bool use_adaptivedomain = (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN);
/* Grid reset before writing again. */
for (z = 0; z < mds->res[0] * mds->res[1] * mds->res[2]; z++) {
if (phi_in) {
phi_in[z] = PHI_MAX;
}
/* Only reset static inflow on first frame. Only use static inflow without adaptive domains. */
if (phistatic_in && (is_first_frame || use_adaptivedomain)) {
phistatic_in[z] = PHI_MAX;
}
if (phiout_in) {
phiout_in[z] = PHI_MAX;
}
/* Sync smoke inflow grids with their counterparts (simulation grids). */
if (density_in) {
density_in[z] = density[z];
}
if (heat_in) {
heat_in[z] = heat[z];
}
if (color_r_in) {
color_r_in[z] = color_r[z];
color_g_in[z] = color_b[z];
color_b_in[z] = color_g[z];
}
if (fuel_in) {
fuel_in[z] = fuel[z];
react_in[z] = react[z];
}
if (emission_in) {
emission_in[z] = 0.0f;
}
if (velx_initial) {
velx_initial[z] = 0.0f;
vely_initial[z] = 0.0f;
velz_initial[z] = 0.0f;
}
}
/* Apply emission data for every flow object. */
for (flow_index = 0; flow_index < numflowobj; flow_index++) {
Object *flowobj = flowobjs[flow_index];
FluidModifierData *mmd2 = (FluidModifierData *)modifiers_findByType(flowobj,
eModifierType_Fluid);
/* Sanity check. */
if (!mmd2) {
continue;
}
/* Check for initialized flow object. */
if ((mmd2->type & MOD_FLUID_TYPE_FLOW) && mmd2->flow) {
FluidFlowSettings *mfs = mmd2->flow;
bool use_velocity = mfs->flags & FLUID_FLOW_INITVELOCITY;
bool use_inflow = (mfs->flags & FLUID_FLOW_USE_INFLOW);
bool is_liquid = (mfs->type == FLUID_FLOW_TYPE_LIQUID);
bool is_inflow = (mfs->behavior == FLUID_FLOW_BEHAVIOR_INFLOW);
bool is_geometry = (mfs->behavior == FLUID_FLOW_BEHAVIOR_GEOMETRY);
bool is_outflow = (mfs->behavior == FLUID_FLOW_BEHAVIOR_OUTFLOW);
bool is_static = is_static_object(flowobj);
/* Cannot use static mode with adaptive domain.
* The adaptive domain might expand and only later in the simulations discover the static
* object. */
if (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
is_static = false;
}
/* Optimization: Skip flow objects with disabled flow flag. */
if (is_inflow && !use_inflow) {
continue;
}
/* Optimization: Liquid objects don't always need emission application after first frame. */
if (is_liquid && !is_first_frame) {
/* Skip static liquid objects that are not on the first frame.
* TODO (sebbas): Also do not use static mode if initial velocities are enabled. */
if (is_static && !use_velocity) {
continue;
}
/* Liquid geometry objects don't need emission application after first frame. */
if (is_geometry) {
continue;
}
}
FluidObjectBB *bb = &bb_maps[flow_index];
float *velocity_map = bb->velocity;
float *emission_map = bb->influence;
float *distance_map = bb->distances;
int gx, gy, gz, ex, ey, ez, dx, dy, dz;
size_t e_index, d_index;
/* Loop through every emission map cell. */
for (gx = bb->min[0]; gx < bb->max[0]; gx++) {
for (gy = bb->min[1]; gy < bb->max[1]; gy++) {
for (gz = bb->min[2]; gz < bb->max[2]; gz++) {
/* Compute emission map index. */
ex = gx - bb->min[0];
ey = gy - bb->min[1];
ez = gz - bb->min[2];
e_index = manta_get_index(ex, bb->res[0], ey, bb->res[1], ez);
/* Get domain index. */
dx = gx - mds->res_min[0];
dy = gy - mds->res_min[1];
dz = gz - mds->res_min[2];
d_index = manta_get_index(dx, mds->res[0], dy, mds->res[1], dz);
/* Make sure emission cell is inside the new domain boundary. */
if (dx < 0 || dy < 0 || dz < 0 || dx >= mds->res[0] || dy >= mds->res[1] ||
dz >= mds->res[2]) {
continue;
}
/* Delete fluid in outflow regions. */
if (is_outflow) {
apply_outflow_fields(d_index,
distance_map[e_index],
density_in,
heat_in,
fuel_in,
react_in,
color_r_in,
color_g_in,
color_b_in,
phiout_in);
}
/* Do not apply inflow after the first frame when in geometry mode. */
else if (is_geometry && !is_first_frame) {
apply_inflow_fields(mfs,
0.0f,
PHI_MAX,
d_index,
density_in,
density,
heat_in,
heat,
fuel_in,
fuel,
react_in,
react,
color_r_in,
color_r,
color_g_in,
color_g,
color_b_in,
color_b,
phi_in,
emission_in);
}
/* Static liquid objects need inflow application onto static phi grid. */
else if (is_inflow && is_liquid && is_static && is_first_frame) {
apply_inflow_fields(mfs,
0.0f,
distance_map[e_index],
d_index,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
phistatic_in,
NULL);
}
/* Main inflow application. */
else if (is_geometry || is_inflow) {
apply_inflow_fields(mfs,
emission_map[e_index],
distance_map[e_index],
d_index,
density_in,
density,
heat_in,
heat,
fuel_in,
fuel,
react_in,
react,
color_r_in,
color_r,
color_g_in,
color_g,
color_b_in,
color_b,
phi_in,
emission_in);
if (mfs->flags & FLUID_FLOW_INITVELOCITY) {
velx_initial[d_index] = velocity_map[e_index * 3];
vely_initial[d_index] = velocity_map[e_index * 3 + 1];
velz_initial[d_index] = velocity_map[e_index * 3 + 2];
}
}
}
}
} /* End of flow emission map loop. */
bb_freeData(bb);
} /* End of flow object loop. */
}
BKE_collision_objects_free(flowobjs);
if (bb_maps) {
MEM_freeN(bb_maps);
}
}
typedef struct UpdateEffectorsData {
Scene *scene;
FluidDomainSettings *mds;
ListBase *effectors;
float *density;
float *fuel;
float *force_x;
float *force_y;
float *force_z;
float *velocity_x;
float *velocity_y;
float *velocity_z;
int *flags;
float *phi_obs_in;
} UpdateEffectorsData;
static void update_effectors_task_cb(void *__restrict userdata,
const int x,
const TaskParallelTLS *__restrict UNUSED(tls))
{
UpdateEffectorsData *data = userdata;
FluidDomainSettings *mds = data->mds;
for (int y = 0; y < mds->res[1]; y++) {
for (int z = 0; z < mds->res[2]; z++) {
EffectedPoint epoint;
float mag;
float voxel_center[3] = {0, 0, 0}, vel[3] = {0, 0, 0}, retvel[3] = {0, 0, 0};
const uint index = manta_get_index(x, mds->res[0], y, mds->res[1], z);
if ((data->fuel && MAX2(data->density[index], data->fuel[index]) < FLT_EPSILON) ||
(data->density && data->density[index] < FLT_EPSILON) ||
(data->phi_obs_in && data->phi_obs_in[index] < 0.0f) ||
data->flags[index] & 2) // mantaflow convention: 2 == FlagObstacle
{
continue;
}
/* get velocities from manta grid space and convert to blender units */
vel[0] = data->velocity_x[index];
vel[1] = data->velocity_y[index];
vel[2] = data->velocity_z[index];
mul_v3_fl(vel, mds->dx);
/* convert vel to global space */
mag = len_v3(vel);
mul_mat3_m4_v3(mds->obmat, vel);
normalize_v3(vel);
mul_v3_fl(vel, mag);
voxel_center[0] = mds->p0[0] + mds->cell_size[0] * ((float)(x + mds->res_min[0]) + 0.5f);
voxel_center[1] = mds->p0[1] + mds->cell_size[1] * ((float)(y + mds->res_min[1]) + 0.5f);
voxel_center[2] = mds->p0[2] + mds->cell_size[2] * ((float)(z + mds->res_min[2]) + 0.5f);
mul_m4_v3(mds->obmat, voxel_center);
/* do effectors */
pd_point_from_loc(data->scene, voxel_center, vel, index, &epoint);
BKE_effectors_apply(data->effectors, NULL, mds->effector_weights, &epoint, retvel, NULL);
/* convert retvel to local space */
mag = len_v3(retvel);
mul_mat3_m4_v3(mds->imat, retvel);
normalize_v3(retvel);
mul_v3_fl(retvel, mag);
/* constrain forces to interval -1 to 1 */
data->force_x[index] = min_ff(max_ff(-1.0f, retvel[0] * 0.2f), 1.0f);
data->force_y[index] = min_ff(max_ff(-1.0f, retvel[1] * 0.2f), 1.0f);
data->force_z[index] = min_ff(max_ff(-1.0f, retvel[2] * 0.2f), 1.0f);
}
}
}
static void update_effectors(
Depsgraph *depsgraph, Scene *scene, Object *ob, FluidDomainSettings *mds, float UNUSED(dt))
{
ListBase *effectors;
/* make sure smoke flow influence is 0.0f */
mds->effector_weights->weight[PFIELD_SMOKEFLOW] = 0.0f;
effectors = BKE_effectors_create(depsgraph, ob, NULL, mds->effector_weights);
if (effectors) {
// precalculate wind forces
UpdateEffectorsData data;
data.scene = scene;
data.mds = mds;
data.effectors = effectors;
data.density = manta_smoke_get_density(mds->fluid);
data.fuel = manta_smoke_get_fuel(mds->fluid);
data.force_x = manta_get_force_x(mds->fluid);
data.force_y = manta_get_force_y(mds->fluid);
data.force_z = manta_get_force_z(mds->fluid);
data.velocity_x = manta_get_velocity_x(mds->fluid);
data.velocity_y = manta_get_velocity_y(mds->fluid);
data.velocity_z = manta_get_velocity_z(mds->fluid);
data.flags = manta_smoke_get_flags(mds->fluid);
data.phi_obs_in = manta_get_phiobs_in(mds->fluid);
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 2;
BLI_task_parallel_range(0, mds->res[0], &data, update_effectors_task_cb, &settings);
}
BKE_effectors_free(effectors);
}
static Mesh *create_liquid_geometry(FluidDomainSettings *mds, Mesh *orgmesh, Object *ob)
{
Mesh *me;
MVert *mverts;
MPoly *mpolys;
MLoop *mloops;
short *normals, *no_s;
float no[3];
float min[3];
float max[3];
float size[3];
float cell_size_scaled[3];
/* Assign material + flags to new mesh.
* If there are no faces in original mesh, keep materials and flags unchanged. */
MPoly *mpoly;
MPoly mp_example = {0};
mpoly = orgmesh->mpoly;
if (mpoly) {
mp_example = *mpoly;
}
const short mp_mat_nr = mp_example.mat_nr;
const char mp_flag = mp_example.flag;
int i;
int num_verts, num_normals, num_faces;
if (!mds->fluid) {
return NULL;
}
num_verts = manta_liquid_get_num_verts(mds->fluid);
num_normals = manta_liquid_get_num_normals(mds->fluid);
num_faces = manta_liquid_get_num_triangles(mds->fluid);
# ifdef DEBUG_PRINT
/* Debugging: Print number of vertices, normals, and faces. */
printf("num_verts: %d, num_normals: %d, num_faces: %d\n", num_verts, num_normals, num_faces);
# endif
if (!num_verts || !num_faces) {
return NULL;
}
/* Normals are per vertex, so these must match. */
BLI_assert(num_verts == num_normals);
/* If needed, vertex velocities will be read too. */
bool use_speedvectors = mds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS;
FluidDomainVertexVelocity *velarray = NULL;
float time_mult = 25.f * DT_DEFAULT;
if (use_speedvectors) {
if (mds->mesh_velocities) {
MEM_freeN(mds->mesh_velocities);
}
mds->mesh_velocities = MEM_calloc_arrayN(
num_verts, sizeof(FluidDomainVertexVelocity), "fluid_mesh_vertvelocities");
mds->totvert = num_verts;
velarray = mds->mesh_velocities;
}
me = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 3, num_faces);
if (!me) {
return NULL;
}
mverts = me->mvert;
mpolys = me->mpoly;
mloops = me->mloop;
/* Get size (dimension) but considering scaling scaling. */
copy_v3_v3(cell_size_scaled, mds->cell_size);
mul_v3_v3(cell_size_scaled, ob->scale);
madd_v3fl_v3fl_v3fl_v3i(min, mds->p0, cell_size_scaled, mds->res_min);
madd_v3fl_v3fl_v3fl_v3i(max, mds->p0, cell_size_scaled, mds->res_max);
sub_v3_v3v3(size, max, min);
/* Biggest dimension will be used for upscaling. */
float max_size = MAX3(size[0], size[1], size[2]);
/* Normals. */
normals = MEM_callocN(sizeof(short) * num_normals * 3, "Fluidmesh_tmp_normals");
/* Loop for vertices and normals. */
for (i = 0, no_s = normals; i < num_verts && i < num_normals; i++, mverts++, no_s += 3) {
/* Vertices (data is normalized cube around domain origin). */
mverts->co[0] = manta_liquid_get_vertex_x_at(mds->fluid, i);
mverts->co[1] = manta_liquid_get_vertex_y_at(mds->fluid, i);
mverts->co[2] = manta_liquid_get_vertex_z_at(mds->fluid, i);
/* If reading raw data directly from manta, normalize now (e.g. during replay mode).
* If reading data from files from disk, omit this normalization. */
if (!manta_liquid_mesh_from_file(mds->fluid)) {
// normalize to unit cube around 0
mverts->co[0] -= ((float)mds->res[0] * mds->mesh_scale) * 0.5f;
mverts->co[1] -= ((float)mds->res[1] * mds->mesh_scale) * 0.5f;
mverts->co[2] -= ((float)mds->res[2] * mds->mesh_scale) * 0.5f;
mverts->co[0] *= mds->dx / mds->mesh_scale;
mverts->co[1] *= mds->dx / mds->mesh_scale;
mverts->co[2] *= mds->dx / mds->mesh_scale;
}
mverts->co[0] *= max_size / fabsf(ob->scale[0]);
mverts->co[1] *= max_size / fabsf(ob->scale[1]);
mverts->co[2] *= max_size / fabsf(ob->scale[2]);
# ifdef DEBUG_PRINT
/* Debugging: Print coordinates of vertices. */
printf("mverts->co[0]: %f, mverts->co[1]: %f, mverts->co[2]: %f\n",
mverts->co[0],
mverts->co[1],
mverts->co[2]);
# endif
/* Normals (data is normalized cube around domain origin). */
no[0] = manta_liquid_get_normal_x_at(mds->fluid, i);
no[1] = manta_liquid_get_normal_y_at(mds->fluid, i);
no[2] = manta_liquid_get_normal_z_at(mds->fluid, i);
normal_float_to_short_v3(no_s, no);
# ifdef DEBUG_PRINT
/* Debugging: Print coordinates of normals. */
printf("no_s[0]: %d, no_s[1]: %d, no_s[2]: %d\n", no_s[0], no_s[1], no_s[2]);
# endif
if (use_speedvectors) {
velarray[i].vel[0] = manta_liquid_get_vertvel_x_at(mds->fluid, i) * (mds->dx / time_mult);
velarray[i].vel[1] = manta_liquid_get_vertvel_y_at(mds->fluid, i) * (mds->dx / time_mult);
velarray[i].vel[2] = manta_liquid_get_vertvel_z_at(mds->fluid, i) * (mds->dx / time_mult);
# ifdef DEBUG_PRINT
/* Debugging: Print velocities of vertices. */
printf("velarray[%d].vel[0]: %f, velarray[%d].vel[1]: %f, velarray[%d].vel[2]: %f\n",
i,
velarray[i].vel[0],
i,
velarray[i].vel[1],
i,
velarray[i].vel[2]);
# endif
}
}
/* Loop for triangles. */
for (i = 0; i < num_faces; i++, mpolys++, mloops += 3) {
/* Initialize from existing face. */
mpolys->mat_nr = mp_mat_nr;
mpolys->flag = mp_flag;
mpolys->loopstart = i * 3;
mpolys->totloop = 3;
mloops[0].v = manta_liquid_get_triangle_x_at(mds->fluid, i);
mloops[1].v = manta_liquid_get_triangle_y_at(mds->fluid, i);
mloops[2].v = manta_liquid_get_triangle_z_at(mds->fluid, i);
# ifdef DEBUG_PRINT
/* Debugging: Print mesh faces. */
printf("mloops[0].v: %d, mloops[1].v: %d, mloops[2].v: %d\n",
mloops[0].v,
mloops[1].v,
mloops[2].v);
# endif
}
BKE_mesh_ensure_normals(me);
BKE_mesh_calc_edges(me, false, false);
BKE_mesh_vert_normals_apply(me, (short(*)[3])normals);
MEM_freeN(normals);
return me;
}
static Mesh *create_smoke_geometry(FluidDomainSettings *mds, Mesh *orgmesh, Object *ob)
{
Mesh *result;
MVert *mverts;
MPoly *mpolys;
MLoop *mloops;
float min[3];
float max[3];
float *co;
MPoly *mp;
MLoop *ml;
int num_verts = 8;
int num_faces = 6;
int i;
float ob_loc[3] = {0};
float ob_cache_loc[3] = {0};
/* Just copy existing mesh if there is no content or if the adaptive domain is not being used. */
if (mds->total_cells <= 1 || (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) == 0) {
return BKE_mesh_copy_for_eval(orgmesh, false);
}
result = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 4, num_faces);
mverts = result->mvert;
mpolys = result->mpoly;
mloops = result->mloop;
if (num_verts) {
/* Volume bounds. */
madd_v3fl_v3fl_v3fl_v3i(min, mds->p0, mds->cell_size, mds->res_min);
madd_v3fl_v3fl_v3fl_v3i(max, mds->p0, mds->cell_size, mds->res_max);
/* Set vertices of smoke BB. Especially important, when BB changes (adaptive domain). */
/* Top slab */
co = mverts[0].co;
co[0] = min[0];
co[1] = min[1];
co[2] = max[2];
co = mverts[1].co;
co[0] = max[0];
co[1] = min[1];
co[2] = max[2];
co = mverts[2].co;
co[0] = max[0];
co[1] = max[1];
co[2] = max[2];
co = mverts[3].co;
co[0] = min[0];
co[1] = max[1];
co[2] = max[2];
/* Bottom slab. */
co = mverts[4].co;
co[0] = min[0];
co[1] = min[1];
co[2] = min[2];
co = mverts[5].co;
co[0] = max[0];
co[1] = min[1];
co[2] = min[2];
co = mverts[6].co;
co[0] = max[0];
co[1] = max[1];
co[2] = min[2];
co = mverts[7].co;
co[0] = min[0];
co[1] = max[1];
co[2] = min[2];
/* Create faces. */
/* Top side. */
mp = &mpolys[0];
ml = &mloops[0 * 4];
mp->loopstart = 0 * 4;
mp->totloop = 4;
ml[0].v = 0;
ml[1].v = 1;
ml[2].v = 2;
ml[3].v = 3;
/* Right side. */
mp = &mpolys[1];
ml = &mloops[1 * 4];
mp->loopstart = 1 * 4;
mp->totloop = 4;
ml[0].v = 2;
ml[1].v = 1;
ml[2].v = 5;
ml[3].v = 6;
/* Bottom side. */
mp = &mpolys[2];
ml = &mloops[2 * 4];
mp->loopstart = 2 * 4;
mp->totloop = 4;
ml[0].v = 7;
ml[1].v = 6;
ml[2].v = 5;
ml[3].v = 4;
/* Left side. */
mp = &mpolys[3];
ml = &mloops[3 * 4];
mp->loopstart = 3 * 4;
mp->totloop = 4;
ml[0].v = 0;
ml[1].v = 3;
ml[2].v = 7;
ml[3].v = 4;
/* Front side. */
mp = &mpolys[4];
ml = &mloops[4 * 4];
mp->loopstart = 4 * 4;
mp->totloop = 4;
ml[0].v = 3;
ml[1].v = 2;
ml[2].v = 6;
ml[3].v = 7;
/* Back side. */
mp = &mpolys[5];
ml = &mloops[5 * 4];
mp->loopstart = 5 * 4;
mp->totloop = 4;
ml[0].v = 1;
ml[1].v = 0;
ml[2].v = 4;
ml[3].v = 5;
/* Calculate required shift to match domain's global position
* it was originally simulated at (if object moves without manta step). */
invert_m4_m4(ob->imat, ob->obmat);
mul_m4_v3(ob->obmat, ob_loc);
mul_m4_v3(mds->obmat, ob_cache_loc);
sub_v3_v3v3(mds->obj_shift_f, ob_cache_loc, ob_loc);
/* Convert shift to local space and apply to vertices. */
mul_mat3_m4_v3(ob->imat, mds->obj_shift_f);
/* Apply shift to vertices. */
for (i = 0; i < num_verts; i++) {
add_v3_v3(mverts[i].co, mds->obj_shift_f);
}
}
BKE_mesh_calc_edges(result, false, false);
result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
return result;
}
static int manta_step(
Depsgraph *depsgraph, Scene *scene, Object *ob, Mesh *me, FluidModifierData *mmd, int frame)
{
FluidDomainSettings *mds = mmd->domain;
float dt, frame_length, time_total;
float time_per_frame;
bool init_resolution = true;
/* Store baking success - bake might be aborted anytime by user. */
int result = 1;
int mode = mds->cache_type;
bool mode_replay = (mode == FLUID_DOMAIN_CACHE_REPLAY);
/* Update object state. */
invert_m4_m4(mds->imat, ob->obmat);
copy_m4_m4(mds->obmat, ob->obmat);
/* Gas domain might use adaptive domain. */
if (mds->type == FLUID_DOMAIN_TYPE_GAS) {
init_resolution = (mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) != 0;
}
manta_set_domain_from_mesh(mds, ob, me, init_resolution);
/* Use local variables for adaptive loop, dt can change. */
frame_length = mds->frame_length;
dt = mds->dt;
time_per_frame = 0;
time_total = mds->time_total;
BLI_mutex_lock(&object_update_lock);
/* Loop as long as time_per_frame (sum of sub dt's) does not exceed actual framelength. */
while (time_per_frame + FLT_EPSILON < frame_length) {
manta_adapt_timestep(mds->fluid);
dt = manta_get_timestep(mds->fluid);
/* Save adapted dt so that MANTA object can access it (important when adaptive domain creates
* new MANTA object). */
mds->dt = dt;
/* Calculate inflow geometry. */
update_flowsfluids(depsgraph, scene, ob, mds, time_per_frame, frame_length, frame, dt);
/* If user requested stop, quit baking */
if (G.is_break && !mode_replay) {
result = 0;
break;
}
manta_update_variables(mds->fluid, mmd);
/* Calculate obstacle geometry. */
update_obstacles(depsgraph, scene, ob, mds, time_per_frame, frame_length, frame, dt);
/* If user requested stop, quit baking */
if (G.is_break && !mode_replay) {
result = 0;
break;
}
if (mds->total_cells > 1) {
update_effectors(depsgraph, scene, ob, mds, dt);
manta_bake_data(mds->fluid, mmd, frame);
}
/* Count for how long this while loop is running. */
time_per_frame += dt;
time_total += dt;
mds->time_per_frame = time_per_frame;
mds->time_total = time_total;
/* If user requested stop, quit baking */
if (G.is_break && !mode_replay) {
result = 0;
break;
}
}
if (mds->type == FLUID_DOMAIN_TYPE_GAS) {
manta_smoke_calc_transparency(mds, DEG_get_evaluated_view_layer(depsgraph));
}
BLI_mutex_unlock(&object_update_lock);
return result;
}
static void manta_guiding(
Depsgraph *depsgraph, Scene *scene, Object *ob, FluidModifierData *mmd, int frame)
{
FluidDomainSettings *mds = mmd->domain;
float fps = scene->r.frs_sec / scene->r.frs_sec_base;
float dt = DT_DEFAULT * (25.0f / fps) * mds->time_scale;
BLI_mutex_lock(&object_update_lock);
update_obstacles(depsgraph, scene, ob, mds, dt, dt, frame, dt);
manta_bake_guiding(mds->fluid, mmd, frame);
BLI_mutex_unlock(&object_update_lock);
}
static void BKE_fluid_modifier_processFlow(FluidModifierData *mmd,
Depsgraph *depsgraph,
Scene *scene,
Object *ob,
Mesh *me,
const int scene_framenr)
{
if (scene_framenr >= mmd->time) {
BKE_fluid_modifier_init(mmd, depsgraph, ob, scene, me);
}
if (mmd->flow) {
if (mmd->flow->mesh) {
BKE_id_free(NULL, mmd->flow->mesh);
}
mmd->flow->mesh = BKE_mesh_copy_for_eval(me, false);
}
if (scene_framenr > mmd->time) {
mmd->time = scene_framenr;
}
else if (scene_framenr < mmd->time) {
mmd->time = scene_framenr;
BKE_fluid_modifier_reset_ex(mmd, false);
}
}
static void BKE_fluid_modifier_processEffector(FluidModifierData *mmd,
Depsgraph *depsgraph,
Scene *scene,
Object *ob,
Mesh *me,
const int scene_framenr)
{
if (scene_framenr >= mmd->time) {
BKE_fluid_modifier_init(mmd, depsgraph, ob, scene, me);
}
if (mmd->effector) {
if (mmd->effector->mesh) {
BKE_id_free(NULL, mmd->effector->mesh);
}
mmd->effector->mesh = BKE_mesh_copy_for_eval(me, false);
}
if (scene_framenr > mmd->time) {
mmd->time = scene_framenr;
}
else if (scene_framenr < mmd->time) {
mmd->time = scene_framenr;
BKE_fluid_modifier_reset_ex(mmd, false);
}
}
static void BKE_fluid_modifier_processDomain(FluidModifierData *mmd,
Depsgraph *depsgraph,
Scene *scene,
Object *ob,
Mesh *me,
const int scene_framenr)
{
FluidDomainSettings *mds = mmd->domain;
Object *guide_parent = NULL;
Object **objs = NULL;
uint numobj = 0;
FluidModifierData *mmd_parent = NULL;
bool is_startframe;
is_startframe = (scene_framenr == mds->cache_frame_start);
/* Reset fluid if no fluid present. */
if (!mds->fluid) {
BKE_fluid_modifier_reset_ex(mmd, false);
/* Fluid domain init must not fail in order to continue modifier evaluation. */
if (!BKE_fluid_modifier_init(mmd, depsgraph, ob, scene, me)) {
return;
}
}
BLI_assert(mds->fluid);
/* Guiding parent res pointer needs initialization. */
guide_parent = mds->guide_parent;
if (guide_parent) {
mmd_parent = (FluidModifierData *)modifiers_findByType(guide_parent, eModifierType_Fluid);
if (mmd_parent && mmd_parent->domain) {
copy_v3_v3_int(mds->guide_res, mmd_parent->domain->res);
}
}
/* Ensure that time parameters are initialized correctly before every step. */
float fps = scene->r.frs_sec / scene->r.frs_sec_base;
mds->frame_length = DT_DEFAULT * (25.0f / fps) * mds->time_scale;
mds->dt = mds->frame_length;
mds->time_per_frame = 0;
/* Get distance between cache start and current frame for total time. */
mds->time_total = abs(scene_framenr - mds->cache_frame_start) * mds->frame_length;
objs = BKE_collision_objects_create(
depsgraph, ob, mds->fluid_group, &numobj, eModifierType_Fluid);
update_flowsflags(mds, objs, numobj);
if (objs) {
MEM_freeN(objs);
}
objs = BKE_collision_objects_create(
depsgraph, ob, mds->effector_group, &numobj, eModifierType_Fluid);
update_obstacleflags(mds, objs, numobj);
if (objs) {
MEM_freeN(objs);
}
/* Ensure cache directory is not relative. */
const char *relbase = modifier_path_relbase_from_global(ob);
BLI_path_abs(mds->cache_directory, relbase);
int data_frame = scene_framenr, noise_frame = scene_framenr;
int mesh_frame = scene_framenr, particles_frame = scene_framenr, guide_frame = scene_framenr;
bool with_smoke, with_liquid;
with_smoke = mds->type == FLUID_DOMAIN_TYPE_GAS;
with_liquid = mds->type == FLUID_DOMAIN_TYPE_LIQUID;
bool drops, bubble, floater;
drops = mds->particle_type & FLUID_DOMAIN_PARTICLE_SPRAY;
bubble = mds->particle_type & FLUID_DOMAIN_PARTICLE_BUBBLE;
floater = mds->particle_type & FLUID_DOMAIN_PARTICLE_FOAM;
bool with_script, with_adaptive, with_noise, with_mesh, with_particles, with_guide;
with_script = mds->flags & FLUID_DOMAIN_EXPORT_MANTA_SCRIPT;
with_adaptive = mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN;
with_noise = mds->flags & FLUID_DOMAIN_USE_NOISE;
with_mesh = mds->flags & FLUID_DOMAIN_USE_MESH;
with_guide = mds->flags & FLUID_DOMAIN_USE_GUIDE;
with_particles = drops || bubble || floater;
bool has_data, has_noise, has_mesh, has_particles, has_guide;
has_data = has_noise = has_mesh = has_particles = has_guide = false;
bool baking_data, baking_noise, baking_mesh, baking_particles, baking_guide, bake_outdated;
baking_data = mds->cache_flag & FLUID_DOMAIN_BAKING_DATA;
baking_noise = mds->cache_flag & FLUID_DOMAIN_BAKING_NOISE;
baking_mesh = mds->cache_flag & FLUID_DOMAIN_BAKING_MESH;
baking_particles = mds->cache_flag & FLUID_DOMAIN_BAKING_PARTICLES;
baking_guide = mds->cache_flag & FLUID_DOMAIN_BAKING_GUIDE;
bake_outdated = mds->cache_flag & (FLUID_DOMAIN_OUTDATED_DATA | FLUID_DOMAIN_OUTDATED_NOISE |
FLUID_DOMAIN_OUTDATED_MESH | FLUID_DOMAIN_OUTDATED_PARTICLES |
FLUID_DOMAIN_OUTDATED_GUIDE);
bool resume_data, resume_noise, resume_mesh, resume_particles, resume_guide;
resume_data = (!is_startframe) && (mds->cache_frame_pause_data == scene_framenr);
resume_noise = (!is_startframe) && (mds->cache_frame_pause_noise == scene_framenr);
resume_mesh = (!is_startframe) && (mds->cache_frame_pause_mesh == scene_framenr);
resume_particles = (!is_startframe) && (mds->cache_frame_pause_particles == scene_framenr);
resume_guide = (!is_startframe) && (mds->cache_frame_pause_guide == scene_framenr);
bool read_cache, bake_cache;
read_cache = false;
bake_cache = baking_data || baking_noise || baking_mesh || baking_particles || baking_guide;
bool with_gdomain;
with_gdomain = (mds->guide_source == FLUID_DOMAIN_GUIDE_SRC_DOMAIN);
int o_res[3], o_min[3], o_max[3], o_shift[3];
int mode = mds->cache_type;
int prev_frame = scene_framenr - 1;
/* Ensure positivity of previous frame. */
CLAMP(prev_frame, 1, prev_frame);
/* Cache mode specific settings. */
switch (mode) {
case FLUID_DOMAIN_CACHE_FINAL:
/* Just load the data that has already been baked */
if (!baking_data && !baking_noise && !baking_mesh && !baking_particles && !baking_guide) {
read_cache = true;
bake_cache = false;
}
break;
case FLUID_DOMAIN_CACHE_MODULAR:
/* Just load the data that has already been baked */
if (!baking_data && !baking_noise && !baking_mesh && !baking_particles && !baking_guide) {
read_cache = true;
bake_cache = false;
break;
}
/* Set to previous frame if the bake was resumed
* ie don't read all of the already baked frames, just the one before bake resumes */
if (baking_data && resume_data) {
data_frame = prev_frame;
}
if (baking_noise && resume_noise) {
noise_frame = prev_frame;
}
if (baking_mesh && resume_mesh) {
mesh_frame = prev_frame;
}
if (baking_particles && resume_particles) {
particles_frame = prev_frame;
}
if (baking_guide && resume_guide) {
guide_frame = prev_frame;
}
/* Noise, mesh and particles can never be baked more than data. */
CLAMP(noise_frame, noise_frame, data_frame);
CLAMP(mesh_frame, mesh_frame, data_frame);
CLAMP(particles_frame, particles_frame, data_frame);
CLAMP(guide_frame, guide_frame, mds->cache_frame_end);
/* Force to read cache as we're resuming the bake */
read_cache = true;
break;
case FLUID_DOMAIN_CACHE_REPLAY:
default:
/* Always trying to read the cache in replay mode. */
read_cache = true;
break;
}
/* Cache outdated? If so reset, don't read, and then just rebake.
* Note: Only do this in replay mode! */
bool mode_replay = (mode == FLUID_DOMAIN_CACHE_REPLAY);
if (bake_outdated && mode_replay) {
read_cache = false;
bake_cache = true;
BKE_fluid_cache_free(mds, ob, mds->cache_flag);
}
/* Try to read from cache and keep track of read success. */
if (read_cache) {
/* Read mesh cache. */
if (with_liquid && with_mesh) {
/* Update mesh data from file is faster than via Python (manta_read_mesh()). */
has_mesh = manta_update_mesh_structures(mds->fluid, mmd, mesh_frame);
}
/* Read particles cache. */
if (with_liquid && with_particles) {
if (!baking_data && !baking_particles && !mode_replay) {
/* Update particle data from file is faster than via Python (manta_read_particles()). */
has_particles = manta_update_particle_structures(mds->fluid, mmd, particles_frame);
}
else {
has_particles = manta_read_particles(mds->fluid, mmd, particles_frame);
}
}
/* Read guide cache. */
if (with_guide) {
FluidModifierData *mmd2 = (with_gdomain) ? mmd_parent : mmd;
has_guide = manta_read_guiding(mds->fluid, mmd2, scene_framenr, with_gdomain);
}
/* Read noise and data cache */
if (with_smoke && with_noise) {
/* Only reallocate when just reading cache or when resuming during bake. */
if ((!baking_noise || (baking_noise && resume_noise)) &&
manta_read_config(mds->fluid, mmd, noise_frame) &&
manta_needs_realloc(mds->fluid, mmd)) {
BKE_fluid_reallocate_fluid(mds, mds->res, 1);
}
if (!baking_data && !baking_noise && !mode_replay) {
has_data = manta_update_noise_structures(mds->fluid, mmd, noise_frame);
}
else {
has_noise = manta_read_noise(mds->fluid, mmd, noise_frame);
}
/* When using the adaptive domain, copy all data that was read to a new fluid object. */
if (with_adaptive && baking_noise) {
/* Adaptive domain needs to know about current state, so save it, then copy. */
copy_v3_v3_int(o_res, mds->res);
copy_v3_v3_int(o_min, mds->res_min);
copy_v3_v3_int(o_max, mds->res_max);
copy_v3_v3_int(o_shift, mds->shift);
if (manta_read_config(mds->fluid, mmd, data_frame) &&
manta_needs_realloc(mds->fluid, mmd)) {
BKE_fluid_reallocate_copy_fluid(
mds, o_res, mds->res, o_min, mds->res_min, o_max, o_shift, mds->shift);
}
}
if (!baking_data && !baking_noise && !mode_replay) {
/* TODO (sebbas): Confirm if this read call is really needed or not. */
has_data = manta_update_smoke_structures(mds->fluid, mmd, data_frame);
}
else {
has_data = manta_read_data(mds->fluid, mmd, data_frame);
}
}
/* Read data cache only */
else {
if (with_smoke) {
/* Read config and realloc fluid object if needed. */
if (manta_read_config(mds->fluid, mmd, data_frame) &&
manta_needs_realloc(mds->fluid, mmd)) {
BKE_fluid_reallocate_fluid(mds, mds->res, 1);
}
/* Read data cache */
if (!baking_data && !baking_particles && !baking_mesh && !mode_replay) {
has_data = manta_update_smoke_structures(mds->fluid, mmd, data_frame);
}
else {
has_data = manta_read_data(mds->fluid, mmd, data_frame);
}
}
if (with_liquid) {
if (!baking_data && !baking_particles && !baking_mesh && !mode_replay) {
has_data = manta_update_liquid_structures(mds->fluid, mmd, data_frame);
}
else {
has_data = manta_read_data(mds->fluid, mmd, data_frame);
}
}
}
}
/* Cache mode specific settings */
switch (mode) {
case FLUID_DOMAIN_CACHE_FINAL:
case FLUID_DOMAIN_CACHE_MODULAR:
break;
case FLUID_DOMAIN_CACHE_REPLAY:
default:
baking_data = !has_data;
if (with_smoke && with_noise) {
baking_noise = !has_noise;
}
if (with_liquid && with_mesh) {
baking_mesh = !has_mesh;
}
if (with_liquid && with_particles) {
baking_particles = !has_particles;
}
bake_cache = baking_data || baking_noise || baking_mesh || baking_particles;
break;
}
/* Trigger bake calls individually */
if (bake_cache) {
/* Ensure fresh variables at every animation step */
manta_update_variables(mds->fluid, mmd);
/* Export mantaflow python script on first frame (once only) and for any bake type */
if (with_script && is_startframe) {
if (with_smoke) {
manta_smoke_export_script(mmd->domain->fluid, mmd);
}
if (with_liquid) {
manta_liquid_export_script(mmd->domain->fluid, mmd);
}
}
if (baking_guide && with_guide) {
manta_guiding(depsgraph, scene, ob, mmd, scene_framenr);
}
if (baking_data) {
/* Only save baked data if all of it completed successfully. */
if (manta_step(depsgraph, scene, ob, me, mmd, scene_framenr)) {
manta_write_config(mds->fluid, mmd, scene_framenr);
manta_write_data(mds->fluid, mmd, scene_framenr);
}
}
if (has_data || baking_data) {
if (baking_noise && with_smoke && with_noise) {
manta_bake_noise(mds->fluid, mmd, scene_framenr);
}
if (baking_mesh && with_liquid && with_mesh) {
manta_bake_mesh(mds->fluid, mmd, scene_framenr);
}
if (baking_particles && with_liquid && with_particles) {
manta_bake_particles(mds->fluid, mmd, scene_framenr);
}
}
}
mmd->time = scene_framenr;
}
static void BKE_fluid_modifier_process(
FluidModifierData *mmd, Depsgraph *depsgraph, Scene *scene, Object *ob, Mesh *me)
{
const int scene_framenr = (int)DEG_get_ctime(depsgraph);
if ((mmd->type & MOD_FLUID_TYPE_FLOW)) {
BKE_fluid_modifier_processFlow(mmd, depsgraph, scene, ob, me, scene_framenr);
}
else if (mmd->type & MOD_FLUID_TYPE_EFFEC) {
BKE_fluid_modifier_processEffector(mmd, depsgraph, scene, ob, me, scene_framenr);
}
else if (mmd->type & MOD_FLUID_TYPE_DOMAIN) {
BKE_fluid_modifier_processDomain(mmd, depsgraph, scene, ob, me, scene_framenr);
}
}
struct Mesh *BKE_fluid_modifier_do(
FluidModifierData *mmd, Depsgraph *depsgraph, Scene *scene, Object *ob, Mesh *me)
{
/* Lock so preview render does not read smoke data while it gets modified. */
if ((mmd->type & MOD_FLUID_TYPE_DOMAIN) && mmd->domain) {
BLI_rw_mutex_lock(mmd->domain->fluid_mutex, THREAD_LOCK_WRITE);
}
BKE_fluid_modifier_process(mmd, depsgraph, scene, ob, me);
if ((mmd->type & MOD_FLUID_TYPE_DOMAIN) && mmd->domain) {
BLI_rw_mutex_unlock(mmd->domain->fluid_mutex);
}
/* Optimization: Do not update viewport during bakes (except in replay mode)
* Reason: UI is locked and updated liquid / smoke geometry is not visible anyways. */
bool needs_viewport_update = false;
if (mmd->domain) {
FluidDomainSettings *mds = mmd->domain;
/* Always update viewport in cache replay mode. */
if (mds->cache_type == FLUID_DOMAIN_CACHE_REPLAY ||
mds->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
needs_viewport_update = true;
}
/* In other cache modes, only update the viewport when no bake is going on. */
else {
bool with_mesh;
with_mesh = mds->flags & FLUID_DOMAIN_USE_MESH;
bool baking_data, baking_noise, baking_mesh, baking_particles, baking_guide;
baking_data = mds->cache_flag & FLUID_DOMAIN_BAKING_DATA;
baking_noise = mds->cache_flag & FLUID_DOMAIN_BAKING_NOISE;
baking_mesh = mds->cache_flag & FLUID_DOMAIN_BAKING_MESH;
baking_particles = mds->cache_flag & FLUID_DOMAIN_BAKING_PARTICLES;
baking_guide = mds->cache_flag & FLUID_DOMAIN_BAKING_GUIDE;
if (with_mesh && !baking_data && !baking_noise && !baking_mesh && !baking_particles &&
!baking_guide) {
needs_viewport_update = true;
}
}
}
Mesh *result = NULL;
if (mmd->type & MOD_FLUID_TYPE_DOMAIN && mmd->domain) {
if (needs_viewport_update) {
/* Return generated geometry depending on domain type. */
if (mmd->domain->type == FLUID_DOMAIN_TYPE_LIQUID) {
result = create_liquid_geometry(mmd->domain, me, ob);
}
if (mmd->domain->type == FLUID_DOMAIN_TYPE_GAS) {
result = create_smoke_geometry(mmd->domain, me, ob);
}
}
/* Clear flag outside of locked block (above). */
mmd->domain->cache_flag &= ~FLUID_DOMAIN_OUTDATED_DATA;
mmd->domain->cache_flag &= ~FLUID_DOMAIN_OUTDATED_NOISE;
mmd->domain->cache_flag &= ~FLUID_DOMAIN_OUTDATED_MESH;
mmd->domain->cache_flag &= ~FLUID_DOMAIN_OUTDATED_PARTICLES;
mmd->domain->cache_flag &= ~FLUID_DOMAIN_OUTDATED_GUIDE;
}
if (!result) {
result = BKE_mesh_copy_for_eval(me, false);
}
else {
BKE_mesh_copy_settings(result, me);
}
/* Liquid simulation has a texture space that based on the bounds of the fluid mesh.
* This does not seem particularly useful, but it's backwards compatible.
*
* Smoke simulation needs a texture space relative to the adaptive domain bounds, not the
* original mesh. So recompute it at this point in the modifier stack. See T58492. */
BKE_mesh_texspace_calc(result);
return result;
}
static float calc_voxel_transp(
float *result, float *input, int res[3], int *pixel, float *t_ray, float correct)
{
const size_t index = manta_get_index(pixel[0], res[0], pixel[1], res[1], pixel[2]);
// T_ray *= T_vox
*t_ray *= expf(input[index] * correct);
if (result[index] < 0.0f) {
result[index] = *t_ray;
}
return *t_ray;
}
static void bresenham_linie_3D(int x1,
int y1,
int z1,
int x2,
int y2,
int z2,
float *t_ray,
BKE_Fluid_BresenhamFn cb,
float *result,
float *input,
int res[3],
float correct)
{
int dx, dy, dz, i, l, m, n, x_inc, y_inc, z_inc, err_1, err_2, dx2, dy2, dz2;
int pixel[3];
pixel[0] = x1;
pixel[1] = y1;
pixel[2] = z1;
dx = x2 - x1;
dy = y2 - y1;
dz = z2 - z1;
x_inc = (dx < 0) ? -1 : 1;
l = abs(dx);
y_inc = (dy < 0) ? -1 : 1;
m = abs(dy);
z_inc = (dz < 0) ? -1 : 1;
n = abs(dz);
dx2 = l << 1;
dy2 = m << 1;
dz2 = n << 1;
if ((l >= m) && (l >= n)) {
err_1 = dy2 - l;
err_2 = dz2 - l;
for (i = 0; i < l; i++) {
if (cb(result, input, res, pixel, t_ray, correct) <= FLT_EPSILON) {
break;
}
if (err_1 > 0) {
pixel[1] += y_inc;
err_1 -= dx2;
}
if (err_2 > 0) {
pixel[2] += z_inc;
err_2 -= dx2;
}
err_1 += dy2;
err_2 += dz2;
pixel[0] += x_inc;
}
}
else if ((m >= l) && (m >= n)) {
err_1 = dx2 - m;
err_2 = dz2 - m;
for (i = 0; i < m; i++) {
if (cb(result, input, res, pixel, t_ray, correct) <= FLT_EPSILON) {
break;
}
if (err_1 > 0) {
pixel[0] += x_inc;
err_1 -= dy2;
}
if (err_2 > 0) {
pixel[2] += z_inc;
err_2 -= dy2;
}
err_1 += dx2;
err_2 += dz2;
pixel[1] += y_inc;
}
}
else {
err_1 = dy2 - n;
err_2 = dx2 - n;
for (i = 0; i < n; i++) {
if (cb(result, input, res, pixel, t_ray, correct) <= FLT_EPSILON) {
break;
}
if (err_1 > 0) {
pixel[1] += y_inc;
err_1 -= dz2;
}
if (err_2 > 0) {
pixel[0] += x_inc;
err_2 -= dz2;
}
err_1 += dy2;
err_2 += dx2;
pixel[2] += z_inc;
}
}
cb(result, input, res, pixel, t_ray, correct);
}
static void manta_smoke_calc_transparency(FluidDomainSettings *mds, ViewLayer *view_layer)
{
float bv[6] = {0};
float light[3];
int a, z, slabsize = mds->res[0] * mds->res[1], size = mds->res[0] * mds->res[1] * mds->res[2];
float *density = manta_smoke_get_density(mds->fluid);
float *shadow = manta_smoke_get_shadow(mds->fluid);
float correct = -7.0f * mds->dx;
if (!get_light(view_layer, light)) {
return;
}
/* convert light pos to sim cell space */
mul_m4_v3(mds->imat, light);
light[0] = (light[0] - mds->p0[0]) / mds->cell_size[0] - 0.5f - (float)mds->res_min[0];
light[1] = (light[1] - mds->p0[1]) / mds->cell_size[1] - 0.5f - (float)mds->res_min[1];
light[2] = (light[2] - mds->p0[2]) / mds->cell_size[2] - 0.5f - (float)mds->res_min[2];
for (a = 0; a < size; a++) {
shadow[a] = -1.0f;
}
/* calculate domain bounds in sim cell space */
// 0,2,4 = 0.0f
bv[1] = (float)mds->res[0]; // x
bv[3] = (float)mds->res[1]; // y
bv[5] = (float)mds->res[2]; // z
for (z = 0; z < mds->res[2]; z++) {
size_t index = z * slabsize;
int x, y;
for (y = 0; y < mds->res[1]; y++) {
for (x = 0; x < mds->res[0]; x++, index++) {
float voxel_center[3];
float pos[3];
int cell[3];
float t_ray = 1.0;
if (shadow[index] >= 0.0f) {
continue;
}
voxel_center[0] = (float)x;
voxel_center[1] = (float)y;
voxel_center[2] = (float)z;
// get starting cell (light pos)
if (BLI_bvhtree_bb_raycast(bv, light, voxel_center, pos) > FLT_EPSILON) {
// we're outside -> use point on side of domain
cell[0] = (int)floor(pos[0]);
cell[1] = (int)floor(pos[1]);
cell[2] = (int)floor(pos[2]);
}
else {
// we're inside -> use light itself
cell[0] = (int)floor(light[0]);
cell[1] = (int)floor(light[1]);
cell[2] = (int)floor(light[2]);
}
/* clamp within grid bounds */
CLAMP(cell[0], 0, mds->res[0] - 1);
CLAMP(cell[1], 0, mds->res[1] - 1);
CLAMP(cell[2], 0, mds->res[2] - 1);
bresenham_linie_3D(cell[0],
cell[1],
cell[2],
x,
y,
z,
&t_ray,
calc_voxel_transp,
shadow,
density,
mds->res,
correct);
// convention -> from a RGBA float array, use G value for t_ray
shadow[index] = t_ray;
}
}
}
}
/* get smoke velocity and density at given coordinates
* returns fluid density or -1.0f if outside domain. */
float BKE_fluid_get_velocity_at(struct Object *ob, float position[3], float velocity[3])
{
FluidModifierData *mmd = (FluidModifierData *)modifiers_findByType(ob, eModifierType_Fluid);
zero_v3(velocity);
if (mmd && (mmd->type & MOD_FLUID_TYPE_DOMAIN) && mmd->domain && mmd->domain->fluid) {
FluidDomainSettings *mds = mmd->domain;
float time_mult = 25.f * DT_DEFAULT;
float vel_mag;
float *velX = manta_get_velocity_x(mds->fluid);
float *velY = manta_get_velocity_y(mds->fluid);
float *velZ = manta_get_velocity_z(mds->fluid);
float density = 0.0f, fuel = 0.0f;
float pos[3];
copy_v3_v3(pos, position);
manta_pos_to_cell(mds, pos);
/* check if point is outside domain max bounds */
if (pos[0] < mds->res_min[0] || pos[1] < mds->res_min[1] || pos[2] < mds->res_min[2]) {
return -1.0f;
}
if (pos[0] > mds->res_max[0] || pos[1] > mds->res_max[1] || pos[2] > mds->res_max[2]) {
return -1.0f;
}
/* map pos between 0.0 - 1.0 */
pos[0] = (pos[0] - mds->res_min[0]) / ((float)mds->res[0]);
pos[1] = (pos[1] - mds->res_min[1]) / ((float)mds->res[1]);
pos[2] = (pos[2] - mds->res_min[2]) / ((float)mds->res[2]);
/* check if point is outside active area */
if (mmd->domain->type == FLUID_DOMAIN_TYPE_GAS &&
mmd->domain->flags & FLUID_DOMAIN_USE_ADAPTIVE_DOMAIN) {
if (pos[0] < 0.0f || pos[1] < 0.0f || pos[2] < 0.0f) {
return 0.0f;
}
if (pos[0] > 1.0f || pos[1] > 1.0f || pos[2] > 1.0f) {
return 0.0f;
}
}
/* get interpolated velocity */
velocity[0] = BLI_voxel_sample_trilinear(velX, mds->res, pos) * mds->global_size[0] *
time_mult;
velocity[1] = BLI_voxel_sample_trilinear(velY, mds->res, pos) * mds->global_size[1] *
time_mult;
velocity[2] = BLI_voxel_sample_trilinear(velZ, mds->res, pos) * mds->global_size[2] *
time_mult;
/* convert velocity direction to global space */
vel_mag = len_v3(velocity);
mul_mat3_m4_v3(mds->obmat, velocity);
normalize_v3(velocity);
mul_v3_fl(velocity, vel_mag);
/* use max value of fuel or smoke density */
density = BLI_voxel_sample_trilinear(manta_smoke_get_density(mds->fluid), mds->res, pos);
if (manta_smoke_has_fuel(mds->fluid)) {
fuel = BLI_voxel_sample_trilinear(manta_smoke_get_fuel(mds->fluid), mds->res, pos);
}
return MAX2(density, fuel);
}
return -1.0f;
}
int BKE_fluid_get_data_flags(FluidDomainSettings *mds)
{
int flags = 0;
if (mds->fluid) {
if (manta_smoke_has_heat(mds->fluid)) {
flags |= FLUID_DOMAIN_ACTIVE_HEAT;
}
if (manta_smoke_has_fuel(mds->fluid)) {
flags |= FLUID_DOMAIN_ACTIVE_FIRE;
}
if (manta_smoke_has_colors(mds->fluid)) {
flags |= FLUID_DOMAIN_ACTIVE_COLORS;
}
}
return flags;
}
void BKE_fluid_particle_system_create(struct Main *bmain,
struct Object *ob,
const char *pset_name,
const char *parts_name,
const char *psys_name,
const int psys_type)
{
ParticleSystem *psys;
ParticleSettings *part;
ParticleSystemModifierData *pmmd;
/* add particle system */
part = BKE_particlesettings_add(bmain, pset_name);
psys = MEM_callocN(sizeof(ParticleSystem), "particle_system");
part->type = psys_type;
part->totpart = 0;
part->draw_size = 0.01f; /* Make fluid particles more subtle in viewport. */
part->draw_col = PART_DRAW_COL_VEL;
psys->part = part;
psys->pointcache = BKE_ptcache_add(&psys->ptcaches);
BLI_strncpy(psys->name, parts_name, sizeof(psys->name));
BLI_addtail(&ob->particlesystem, psys);
/* add modifier */
pmmd = (ParticleSystemModifierData *)modifier_new(eModifierType_ParticleSystem);
BLI_strncpy(pmmd->modifier.name, psys_name, sizeof(pmmd->modifier.name));
pmmd->psys = psys;
BLI_addtail(&ob->modifiers, pmmd);
modifier_unique_name(&ob->modifiers, (ModifierData *)pmmd);
}
void BKE_fluid_particle_system_destroy(struct Object *ob, const int particle_type)
{
ParticleSystemModifierData *pmmd;
ParticleSystem *psys, *next_psys;
for (psys = ob->particlesystem.first; psys; psys = next_psys) {
next_psys = psys->next;
if (psys->part->type == particle_type) {
/* clear modifier */
pmmd = psys_get_modifier(ob, psys);
BLI_remlink(&ob->modifiers, pmmd);
modifier_free((ModifierData *)pmmd);
/* clear particle system */
BLI_remlink(&ob->particlesystem, psys);
psys_free(ob, psys);
}
}
}
#endif /* WITH_FLUID */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Data Access API
*
* Use for versioning, even when fluids are disabled.
* \{ */
void BKE_fluid_cachetype_mesh_set(FluidDomainSettings *settings, int cache_mesh_format)
{
if (cache_mesh_format == settings->cache_mesh_format) {
return;
}
/* TODO(sebbas): Clear old caches. */
settings->cache_mesh_format = cache_mesh_format;
}
void BKE_fluid_cachetype_data_set(FluidDomainSettings *settings, int cache_data_format)
{
if (cache_data_format == settings->cache_data_format) {
return;
}
/* TODO(sebbas): Clear old caches. */
settings->cache_data_format = cache_data_format;
}
void BKE_fluid_cachetype_particle_set(FluidDomainSettings *settings, int cache_particle_format)
{
if (cache_particle_format == settings->cache_particle_format) {
return;
}
/* TODO(sebbas): Clear old caches. */
settings->cache_particle_format = cache_particle_format;
}
void BKE_fluid_cachetype_noise_set(FluidDomainSettings *settings, int cache_noise_format)
{
if (cache_noise_format == settings->cache_noise_format) {
return;
}
/* TODO(sebbas): Clear old caches. */
settings->cache_noise_format = cache_noise_format;
}
void BKE_fluid_collisionextents_set(FluidDomainSettings *settings, int value, bool clear)
{
if (clear) {
settings->border_collisions &= value;
}
else {
settings->border_collisions |= value;
}
}
void BKE_fluid_particles_set(FluidDomainSettings *settings, int value, bool clear)
{
if (clear) {
settings->particle_type &= ~value;
}
else {
settings->particle_type |= value;
}
}
void BKE_fluid_domain_type_set(Object *object, FluidDomainSettings *settings, int type)
{
/* Set common values for liquid/smoke domain: cache type,
* border collision and viewport draw-type. */
if (type == FLUID_DOMAIN_TYPE_GAS) {
BKE_fluid_cachetype_mesh_set(settings, FLUID_DOMAIN_FILE_BIN_OBJECT);
BKE_fluid_cachetype_data_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_cachetype_particle_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_cachetype_noise_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_FRONT, 1);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_BACK, 1);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_RIGHT, 1);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_LEFT, 1);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_TOP, 1);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_BOTTOM, 1);
object->dt = OB_WIRE;
}
else if (type == FLUID_DOMAIN_TYPE_LIQUID) {
BKE_fluid_cachetype_mesh_set(settings, FLUID_DOMAIN_FILE_BIN_OBJECT);
BKE_fluid_cachetype_data_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_cachetype_particle_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_cachetype_noise_set(settings, FLUID_DOMAIN_FILE_UNI);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_FRONT, 0);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_BACK, 0);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_RIGHT, 0);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_LEFT, 0);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_TOP, 0);
BKE_fluid_collisionextents_set(settings, FLUID_DOMAIN_BORDER_BOTTOM, 0);
object->dt = OB_SOLID;
}
/* Set actual domain type. */
settings->type = type;
}
void BKE_fluid_flow_behavior_set(Object *UNUSED(object), FluidFlowSettings *settings, int behavior)
{
settings->behavior = behavior;
}
void BKE_fluid_flow_type_set(Object *object, FluidFlowSettings *settings, int type)
{
/* By default, liquid flow objects should behave like their geometry (geometry behavior),
* gas flow objects should continuously produce smoke (inflow behavior). */
if (type == FLUID_FLOW_TYPE_LIQUID) {
BKE_fluid_flow_behavior_set(object, settings, FLUID_FLOW_BEHAVIOR_GEOMETRY);
}
else {
BKE_fluid_flow_behavior_set(object, settings, FLUID_FLOW_BEHAVIOR_INFLOW);
}
/* Set actual flow type. */
settings->type = type;
}
void BKE_fluid_effector_type_set(Object *UNUSED(object), FluidEffectorSettings *settings, int type)
{
settings->type = type;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Modifier API
*
* Use for versioning, even when fluids are disabled.
* \{ */
static void BKE_fluid_modifier_freeDomain(FluidModifierData *mmd)
{
if (mmd->domain) {
if (mmd->domain->fluid) {
#ifdef WITH_FLUID
manta_free(mmd->domain->fluid);
#endif
}
if (mmd->domain->fluid_mutex) {
BLI_rw_mutex_free(mmd->domain->fluid_mutex);
}
if (mmd->domain->effector_weights) {
MEM_freeN(mmd->domain->effector_weights);
}
mmd->domain->effector_weights = NULL;
if (!(mmd->modifier.flag & eModifierFlag_SharedCaches)) {
BKE_ptcache_free_list(&(mmd->domain->ptcaches[0]));
mmd->domain->point_cache[0] = NULL;
}
if (mmd->domain->mesh_velocities) {
MEM_freeN(mmd->domain->mesh_velocities);
}
mmd->domain->mesh_velocities = NULL;
if (mmd->domain->coba) {
MEM_freeN(mmd->domain->coba);
}
MEM_freeN(mmd->domain);
mmd->domain = NULL;
}
}
static void BKE_fluid_modifier_freeFlow(FluidModifierData *mmd)
{
if (mmd->flow) {
if (mmd->flow->mesh) {
BKE_id_free(NULL, mmd->flow->mesh);
}
mmd->flow->mesh = NULL;
if (mmd->flow->verts_old) {
MEM_freeN(mmd->flow->verts_old);
}
mmd->flow->verts_old = NULL;
mmd->flow->numverts = 0;
MEM_freeN(mmd->flow);
mmd->flow = NULL;
}
}
static void BKE_fluid_modifier_freeEffector(FluidModifierData *mmd)
{
if (mmd->effector) {
if (mmd->effector->mesh) {
BKE_id_free(NULL, mmd->effector->mesh);
}
mmd->effector->mesh = NULL;
if (mmd->effector->verts_old) {
MEM_freeN(mmd->effector->verts_old);
}
mmd->effector->verts_old = NULL;
mmd->effector->numverts = 0;
MEM_freeN(mmd->effector);
mmd->effector = NULL;
}
}
static void BKE_fluid_modifier_reset_ex(struct FluidModifierData *mmd, bool need_lock)
{
if (!mmd) {
return;
}
if (mmd->domain) {
if (mmd->domain->fluid) {
if (need_lock) {
BLI_rw_mutex_lock(mmd->domain->fluid_mutex, THREAD_LOCK_WRITE);
}
#ifdef WITH_FLUID
manta_free(mmd->domain->fluid);
#endif
mmd->domain->fluid = NULL;
if (need_lock) {
BLI_rw_mutex_unlock(mmd->domain->fluid_mutex);
}
}
mmd->time = -1;
mmd->domain->total_cells = 0;
mmd->domain->active_fields = 0;
}
else if (mmd->flow) {
if (mmd->flow->verts_old) {
MEM_freeN(mmd->flow->verts_old);
}
mmd->flow->verts_old = NULL;
mmd->flow->numverts = 0;
}
else if (mmd->effector) {
if (mmd->effector->verts_old) {
MEM_freeN(mmd->effector->verts_old);
}
mmd->effector->verts_old = NULL;
mmd->effector->numverts = 0;
}
}
void BKE_fluid_modifier_reset(struct FluidModifierData *mmd)
{
BKE_fluid_modifier_reset_ex(mmd, true);
}
void BKE_fluid_modifier_free(FluidModifierData *mmd)
{
if (!mmd) {
return;
}
BKE_fluid_modifier_freeDomain(mmd);
BKE_fluid_modifier_freeFlow(mmd);
BKE_fluid_modifier_freeEffector(mmd);
}
void BKE_fluid_modifier_create_type_data(struct FluidModifierData *mmd)
{
if (!mmd) {
return;
}
if (mmd->type & MOD_FLUID_TYPE_DOMAIN) {
if (mmd->domain) {
BKE_fluid_modifier_freeDomain(mmd);
}
/* domain object data */
mmd->domain = MEM_callocN(sizeof(FluidDomainSettings), "FluidDomain");
mmd->domain->mmd = mmd;
mmd->domain->effector_weights = BKE_effector_add_weights(NULL);
mmd->domain->fluid = NULL;
mmd->domain->fluid_mutex = BLI_rw_mutex_alloc();
mmd->domain->force_group = NULL;
mmd->domain->fluid_group = NULL;
mmd->domain->effector_group = NULL;
/* adaptive domain options */
mmd->domain->adapt_margin = 4;
mmd->domain->adapt_res = 0;
mmd->domain->adapt_threshold = 0.02f;
/* fluid domain options */
mmd->domain->maxres = 64;
mmd->domain->solver_res = 3;
mmd->domain->border_collisions = 0; // open domain
mmd->domain->flags = FLUID_DOMAIN_USE_DISSOLVE_LOG | FLUID_DOMAIN_USE_ADAPTIVE_TIME;
mmd->domain->gravity[0] = 0.0f;
mmd->domain->gravity[1] = 0.0f;
mmd->domain->gravity[2] = -1.0f;
mmd->domain->active_fields = 0;
mmd->domain->type = FLUID_DOMAIN_TYPE_GAS;
mmd->domain->boundary_width = 1;
/* smoke domain options */
mmd->domain->alpha = 1.0f;
mmd->domain->beta = 1.0f;
mmd->domain->diss_speed = 5;
mmd->domain->vorticity = 0;
mmd->domain->active_color[0] = 0.0f;
mmd->domain->active_color[1] = 0.0f;
mmd->domain->active_color[2] = 0.0f;
mmd->domain->highres_sampling = SM_HRES_FULLSAMPLE;
/* flame options */
mmd->domain->burning_rate = 0.75f;
mmd->domain->flame_smoke = 1.0f;
mmd->domain->flame_vorticity = 0.5f;
mmd->domain->flame_ignition = 1.5f;
mmd->domain->flame_max_temp = 3.0f;
mmd->domain->flame_smoke_color[0] = 0.7f;
mmd->domain->flame_smoke_color[1] = 0.7f;
mmd->domain->flame_smoke_color[2] = 0.7f;
/* noise options */
mmd->domain->noise_strength = 1.0;
mmd->domain->noise_pos_scale = 2.0f;
mmd->domain->noise_time_anim = 0.1f;
mmd->domain->noise_scale = 2;
mmd->domain->noise_type = FLUID_NOISE_TYPE_WAVELET;
/* liquid domain options */
mmd->domain->simulation_method = FLUID_DOMAIN_METHOD_FLIP;
mmd->domain->flip_ratio = 0.97f;
mmd->domain->particle_randomness = 0.1f;
mmd->domain->particle_number = 2;
mmd->domain->particle_minimum = 8;
mmd->domain->particle_maximum = 16;
mmd->domain->particle_radius = 1.0f;
mmd->domain->particle_band_width = 3.0f;
mmd->domain->fractions_threshold = 0.05f;
/* diffusion options*/
mmd->domain->surface_tension = 0.0f;
mmd->domain->viscosity_base = 1.0f;
mmd->domain->viscosity_exponent = 6.0f;
mmd->domain->domain_size = 0.5f;
/* mesh options */
mmd->domain->mesh_velocities = NULL;
mmd->domain->mesh_concave_upper = 3.5f;
mmd->domain->mesh_concave_lower = 0.4f;
mmd->domain->mesh_particle_radius = 2.0;
mmd->domain->mesh_smoothen_pos = 1;
mmd->domain->mesh_smoothen_neg = 1;
mmd->domain->mesh_scale = 2;
mmd->domain->totvert = 0;
mmd->domain->mesh_generator = FLUID_DOMAIN_MESH_IMPROVED;
/* secondary particle options */
mmd->domain->sndparticle_tau_min_wc = 2.0;
mmd->domain->sndparticle_tau_max_wc = 8.0;
mmd->domain->sndparticle_tau_min_ta = 5.0;
mmd->domain->sndparticle_tau_max_ta = 20.0;
mmd->domain->sndparticle_tau_min_k = 1.0;
mmd->domain->sndparticle_tau_max_k = 5.0;
mmd->domain->sndparticle_k_wc = 200;
mmd->domain->sndparticle_k_ta = 40;
mmd->domain->sndparticle_k_b = 0.5;
mmd->domain->sndparticle_k_d = 0.6;
mmd->domain->sndparticle_l_min = 10.0;
mmd->domain->sndparticle_l_max = 25.0;
mmd->domain->sndparticle_boundary = SNDPARTICLE_BOUNDARY_DELETE;
mmd->domain->sndparticle_combined_export = SNDPARTICLE_COMBINED_EXPORT_OFF;
mmd->domain->sndparticle_potential_radius = 2;
mmd->domain->sndparticle_update_radius = 2;
mmd->domain->particle_type = 0;
mmd->domain->particle_scale = 1;
/* fluid guide options */
mmd->domain->guide_parent = NULL;
mmd->domain->guide_alpha = 2.0f;
mmd->domain->guide_beta = 5;
mmd->domain->guide_vel_factor = 2.0f;
mmd->domain->guide_source = FLUID_DOMAIN_GUIDE_SRC_DOMAIN;
/* cache options */
mmd->domain->cache_frame_start = 1;
mmd->domain->cache_frame_end = 50;
mmd->domain->cache_frame_pause_data = 0;
mmd->domain->cache_frame_pause_noise = 0;
mmd->domain->cache_frame_pause_mesh = 0;
mmd->domain->cache_frame_pause_particles = 0;
mmd->domain->cache_frame_pause_guide = 0;
mmd->domain->cache_flag = 0;
mmd->domain->cache_type = FLUID_DOMAIN_CACHE_MODULAR;
mmd->domain->cache_mesh_format = FLUID_DOMAIN_FILE_BIN_OBJECT;
#ifdef WITH_OPENVDB
mmd->domain->cache_data_format = FLUID_DOMAIN_FILE_OPENVDB;
mmd->domain->cache_particle_format = FLUID_DOMAIN_FILE_OPENVDB;
mmd->domain->cache_noise_format = FLUID_DOMAIN_FILE_OPENVDB;
#else
mmd->domain->cache_data_format = FLUID_DOMAIN_FILE_UNI;
mmd->domain->cache_particle_format = FLUID_DOMAIN_FILE_UNI;
mmd->domain->cache_noise_format = FLUID_DOMAIN_FILE_UNI;
#endif
char cache_name[64];
BKE_fluid_cache_new_name_for_current_session(sizeof(cache_name), cache_name);
modifier_path_init(
mmd->domain->cache_directory, sizeof(mmd->domain->cache_directory), cache_name);
/* time options */
mmd->domain->time_scale = 1.0;
mmd->domain->cfl_condition = 4.0;
mmd->domain->timesteps_minimum = 1;
mmd->domain->timesteps_maximum = 4;
/* display options */
mmd->domain->slice_method = FLUID_DOMAIN_SLICE_VIEW_ALIGNED;
mmd->domain->axis_slice_method = AXIS_SLICE_FULL;
mmd->domain->slice_axis = 0;
mmd->domain->interp_method = 0;
mmd->domain->draw_velocity = false;
mmd->domain->slice_per_voxel = 5.0f;
mmd->domain->slice_depth = 0.5f;
mmd->domain->display_thickness = 1.0f;
mmd->domain->coba = NULL;
mmd->domain->vector_scale = 1.0f;
mmd->domain->vector_draw_type = VECTOR_DRAW_NEEDLE;
mmd->domain->use_coba = false;
mmd->domain->coba_field = FLUID_DOMAIN_FIELD_DENSITY;
/* -- Deprecated / unsed options (below)-- */
/* pointcache options */
BLI_listbase_clear(&mmd->domain->ptcaches[1]);
mmd->domain->point_cache[0] = BKE_ptcache_add(&(mmd->domain->ptcaches[0]));
mmd->domain->point_cache[0]->flag |= PTCACHE_DISK_CACHE;
mmd->domain->point_cache[0]->step = 1;
mmd->domain->point_cache[1] = NULL; /* Deprecated */
mmd->domain->cache_comp = SM_CACHE_LIGHT;
mmd->domain->cache_high_comp = SM_CACHE_LIGHT;
/* OpenVDB cache options */
#ifdef WITH_OPENVDB_BLOSC
mmd->domain->openvdb_comp = VDB_COMPRESSION_BLOSC;
#else
mmd->domain->openvdb_comp = VDB_COMPRESSION_ZIP;
#endif
mmd->domain->clipping = 1e-6f;
mmd->domain->data_depth = 0;
}
else if (mmd->type & MOD_FLUID_TYPE_FLOW) {
if (mmd->flow) {
BKE_fluid_modifier_freeFlow(mmd);
}
/* flow object data */
mmd->flow = MEM_callocN(sizeof(FluidFlowSettings), "MantaFlow");
mmd->flow->mmd = mmd;
mmd->flow->mesh = NULL;
mmd->flow->psys = NULL;
mmd->flow->noise_texture = NULL;
/* initial velocity */
mmd->flow->verts_old = NULL;
mmd->flow->numverts = 0;
mmd->flow->vel_multi = 1.0f;
mmd->flow->vel_normal = 0.0f;
mmd->flow->vel_random = 0.0f;
mmd->flow->vel_coord[0] = 0.0f;
mmd->flow->vel_coord[1] = 0.0f;
mmd->flow->vel_coord[2] = 0.0f;
/* emission */
mmd->flow->density = 1.0f;
mmd->flow->color[0] = 0.7f;
mmd->flow->color[1] = 0.7f;
mmd->flow->color[2] = 0.7f;
mmd->flow->fuel_amount = 1.0f;
mmd->flow->temperature = 1.0f;
mmd->flow->volume_density = 0.0f;
mmd->flow->surface_distance = 1.5f;
mmd->flow->particle_size = 1.0f;
mmd->flow->subframes = 0;
/* texture control */
mmd->flow->source = FLUID_FLOW_SOURCE_MESH;
mmd->flow->texture_size = 1.0f;
mmd->flow->type = FLUID_FLOW_TYPE_SMOKE;
mmd->flow->behavior = FLUID_FLOW_BEHAVIOR_GEOMETRY;
mmd->flow->flags = FLUID_FLOW_ABSOLUTE | FLUID_FLOW_USE_PART_SIZE | FLUID_FLOW_USE_INFLOW;
}
else if (mmd->type & MOD_FLUID_TYPE_EFFEC) {
if (mmd->effector) {
BKE_fluid_modifier_freeEffector(mmd);
}
/* effector object data */
mmd->effector = MEM_callocN(sizeof(FluidEffectorSettings), "MantaEffector");
mmd->effector->mmd = mmd;
mmd->effector->mesh = NULL;
mmd->effector->verts_old = NULL;
mmd->effector->numverts = 0;
mmd->effector->surface_distance = 0.0f;
mmd->effector->type = FLUID_EFFECTOR_TYPE_COLLISION;
mmd->effector->flags = FLUID_EFFECTOR_USE_EFFEC;
/* guide options */
mmd->effector->guide_mode = FLUID_EFFECTOR_GUIDE_OVERRIDE;
mmd->effector->vel_multi = 1.0f;
}
}
void BKE_fluid_modifier_copy(const struct FluidModifierData *mmd,
struct FluidModifierData *tmmd,
const int flag)
{
tmmd->type = mmd->type;
tmmd->time = mmd->time;
BKE_fluid_modifier_create_type_data(tmmd);
if (tmmd->domain) {
FluidDomainSettings *tmds = tmmd->domain;
FluidDomainSettings *mds = mmd->domain;
/* domain object data */
tmds->fluid_group = mds->fluid_group;
tmds->force_group = mds->force_group;
tmds->effector_group = mds->effector_group;
if (tmds->effector_weights) {
MEM_freeN(tmds->effector_weights);
}
tmds->effector_weights = MEM_dupallocN(mds->effector_weights);
/* adaptive domain options */
tmds->adapt_margin = mds->adapt_margin;
tmds->adapt_res = mds->adapt_res;
tmds->adapt_threshold = mds->adapt_threshold;
/* fluid domain options */
tmds->maxres = mds->maxres;
tmds->solver_res = mds->solver_res;
tmds->border_collisions = mds->border_collisions;
tmds->flags = mds->flags;
tmds->gravity[0] = mds->gravity[0];
tmds->gravity[1] = mds->gravity[1];
tmds->gravity[2] = mds->gravity[2];
tmds->active_fields = mds->active_fields;
tmds->type = mds->type;
tmds->boundary_width = mds->boundary_width;
/* smoke domain options */
tmds->alpha = mds->alpha;
tmds->beta = mds->beta;
tmds->diss_speed = mds->diss_speed;
tmds->vorticity = mds->vorticity;
tmds->highres_sampling = mds->highres_sampling;
/* flame options */
tmds->burning_rate = mds->burning_rate;
tmds->flame_smoke = mds->flame_smoke;
tmds->flame_vorticity = mds->flame_vorticity;
tmds->flame_ignition = mds->flame_ignition;
tmds->flame_max_temp = mds->flame_max_temp;
copy_v3_v3(tmds->flame_smoke_color, mds->flame_smoke_color);
/* noise options */
tmds->noise_strength = mds->noise_strength;
tmds->noise_pos_scale = mds->noise_pos_scale;
tmds->noise_time_anim = mds->noise_time_anim;
tmds->noise_scale = mds->noise_scale;
tmds->noise_type = mds->noise_type;
/* liquid domain options */
tmds->flip_ratio = mds->flip_ratio;
tmds->particle_randomness = mds->particle_randomness;
tmds->particle_number = mds->particle_number;
tmds->particle_minimum = mds->particle_minimum;
tmds->particle_maximum = mds->particle_maximum;
tmds->particle_radius = mds->particle_radius;
tmds->particle_band_width = mds->particle_band_width;
tmds->fractions_threshold = mds->fractions_threshold;
/* diffusion options*/
tmds->surface_tension = mds->surface_tension;
tmds->viscosity_base = mds->viscosity_base;
tmds->viscosity_exponent = mds->viscosity_exponent;
tmds->domain_size = mds->domain_size;
/* mesh options */
if (mds->mesh_velocities) {
tmds->mesh_velocities = MEM_dupallocN(mds->mesh_velocities);
}
tmds->mesh_concave_upper = mds->mesh_concave_upper;
tmds->mesh_concave_lower = mds->mesh_concave_lower;
tmds->mesh_particle_radius = mds->mesh_particle_radius;
tmds->mesh_smoothen_pos = mds->mesh_smoothen_pos;
tmds->mesh_smoothen_neg = mds->mesh_smoothen_neg;
tmds->mesh_scale = mds->mesh_scale;
tmds->totvert = mds->totvert;
tmds->mesh_generator = mds->mesh_generator;
/* secondary particle options */
tmds->sndparticle_k_b = mds->sndparticle_k_b;
tmds->sndparticle_k_d = mds->sndparticle_k_d;
tmds->sndparticle_k_ta = mds->sndparticle_k_ta;
tmds->sndparticle_k_wc = mds->sndparticle_k_wc;
tmds->sndparticle_l_max = mds->sndparticle_l_max;
tmds->sndparticle_l_min = mds->sndparticle_l_min;
tmds->sndparticle_tau_max_k = mds->sndparticle_tau_max_k;
tmds->sndparticle_tau_max_ta = mds->sndparticle_tau_max_ta;
tmds->sndparticle_tau_max_wc = mds->sndparticle_tau_max_wc;
tmds->sndparticle_tau_min_k = mds->sndparticle_tau_min_k;
tmds->sndparticle_tau_min_ta = mds->sndparticle_tau_min_ta;
tmds->sndparticle_tau_min_wc = mds->sndparticle_tau_min_wc;
tmds->sndparticle_boundary = mds->sndparticle_boundary;
tmds->sndparticle_combined_export = mds->sndparticle_combined_export;
tmds->sndparticle_potential_radius = mds->sndparticle_potential_radius;
tmds->sndparticle_update_radius = mds->sndparticle_update_radius;
tmds->particle_type = mds->particle_type;
tmds->particle_scale = mds->particle_scale;
/* fluid guide options */
tmds->guide_parent = mds->guide_parent;
tmds->guide_alpha = mds->guide_alpha;
tmds->guide_beta = mds->guide_beta;
tmds->guide_vel_factor = mds->guide_vel_factor;
copy_v3_v3_int(tmds->guide_res, mds->guide_res);
tmds->guide_source = mds->guide_source;
/* cache options */
tmds->cache_frame_start = mds->cache_frame_start;
tmds->cache_frame_end = mds->cache_frame_end;
tmds->cache_frame_pause_data = mds->cache_frame_pause_data;
tmds->cache_frame_pause_noise = mds->cache_frame_pause_noise;
tmds->cache_frame_pause_mesh = mds->cache_frame_pause_mesh;
tmds->cache_frame_pause_particles = mds->cache_frame_pause_particles;
tmds->cache_frame_pause_guide = mds->cache_frame_pause_guide;
tmds->cache_flag = mds->cache_flag;
tmds->cache_type = mds->cache_type;
tmds->cache_mesh_format = mds->cache_mesh_format;
tmds->cache_data_format = mds->cache_data_format;
tmds->cache_particle_format = mds->cache_particle_format;
tmds->cache_noise_format = mds->cache_noise_format;
BLI_strncpy(tmds->cache_directory, mds->cache_directory, sizeof(tmds->cache_directory));
/* time options */
tmds->time_scale = mds->time_scale;
tmds->cfl_condition = mds->cfl_condition;
tmds->timesteps_minimum = mds->timesteps_minimum;
tmds->timesteps_maximum = mds->timesteps_maximum;
/* display options */
tmds->slice_method = mds->slice_method;
tmds->axis_slice_method = mds->axis_slice_method;
tmds->slice_axis = mds->slice_axis;
tmds->interp_method = mds->interp_method;
tmds->draw_velocity = mds->draw_velocity;
tmds->slice_per_voxel = mds->slice_per_voxel;
tmds->slice_depth = mds->slice_depth;
tmds->display_thickness = mds->display_thickness;
if (mds->coba) {
tmds->coba = MEM_dupallocN(mds->coba);
}
tmds->vector_scale = mds->vector_scale;
tmds->vector_draw_type = mds->vector_draw_type;
tmds->use_coba = mds->use_coba;
tmds->coba_field = mds->coba_field;
/* -- Deprecated / unsed options (below)-- */
/* pointcache options */
BKE_ptcache_free_list(&(tmds->ptcaches[0]));
if (flag & LIB_ID_CREATE_NO_MAIN) {
/* Share the cache with the original object's modifier. */
tmmd->modifier.flag |= eModifierFlag_SharedCaches;
tmds->point_cache[0] = mds->point_cache[0];
tmds->ptcaches[0] = mds->ptcaches[0];
}
else {
tmds->point_cache[0] = BKE_ptcache_copy_list(
&(tmds->ptcaches[0]), &(mds->ptcaches[0]), flag);
}
/* OpenVDB cache options */
tmds->openvdb_comp = mds->openvdb_comp;
tmds->clipping = mds->clipping;
tmds->data_depth = mds->data_depth;
}
else if (tmmd->flow) {
FluidFlowSettings *tmfs = tmmd->flow;
FluidFlowSettings *mfs = mmd->flow;
tmfs->psys = mfs->psys;
tmfs->noise_texture = mfs->noise_texture;
/* initial velocity */
tmfs->vel_multi = mfs->vel_multi;
tmfs->vel_normal = mfs->vel_normal;
tmfs->vel_random = mfs->vel_random;
tmfs->vel_coord[0] = mfs->vel_coord[0];
tmfs->vel_coord[1] = mfs->vel_coord[1];
tmfs->vel_coord[2] = mfs->vel_coord[2];
/* emission */
tmfs->density = mfs->density;
copy_v3_v3(tmfs->color, mfs->color);
tmfs->fuel_amount = mfs->fuel_amount;
tmfs->temperature = mfs->temperature;
tmfs->volume_density = mfs->volume_density;
tmfs->surface_distance = mfs->surface_distance;
tmfs->particle_size = mfs->particle_size;
tmfs->subframes = mfs->subframes;
/* texture control */
tmfs->texture_size = mfs->texture_size;
tmfs->texture_offset = mfs->texture_offset;
BLI_strncpy(tmfs->uvlayer_name, mfs->uvlayer_name, sizeof(tmfs->uvlayer_name));
tmfs->vgroup_density = mfs->vgroup_density;
tmfs->type = mfs->type;
tmfs->behavior = mfs->behavior;
tmfs->source = mfs->source;
tmfs->texture_type = mfs->texture_type;
tmfs->flags = mfs->flags;
}
else if (tmmd->effector) {
FluidEffectorSettings *tmes = tmmd->effector;
FluidEffectorSettings *mes = mmd->effector;
tmes->surface_distance = mes->surface_distance;
tmes->type = mes->type;
tmes->flags = mes->flags;
tmes->subframes = mes->subframes;
/* guide options */
tmes->guide_mode = mes->guide_mode;
tmes->vel_multi = mes->vel_multi;
}
}
void BKE_fluid_cache_new_name_for_current_session(int maxlen, char *r_name)
{
static int counter = 1;
BLI_snprintf(r_name, maxlen, FLUID_DOMAIN_DIR_DEFAULT "_%x", BLI_hash_int(counter));
counter++;
}
/** \} */