This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/rigidbody.c
Hans Goudey 1af62cb3bf Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.

Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).

This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.

One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.

**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.

The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.

Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.

The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.

**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.

**Future Improvements**
* Remove uses of "vert_coords" functions:
  * `BKE_mesh_vert_coords_alloc`
  * `BKE_mesh_vert_coords_get`
  * `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
  * Currently `reinterpret_cast` is used for those C-API functions

Differential Revision: https://developer.blender.org/D15982
2023-01-10 00:10:43 -05:00

2411 lines
77 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2013 Blender Foundation. All rights reserved. */
/** \file
* \ingroup bke
* \brief Blender-side interface and methods for dealing with Rigid Body simulations
*/
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "CLG_log.h"
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#ifdef WITH_BULLET
# include "RBI_api.h"
#endif
#include "DNA_ID.h"
#include "DNA_collection_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_force_types.h"
#include "DNA_object_types.h"
#include "DNA_rigidbody_types.h"
#include "DNA_scene_types.h"
#include "BKE_collection.h"
#include "BKE_effect.h"
#include "BKE_global.h"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_object.h"
#include "BKE_pointcache.h"
#include "BKE_report.h"
#include "BKE_rigidbody.h"
#include "BKE_scene.h"
#ifdef WITH_BULLET
# include "BKE_lib_id.h"
# include "BKE_lib_query.h"
#endif
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#ifdef WITH_BULLET
static CLG_LogRef LOG = {"bke.rigidbody"};
#endif
/* ************************************** */
/* Memory Management */
/* Freeing Methods --------------------- */
#ifdef WITH_BULLET
static void rigidbody_update_ob_array(RigidBodyWorld *rbw);
#else
static void RB_dworld_remove_constraint(void *UNUSED(world), void *UNUSED(con))
{
}
static void RB_dworld_remove_body(void *UNUSED(world), void *UNUSED(body))
{
}
static void RB_dworld_delete(void *UNUSED(world))
{
}
static void RB_body_delete(void *UNUSED(body))
{
}
static void RB_shape_delete(void *UNUSED(shape))
{
}
static void RB_constraint_delete(void *UNUSED(con))
{
}
#endif
void BKE_rigidbody_free_world(Scene *scene)
{
bool is_orig = (scene->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0;
RigidBodyWorld *rbw = scene->rigidbody_world;
scene->rigidbody_world = NULL;
/* sanity check */
if (!rbw) {
return;
}
if (is_orig && rbw->shared->physics_world) {
/* Free physics references,
* we assume that all physics objects in will have been added to the world. */
if (rbw->constraints) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->constraints, object) {
if (object->rigidbody_constraint) {
RigidBodyCon *rbc = object->rigidbody_constraint;
if (rbc->physics_constraint) {
RB_dworld_remove_constraint(rbw->shared->physics_world, rbc->physics_constraint);
}
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
if (rbw->group) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, object) {
BKE_rigidbody_free_object(object, rbw);
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
/* free dynamics world */
RB_dworld_delete(rbw->shared->physics_world);
}
if (rbw->objects) {
free(rbw->objects);
}
if (is_orig) {
/* free cache */
BKE_ptcache_free_list(&(rbw->shared->ptcaches));
rbw->shared->pointcache = NULL;
MEM_freeN(rbw->shared);
}
/* free effector weights */
if (rbw->effector_weights) {
MEM_freeN(rbw->effector_weights);
}
/* free rigidbody world itself */
MEM_freeN(rbw);
}
void BKE_rigidbody_free_object(Object *ob, RigidBodyWorld *rbw)
{
bool is_orig = (ob->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0;
RigidBodyOb *rbo = ob->rigidbody_object;
/* sanity check */
if (rbo == NULL) {
return;
}
/* free physics references */
if (is_orig) {
if (rbo->shared->physics_object) {
if (rbw != NULL && rbw->shared->physics_world != NULL) {
/* We can only remove the body from the world if the world is known.
* The world is generally only unknown if it's an evaluated copy of
* an object that's being freed, in which case this code isn't run anyway. */
RB_dworld_remove_body(rbw->shared->physics_world, rbo->shared->physics_object);
}
else {
/* We have no access to 'owner' RBW when deleting the object ID itself... No choice bu to
* loop over all scenes then. */
for (Scene *scene = G_MAIN->scenes.first; scene != NULL; scene = scene->id.next) {
RigidBodyWorld *scene_rbw = scene->rigidbody_world;
if (scene_rbw != NULL && scene_rbw->shared->physics_world != NULL) {
RB_dworld_remove_body(scene_rbw->shared->physics_world, rbo->shared->physics_object);
}
}
}
RB_body_delete(rbo->shared->physics_object);
rbo->shared->physics_object = NULL;
}
if (rbo->shared->physics_shape) {
RB_shape_delete(rbo->shared->physics_shape);
rbo->shared->physics_shape = NULL;
}
MEM_freeN(rbo->shared);
}
/* free data itself */
MEM_freeN(rbo);
ob->rigidbody_object = NULL;
}
void BKE_rigidbody_free_constraint(Object *ob)
{
RigidBodyCon *rbc = (ob) ? ob->rigidbody_constraint : NULL;
/* sanity check */
if (rbc == NULL) {
return;
}
/* free physics reference */
if (rbc->physics_constraint) {
RB_constraint_delete(rbc->physics_constraint);
rbc->physics_constraint = NULL;
}
/* free data itself */
MEM_freeN(rbc);
ob->rigidbody_constraint = NULL;
}
bool BKE_rigidbody_is_affected_by_simulation(Object *ob)
{
/* Check if the object will have its transform changed by the rigidbody simulation. */
/* True if the shape of this object's parent is of type compound */
bool obCompoundParent = (ob->parent != NULL && ob->parent->rigidbody_object != NULL &&
ob->parent->rigidbody_object->shape == RB_SHAPE_COMPOUND);
RigidBodyOb *rbo = ob->rigidbody_object;
if (rbo == NULL || rbo->flag & RBO_FLAG_KINEMATIC || rbo->type == RBO_TYPE_PASSIVE ||
obCompoundParent) {
return false;
}
return true;
}
#ifdef WITH_BULLET
/* Copying Methods --------------------- */
/* These just copy the data, clearing out references to physics objects.
* Anything that uses them MUST verify that the copied object will
* be added to relevant groups later...
*/
static RigidBodyOb *rigidbody_copy_object(const Object *ob, const int flag)
{
RigidBodyOb *rboN = NULL;
if (ob->rigidbody_object) {
const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0;
/* just duplicate the whole struct first (to catch all the settings) */
rboN = MEM_dupallocN(ob->rigidbody_object);
if (is_orig) {
/* This is a regular copy, and not a CoW copy for depsgraph evaluation */
rboN->shared = MEM_callocN(sizeof(*rboN->shared), "RigidBodyOb_Shared");
}
/* tag object as needing to be verified */
rboN->flag |= RBO_FLAG_NEEDS_VALIDATE;
}
/* return new copy of settings */
return rboN;
}
static RigidBodyCon *rigidbody_copy_constraint(const Object *ob, const int UNUSED(flag))
{
RigidBodyCon *rbcN = NULL;
if (ob->rigidbody_constraint) {
/* Just duplicate the whole struct first (to catch all the settings). */
rbcN = MEM_dupallocN(ob->rigidbody_constraint);
/* Tag object as needing to be verified. */
rbcN->flag |= RBC_FLAG_NEEDS_VALIDATE;
/* Clear out all the fields which need to be re-validated later. */
rbcN->physics_constraint = NULL;
}
/* return new copy of settings */
return rbcN;
}
void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src, const int flag)
{
ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag);
ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag);
if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) {
return;
}
/* We have to ensure that duplicated object ends up in relevant rigidbody collections...
* Otherwise duplicating the RB data itself is meaningless. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
RigidBodyWorld *rigidbody_world = scene->rigidbody_world;
if (rigidbody_world != NULL) {
bool need_objects_update = false;
bool need_constraints_update = false;
if (ob_dst->rigidbody_object) {
if (BKE_collection_has_object(rigidbody_world->group, ob_src)) {
BKE_collection_object_add(bmain, rigidbody_world->group, ob_dst);
need_objects_update = true;
}
}
if (ob_dst->rigidbody_constraint) {
if (BKE_collection_has_object(rigidbody_world->constraints, ob_src)) {
BKE_collection_object_add(bmain, rigidbody_world->constraints, ob_dst);
need_constraints_update = true;
}
}
if ((flag & LIB_ID_CREATE_NO_DEG_TAG) == 0 &&
(need_objects_update || need_constraints_update)) {
BKE_rigidbody_cache_reset(rigidbody_world);
DEG_relations_tag_update(bmain);
if (need_objects_update) {
DEG_id_tag_update(&rigidbody_world->group->id, ID_RECALC_COPY_ON_WRITE);
}
if (need_constraints_update) {
DEG_id_tag_update(&rigidbody_world->constraints->id, ID_RECALC_COPY_ON_WRITE);
}
DEG_id_tag_update(&ob_dst->id, ID_RECALC_TRANSFORM);
}
}
}
}
/* ************************************** */
/* Setup Utilities - Validate Sim Instances */
/* get the appropriate evaluated mesh based on rigid body mesh source */
static Mesh *rigidbody_get_mesh(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
switch (ob->rigidbody_object->mesh_source) {
case RBO_MESH_DEFORM:
return ob->runtime.mesh_deform_eval;
case RBO_MESH_FINAL:
return BKE_object_get_evaluated_mesh(ob);
case RBO_MESH_BASE:
/* This mesh may be used for computing looptris, which should be done
* on the original; otherwise every time the CoW is recreated it will
* have to be recomputed. */
BLI_assert(ob->rigidbody_object->mesh_source == RBO_MESH_BASE);
return (Mesh *)ob->runtime.data_orig;
}
/* Just return something sensible so that at least Blender won't crash. */
BLI_assert_msg(0, "Unknown mesh source");
return BKE_object_get_evaluated_mesh(ob);
}
/* create collision shape of mesh - convex hull */
static rbCollisionShape *rigidbody_get_shape_convexhull_from_mesh(Object *ob,
float margin,
bool *can_embed)
{
rbCollisionShape *shape = NULL;
Mesh *mesh = NULL;
float(*positions)[3] = NULL;
int totvert = 0;
if (ob->type == OB_MESH && ob->data) {
mesh = rigidbody_get_mesh(ob);
positions = (mesh) ? BKE_mesh_vert_positions_for_write(mesh) : NULL;
totvert = (mesh) ? mesh->totvert : 0;
}
else {
CLOG_ERROR(&LOG, "cannot make Convex Hull collision shape for non-Mesh object");
}
if (totvert) {
shape = RB_shape_new_convex_hull(
(float *)positions, sizeof(float[3]), totvert, margin, can_embed);
}
else {
CLOG_ERROR(&LOG, "no vertices to define Convex Hull collision shape with");
}
return shape;
}
/* create collision shape of mesh - triangulated mesh
* returns NULL if creation fails.
*/
static rbCollisionShape *rigidbody_get_shape_trimesh_from_mesh(Object *ob)
{
rbCollisionShape *shape = NULL;
if (ob->type == OB_MESH) {
Mesh *mesh = NULL;
const MLoopTri *looptri;
int totvert;
int tottri;
mesh = rigidbody_get_mesh(ob);
/* ensure mesh validity, then grab data */
if (mesh == NULL) {
return NULL;
}
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
totvert = mesh->totvert;
looptri = BKE_mesh_runtime_looptri_ensure(mesh);
tottri = BKE_mesh_runtime_looptri_len(mesh);
const MLoop *mloop = BKE_mesh_loops(mesh);
/* sanity checking - potential case when no data will be present */
if ((totvert == 0) || (tottri == 0)) {
CLOG_WARN(
&LOG, "no geometry data converted for Mesh Collision Shape (ob = %s)", ob->id.name + 2);
}
else {
rbMeshData *mdata;
int i;
/* init mesh data for collision shape */
mdata = RB_trimesh_data_new(tottri, totvert);
RB_trimesh_add_vertices(mdata, (float *)positions, totvert, sizeof(float[3]));
/* loop over all faces, adding them as triangles to the collision shape
* (so for some faces, more than triangle will get added)
*/
if (positions && looptri) {
for (i = 0; i < tottri; i++) {
/* add first triangle - verts 1,2,3 */
const MLoopTri *lt = &looptri[i];
int vtri[3];
vtri[0] = mloop[lt->tri[0]].v;
vtri[1] = mloop[lt->tri[1]].v;
vtri[2] = mloop[lt->tri[2]].v;
RB_trimesh_add_triangle_indices(mdata, i, UNPACK3(vtri));
}
}
RB_trimesh_finish(mdata);
/* construct collision shape
*
* These have been chosen to get better speed/accuracy tradeoffs with regards
* to limitations of each:
* - BVH-Triangle Mesh: for passive objects only. Despite having greater
* speed/accuracy, they cannot be used for moving objects.
* - GImpact Mesh: for active objects. These are slower and less stable,
* but are more flexible for general usage.
*/
if (ob->rigidbody_object->type == RBO_TYPE_PASSIVE) {
shape = RB_shape_new_trimesh(mdata);
}
else {
shape = RB_shape_new_gimpact_mesh(mdata);
}
}
}
else {
CLOG_ERROR(&LOG, "cannot make Triangular Mesh collision shape for non-Mesh object");
}
return shape;
}
/* Helper function to create physics collision shape for object.
* Returns a new collision shape.
*/
static rbCollisionShape *rigidbody_validate_sim_shape_helper(RigidBodyWorld *rbw, Object *ob)
{
RigidBodyOb *rbo = ob->rigidbody_object;
rbCollisionShape *new_shape = NULL;
float size[3] = {1.0f, 1.0f, 1.0f};
float radius = 1.0f;
float height = 1.0f;
float capsule_height;
float hull_margin = 0.0f;
bool can_embed = true;
bool has_volume;
/* sanity check */
if (rbo == NULL) {
return NULL;
}
/* if automatically determining dimensions, use the Object's boundbox
* - assume that all quadrics are standing upright on local z-axis
* - assume even distribution of mass around the Object's pivot
* (i.e. Object pivot is centralized in boundbox)
*/
/* XXX: all dimensions are auto-determined now... later can add stored settings for this */
/* get object dimensions without scaling */
const BoundBox *bb = BKE_object_boundbox_get(ob);
if (bb) {
size[0] = (bb->vec[4][0] - bb->vec[0][0]);
size[1] = (bb->vec[2][1] - bb->vec[0][1]);
size[2] = (bb->vec[1][2] - bb->vec[0][2]);
}
mul_v3_fl(size, 0.5f);
if (ELEM(rbo->shape, RB_SHAPE_CAPSULE, RB_SHAPE_CYLINDER, RB_SHAPE_CONE)) {
/* take radius as largest x/y dimension, and height as z-dimension */
radius = MAX2(size[0], size[1]);
height = size[2];
}
else if (rbo->shape == RB_SHAPE_SPHERE) {
/* take radius to the largest dimension to try and encompass everything */
radius = MAX3(size[0], size[1], size[2]);
}
/* create new shape */
switch (rbo->shape) {
case RB_SHAPE_BOX:
new_shape = RB_shape_new_box(size[0], size[1], size[2]);
break;
case RB_SHAPE_SPHERE:
new_shape = RB_shape_new_sphere(radius);
break;
case RB_SHAPE_CAPSULE:
capsule_height = (height - radius) * 2.0f;
new_shape = RB_shape_new_capsule(radius, (capsule_height > 0.0f) ? capsule_height : 0.0f);
break;
case RB_SHAPE_CYLINDER:
new_shape = RB_shape_new_cylinder(radius, height);
break;
case RB_SHAPE_CONE:
new_shape = RB_shape_new_cone(radius, height * 2.0f);
break;
case RB_SHAPE_CONVEXH:
/* try to embed collision margin */
has_volume = (MIN3(size[0], size[1], size[2]) > 0.0f);
if (!(rbo->flag & RBO_FLAG_USE_MARGIN) && has_volume) {
hull_margin = 0.04f;
}
new_shape = rigidbody_get_shape_convexhull_from_mesh(ob, hull_margin, &can_embed);
if (!(rbo->flag & RBO_FLAG_USE_MARGIN)) {
rbo->margin = (can_embed && has_volume) ?
0.04f :
0.0f; /* RB_TODO ideally we shouldn't directly change the margin here */
}
break;
case RB_SHAPE_TRIMESH:
new_shape = rigidbody_get_shape_trimesh_from_mesh(ob);
break;
case RB_SHAPE_COMPOUND:
new_shape = RB_shape_new_compound();
rbCollisionShape *childShape = NULL;
float loc[3], rot[4];
float mat[4][4];
/* Add children to the compound shape */
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, childObject) {
if (childObject->parent == ob) {
childShape = rigidbody_validate_sim_shape_helper(rbw, childObject);
if (childShape) {
BKE_object_matrix_local_get(childObject, mat);
mat4_to_loc_quat(loc, rot, mat);
RB_compound_add_child_shape(new_shape, childShape, loc, rot);
}
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
break;
}
/* use box shape if it failed to create new shape */
if (new_shape == NULL) {
new_shape = RB_shape_new_box(size[0], size[1], size[2]);
}
if (new_shape) {
RB_shape_set_margin(new_shape, RBO_GET_MARGIN(rbo));
}
return new_shape;
}
/* Create new physics sim collision shape for object and store it,
* or remove the existing one first and replace...
*/
static void rigidbody_validate_sim_shape(RigidBodyWorld *rbw, Object *ob, bool rebuild)
{
RigidBodyOb *rbo = ob->rigidbody_object;
rbCollisionShape *new_shape = NULL;
/* sanity check */
if (rbo == NULL) {
return;
}
/* don't create a new shape if we already have one and don't want to rebuild it */
if (rbo->shared->physics_shape && !rebuild) {
return;
}
/* Also don't create a shape if this object is parent of a compound shape */
if (ob->parent != NULL && ob->parent->rigidbody_object != NULL &&
ob->parent->rigidbody_object->shape == RB_SHAPE_COMPOUND) {
return;
}
new_shape = rigidbody_validate_sim_shape_helper(rbw, ob);
/* assign new collision shape if creation was successful */
if (new_shape) {
if (rbo->shared->physics_shape) {
RB_shape_delete(rbo->shared->physics_shape);
}
rbo->shared->physics_shape = new_shape;
}
}
/* --------------------- */
void BKE_rigidbody_calc_volume(Object *ob, float *r_vol)
{
RigidBodyOb *rbo = ob->rigidbody_object;
float size[3] = {1.0f, 1.0f, 1.0f};
float radius = 1.0f;
float height = 1.0f;
float volume = 0.0f;
/* if automatically determining dimensions, use the Object's boundbox
* - assume that all quadrics are standing upright on local z-axis
* - assume even distribution of mass around the Object's pivot
* (i.e. Object pivot is centralized in boundbox)
* - boundbox gives full width
*/
/* XXX: all dimensions are auto-determined now... later can add stored settings for this */
BKE_object_dimensions_get(ob, size);
if (ELEM(rbo->shape, RB_SHAPE_CAPSULE, RB_SHAPE_CYLINDER, RB_SHAPE_CONE)) {
/* take radius as largest x/y dimension, and height as z-dimension */
radius = MAX2(size[0], size[1]) * 0.5f;
height = size[2];
}
else if (rbo->shape == RB_SHAPE_SPHERE) {
/* take radius to the largest dimension to try and encompass everything */
radius = max_fff(size[0], size[1], size[2]) * 0.5f;
}
/* Calculate volume as appropriate. */
switch (rbo->shape) {
case RB_SHAPE_BOX:
volume = size[0] * size[1] * size[2];
break;
case RB_SHAPE_SPHERE:
volume = 4.0f / 3.0f * (float)M_PI * radius * radius * radius;
break;
/* for now, assume that capsule is close enough to a cylinder... */
case RB_SHAPE_CAPSULE:
case RB_SHAPE_CYLINDER:
volume = (float)M_PI * radius * radius * height;
break;
case RB_SHAPE_CONE:
volume = (float)M_PI / 3.0f * radius * radius * height;
break;
case RB_SHAPE_CONVEXH:
case RB_SHAPE_TRIMESH: {
if (ob->type == OB_MESH) {
Mesh *mesh = rigidbody_get_mesh(ob);
const MLoopTri *lt = NULL;
int totvert, tottri = 0;
/* ensure mesh validity, then grab data */
if (mesh == NULL) {
return;
}
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
totvert = mesh->totvert;
lt = BKE_mesh_runtime_looptri_ensure(mesh);
tottri = BKE_mesh_runtime_looptri_len(mesh);
const MLoop *mloop = BKE_mesh_loops(mesh);
if (totvert > 0 && tottri > 0) {
BKE_mesh_calc_volume(positions, totvert, lt, tottri, mloop, &volume, NULL);
const float volume_scale = mat4_to_volume_scale(ob->object_to_world);
volume *= fabsf(volume_scale);
}
}
else {
/* rough estimate from boundbox as fallback */
/* XXX could implement other types of geometry here (curves, etc.) */
volume = size[0] * size[1] * size[2];
}
break;
}
}
/* return the volume calculated */
if (r_vol) {
*r_vol = volume;
}
}
void BKE_rigidbody_calc_center_of_mass(Object *ob, float r_center[3])
{
RigidBodyOb *rbo = ob->rigidbody_object;
float size[3] = {1.0f, 1.0f, 1.0f};
float height = 1.0f;
zero_v3(r_center);
/* if automatically determining dimensions, use the Object's boundbox
* - assume that all quadrics are standing upright on local z-axis
* - assume even distribution of mass around the Object's pivot
* (i.e. Object pivot is centralized in boundbox)
* - boundbox gives full width
*/
/* XXX: all dimensions are auto-determined now... later can add stored settings for this. */
BKE_object_dimensions_get(ob, size);
/* Calculate volume as appropriate. */
switch (rbo->shape) {
case RB_SHAPE_BOX:
case RB_SHAPE_SPHERE:
case RB_SHAPE_CAPSULE:
case RB_SHAPE_CYLINDER:
break;
case RB_SHAPE_CONE:
/* take radius as largest x/y dimension, and height as z-dimension */
height = size[2];
/* cone is geometrically centered on the median,
* center of mass is 1/4 up from the base
*/
r_center[2] = -0.25f * height;
break;
case RB_SHAPE_CONVEXH:
case RB_SHAPE_TRIMESH: {
if (ob->type == OB_MESH) {
Mesh *mesh = rigidbody_get_mesh(ob);
const MLoopTri *looptri;
int totvert, tottri;
/* ensure mesh validity, then grab data */
if (mesh == NULL) {
return;
}
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
totvert = mesh->totvert;
looptri = BKE_mesh_runtime_looptri_ensure(mesh);
tottri = BKE_mesh_runtime_looptri_len(mesh);
const MLoop *mloop = BKE_mesh_loops(mesh);
if (totvert > 0 && tottri > 0) {
BKE_mesh_calc_volume(positions, totvert, looptri, tottri, mloop, NULL, r_center);
}
}
break;
}
}
}
/* --------------------- */
/**
* Create physics sim representation of object given RigidBody settings
*
* \param rebuild: Even if an instance already exists, replace it
*/
static void rigidbody_validate_sim_object(RigidBodyWorld *rbw, Object *ob, bool rebuild)
{
RigidBodyOb *rbo = (ob) ? ob->rigidbody_object : NULL;
float loc[3];
float rot[4];
/* sanity checks:
* - object doesn't have RigidBody info already: then why is it here?
*/
if (rbo == NULL) {
return;
}
/* make sure collision shape exists */
/* FIXME we shouldn't always have to rebuild collision shapes when rebuilding objects,
* but it's needed for constraints to update correctly. */
if (rbo->shared->physics_shape == NULL || rebuild) {
rigidbody_validate_sim_shape(rbw, ob, true);
}
if (rbo->shared->physics_object && !rebuild) {
/* Don't remove body on rebuild as it has already been removed when deleting and rebuilding the
* world. */
RB_dworld_remove_body(rbw->shared->physics_world, rbo->shared->physics_object);
}
if (!rbo->shared->physics_object || rebuild) {
/* remove rigid body if it already exists before creating a new one */
if (rbo->shared->physics_object) {
RB_body_delete(rbo->shared->physics_object);
rbo->shared->physics_object = NULL;
}
/* Don't create rigid body object if the parent is a compound shape */
if (ob->parent != NULL && ob->parent->rigidbody_object != NULL &&
ob->parent->rigidbody_object->shape == RB_SHAPE_COMPOUND) {
return;
}
mat4_to_loc_quat(loc, rot, ob->object_to_world);
rbo->shared->physics_object = RB_body_new(rbo->shared->physics_shape, loc, rot);
RB_body_set_friction(rbo->shared->physics_object, rbo->friction);
RB_body_set_restitution(rbo->shared->physics_object, rbo->restitution);
RB_body_set_damping(rbo->shared->physics_object, rbo->lin_damping, rbo->ang_damping);
RB_body_set_sleep_thresh(
rbo->shared->physics_object, rbo->lin_sleep_thresh, rbo->ang_sleep_thresh);
RB_body_set_activation_state(rbo->shared->physics_object,
rbo->flag & RBO_FLAG_USE_DEACTIVATION);
if (rbo->type == RBO_TYPE_PASSIVE || rbo->flag & RBO_FLAG_START_DEACTIVATED) {
RB_body_deactivate(rbo->shared->physics_object);
}
RB_body_set_linear_factor(rbo->shared->physics_object,
(ob->protectflag & OB_LOCK_LOCX) == 0,
(ob->protectflag & OB_LOCK_LOCY) == 0,
(ob->protectflag & OB_LOCK_LOCZ) == 0);
RB_body_set_angular_factor(rbo->shared->physics_object,
(ob->protectflag & OB_LOCK_ROTX) == 0,
(ob->protectflag & OB_LOCK_ROTY) == 0,
(ob->protectflag & OB_LOCK_ROTZ) == 0);
RB_body_set_mass(rbo->shared->physics_object, RBO_GET_MASS(rbo));
RB_body_set_kinematic_state(rbo->shared->physics_object,
rbo->flag & RBO_FLAG_KINEMATIC || rbo->flag & RBO_FLAG_DISABLED);
}
if (rbw && rbw->shared->physics_world && rbo->shared->physics_object) {
RB_dworld_add_body(rbw->shared->physics_world, rbo->shared->physics_object, rbo->col_groups);
}
}
/* --------------------- */
static void rigidbody_constraint_init_spring(RigidBodyCon *rbc,
void (*set_spring)(rbConstraint *, int, int),
void (*set_stiffness)(rbConstraint *, int, float),
void (*set_damping)(rbConstraint *, int, float))
{
set_spring(rbc->physics_constraint, RB_LIMIT_LIN_X, rbc->flag & RBC_FLAG_USE_SPRING_X);
set_stiffness(rbc->physics_constraint, RB_LIMIT_LIN_X, rbc->spring_stiffness_x);
set_damping(rbc->physics_constraint, RB_LIMIT_LIN_X, rbc->spring_damping_x);
set_spring(rbc->physics_constraint, RB_LIMIT_LIN_Y, rbc->flag & RBC_FLAG_USE_SPRING_Y);
set_stiffness(rbc->physics_constraint, RB_LIMIT_LIN_Y, rbc->spring_stiffness_y);
set_damping(rbc->physics_constraint, RB_LIMIT_LIN_Y, rbc->spring_damping_y);
set_spring(rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->flag & RBC_FLAG_USE_SPRING_Z);
set_stiffness(rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->spring_stiffness_z);
set_damping(rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->spring_damping_z);
set_spring(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->flag & RBC_FLAG_USE_SPRING_ANG_X);
set_stiffness(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->spring_stiffness_ang_x);
set_damping(rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->spring_damping_ang_x);
set_spring(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->flag & RBC_FLAG_USE_SPRING_ANG_Y);
set_stiffness(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->spring_stiffness_ang_y);
set_damping(rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->spring_damping_ang_y);
set_spring(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->flag & RBC_FLAG_USE_SPRING_ANG_Z);
set_stiffness(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->spring_stiffness_ang_z);
set_damping(rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->spring_damping_ang_z);
}
static void rigidbody_constraint_set_limits(RigidBodyCon *rbc,
void (*set_limits)(rbConstraint *, int, float, float))
{
if (rbc->flag & RBC_FLAG_USE_LIMIT_LIN_X) {
set_limits(
rbc->physics_constraint, RB_LIMIT_LIN_X, rbc->limit_lin_x_lower, rbc->limit_lin_x_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_LIN_X, 0.0f, -1.0f);
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_LIN_Y) {
set_limits(
rbc->physics_constraint, RB_LIMIT_LIN_Y, rbc->limit_lin_y_lower, rbc->limit_lin_y_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_LIN_Y, 0.0f, -1.0f);
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_LIN_Z) {
set_limits(
rbc->physics_constraint, RB_LIMIT_LIN_Z, rbc->limit_lin_z_lower, rbc->limit_lin_z_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_LIN_Z, 0.0f, -1.0f);
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_ANG_X) {
set_limits(
rbc->physics_constraint, RB_LIMIT_ANG_X, rbc->limit_ang_x_lower, rbc->limit_ang_x_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_ANG_X, 0.0f, -1.0f);
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_ANG_Y) {
set_limits(
rbc->physics_constraint, RB_LIMIT_ANG_Y, rbc->limit_ang_y_lower, rbc->limit_ang_y_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_ANG_Y, 0.0f, -1.0f);
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_ANG_Z) {
set_limits(
rbc->physics_constraint, RB_LIMIT_ANG_Z, rbc->limit_ang_z_lower, rbc->limit_ang_z_upper);
}
else {
set_limits(rbc->physics_constraint, RB_LIMIT_ANG_Z, 0.0f, -1.0f);
}
}
/**
* Create physics sim representation of constraint given rigid body constraint settings
*
* \param rebuild: Even if an instance already exists, replace it
*/
static void rigidbody_validate_sim_constraint(RigidBodyWorld *rbw, Object *ob, bool rebuild)
{
RigidBodyCon *rbc = (ob) ? ob->rigidbody_constraint : NULL;
float loc[3];
float rot[4];
float lin_lower;
float lin_upper;
float ang_lower;
float ang_upper;
/* sanity checks:
* - object should have a rigid body constraint
* - rigid body constraint should have at least one constrained object
*/
if (rbc == NULL) {
return;
}
if (ELEM(NULL, rbc->ob1, rbc->ob1->rigidbody_object, rbc->ob2, rbc->ob2->rigidbody_object)) {
if (rbc->physics_constraint) {
RB_dworld_remove_constraint(rbw->shared->physics_world, rbc->physics_constraint);
RB_constraint_delete(rbc->physics_constraint);
rbc->physics_constraint = NULL;
}
return;
}
if (rbc->physics_constraint && rebuild == false) {
RB_dworld_remove_constraint(rbw->shared->physics_world, rbc->physics_constraint);
}
if (rbc->physics_constraint == NULL || rebuild) {
rbRigidBody *rb1 = rbc->ob1->rigidbody_object->shared->physics_object;
rbRigidBody *rb2 = rbc->ob2->rigidbody_object->shared->physics_object;
/* remove constraint if it already exists before creating a new one */
if (rbc->physics_constraint) {
RB_constraint_delete(rbc->physics_constraint);
rbc->physics_constraint = NULL;
}
mat4_to_loc_quat(loc, rot, ob->object_to_world);
if (rb1 && rb2) {
switch (rbc->type) {
case RBC_TYPE_POINT:
rbc->physics_constraint = RB_constraint_new_point(loc, rb1, rb2);
break;
case RBC_TYPE_FIXED:
rbc->physics_constraint = RB_constraint_new_fixed(loc, rot, rb1, rb2);
break;
case RBC_TYPE_HINGE:
rbc->physics_constraint = RB_constraint_new_hinge(loc, rot, rb1, rb2);
if (rbc->flag & RBC_FLAG_USE_LIMIT_ANG_Z) {
RB_constraint_set_limits_hinge(
rbc->physics_constraint, rbc->limit_ang_z_lower, rbc->limit_ang_z_upper);
}
else {
RB_constraint_set_limits_hinge(rbc->physics_constraint, 0.0f, -1.0f);
}
break;
case RBC_TYPE_SLIDER:
rbc->physics_constraint = RB_constraint_new_slider(loc, rot, rb1, rb2);
if (rbc->flag & RBC_FLAG_USE_LIMIT_LIN_X) {
RB_constraint_set_limits_slider(
rbc->physics_constraint, rbc->limit_lin_x_lower, rbc->limit_lin_x_upper);
}
else {
RB_constraint_set_limits_slider(rbc->physics_constraint, 0.0f, -1.0f);
}
break;
case RBC_TYPE_PISTON:
rbc->physics_constraint = RB_constraint_new_piston(loc, rot, rb1, rb2);
if (rbc->flag & RBC_FLAG_USE_LIMIT_LIN_X) {
lin_lower = rbc->limit_lin_x_lower;
lin_upper = rbc->limit_lin_x_upper;
}
else {
lin_lower = 0.0f;
lin_upper = -1.0f;
}
if (rbc->flag & RBC_FLAG_USE_LIMIT_ANG_X) {
ang_lower = rbc->limit_ang_x_lower;
ang_upper = rbc->limit_ang_x_upper;
}
else {
ang_lower = 0.0f;
ang_upper = -1.0f;
}
RB_constraint_set_limits_piston(
rbc->physics_constraint, lin_lower, lin_upper, ang_lower, ang_upper);
break;
case RBC_TYPE_6DOF_SPRING:
if (rbc->spring_type == RBC_SPRING_TYPE2) {
rbc->physics_constraint = RB_constraint_new_6dof_spring2(loc, rot, rb1, rb2);
rigidbody_constraint_init_spring(rbc,
RB_constraint_set_spring_6dof_spring2,
RB_constraint_set_stiffness_6dof_spring2,
RB_constraint_set_damping_6dof_spring2);
RB_constraint_set_equilibrium_6dof_spring2(rbc->physics_constraint);
rigidbody_constraint_set_limits(rbc, RB_constraint_set_limits_6dof_spring2);
}
else {
rbc->physics_constraint = RB_constraint_new_6dof_spring(loc, rot, rb1, rb2);
rigidbody_constraint_init_spring(rbc,
RB_constraint_set_spring_6dof_spring,
RB_constraint_set_stiffness_6dof_spring,
RB_constraint_set_damping_6dof_spring);
RB_constraint_set_equilibrium_6dof_spring(rbc->physics_constraint);
rigidbody_constraint_set_limits(rbc, RB_constraint_set_limits_6dof);
}
break;
case RBC_TYPE_6DOF:
rbc->physics_constraint = RB_constraint_new_6dof(loc, rot, rb1, rb2);
rigidbody_constraint_set_limits(rbc, RB_constraint_set_limits_6dof);
break;
case RBC_TYPE_MOTOR:
rbc->physics_constraint = RB_constraint_new_motor(loc, rot, rb1, rb2);
RB_constraint_set_enable_motor(rbc->physics_constraint,
rbc->flag & RBC_FLAG_USE_MOTOR_LIN,
rbc->flag & RBC_FLAG_USE_MOTOR_ANG);
RB_constraint_set_max_impulse_motor(
rbc->physics_constraint, rbc->motor_lin_max_impulse, rbc->motor_ang_max_impulse);
RB_constraint_set_target_velocity_motor(rbc->physics_constraint,
rbc->motor_lin_target_velocity,
rbc->motor_ang_target_velocity);
break;
}
}
else { /* can't create constraint without both rigid bodies */
return;
}
/* When 'rbc->type' is unknown. */
if (rbc->physics_constraint == NULL) {
return;
}
RB_constraint_set_enabled(rbc->physics_constraint, rbc->flag & RBC_FLAG_ENABLED);
if (rbc->flag & RBC_FLAG_USE_BREAKING) {
RB_constraint_set_breaking_threshold(rbc->physics_constraint, rbc->breaking_threshold);
}
else {
RB_constraint_set_breaking_threshold(rbc->physics_constraint, FLT_MAX);
}
if (rbc->flag & RBC_FLAG_OVERRIDE_SOLVER_ITERATIONS) {
RB_constraint_set_solver_iterations(rbc->physics_constraint, rbc->num_solver_iterations);
}
else {
RB_constraint_set_solver_iterations(rbc->physics_constraint, -1);
}
}
if (rbw && rbw->shared->physics_world && rbc->physics_constraint) {
RB_dworld_add_constraint(rbw->shared->physics_world,
rbc->physics_constraint,
rbc->flag & RBC_FLAG_DISABLE_COLLISIONS);
}
}
/* --------------------- */
void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, bool rebuild)
{
/* sanity checks */
if (rbw == NULL) {
return;
}
/* create new sim world */
if (rebuild || rbw->shared->physics_world == NULL) {
if (rbw->shared->physics_world) {
RB_dworld_delete(rbw->shared->physics_world);
}
rbw->shared->physics_world = RB_dworld_new(scene->physics_settings.gravity);
}
RB_dworld_set_solver_iterations(rbw->shared->physics_world, rbw->num_solver_iterations);
RB_dworld_set_split_impulse(rbw->shared->physics_world, rbw->flag & RBW_FLAG_USE_SPLIT_IMPULSE);
}
/* ************************************** */
/* Setup Utilities - Create Settings Blocks */
RigidBodyWorld *BKE_rigidbody_create_world(Scene *scene)
{
/* try to get whatever RigidBody world that might be representing this already */
RigidBodyWorld *rbw;
/* sanity checks
* - there must be a valid scene to add world to
* - there mustn't be a sim world using this group already
*/
if (scene == NULL) {
return NULL;
}
/* create a new sim world */
rbw = MEM_callocN(sizeof(RigidBodyWorld), "RigidBodyWorld");
rbw->shared = MEM_callocN(sizeof(*rbw->shared), "RigidBodyWorld_Shared");
/* set default settings */
rbw->effector_weights = BKE_effector_add_weights(NULL);
rbw->ltime = PSFRA;
rbw->time_scale = 1.0f;
/* Most high quality Bullet example files has an internal framerate of 240hz.
* The blender default scene has a frame rate of 24, so take 10 substeps (24fps * 10).
*/
rbw->substeps_per_frame = 10;
rbw->num_solver_iterations = 10; /* 10 is bullet default */
rbw->shared->pointcache = BKE_ptcache_add(&(rbw->shared->ptcaches));
rbw->shared->pointcache->step = 1;
/* return this sim world */
return rbw;
}
RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag)
{
RigidBodyWorld *rbw_copy = MEM_dupallocN(rbw);
if (rbw->effector_weights) {
rbw_copy->effector_weights = MEM_dupallocN(rbw->effector_weights);
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus((ID *)rbw->effector_weights->group);
}
}
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus((ID *)rbw_copy->group);
id_us_plus((ID *)rbw_copy->constraints);
}
if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) {
/* This is a regular copy, and not a CoW copy for depsgraph evaluation. */
rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared");
BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES);
rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first;
}
rbw_copy->objects = NULL;
rbw_copy->numbodies = 0;
rigidbody_update_ob_array(rbw_copy);
return rbw_copy;
}
void BKE_rigidbody_world_groups_relink(RigidBodyWorld *rbw)
{
ID_NEW_REMAP(rbw->group);
ID_NEW_REMAP(rbw->constraints);
ID_NEW_REMAP(rbw->effector_weights->group);
}
void BKE_rigidbody_world_id_loop(RigidBodyWorld *rbw, RigidbodyWorldIDFunc func, void *userdata)
{
func(rbw, (ID **)&rbw->group, userdata, IDWALK_CB_USER);
func(rbw, (ID **)&rbw->constraints, userdata, IDWALK_CB_USER);
func(rbw, (ID **)&rbw->effector_weights->group, userdata, IDWALK_CB_USER);
if (rbw->objects) {
int i;
for (i = 0; i < rbw->numbodies; i++) {
func(rbw, (ID **)&rbw->objects[i], userdata, IDWALK_CB_NOP);
}
}
}
RigidBodyOb *BKE_rigidbody_create_object(Scene *scene, Object *ob, short type)
{
RigidBodyOb *rbo;
RigidBodyWorld *rbw = scene->rigidbody_world;
/* sanity checks
* - rigidbody world must exist
* - object must exist
* - cannot add rigid body if it already exists
*/
if (ob == NULL) {
return NULL;
}
if (ob->rigidbody_object != NULL) {
return ob->rigidbody_object;
}
/* create new settings data, and link it up */
rbo = MEM_callocN(sizeof(RigidBodyOb), "RigidBodyOb");
rbo->shared = MEM_callocN(sizeof(*rbo->shared), "RigidBodyOb_Shared");
/* set default settings */
rbo->type = type;
rbo->mass = 1.0f;
rbo->friction = 0.5f; /* best when non-zero. 0.5 is Bullet default */
rbo->restitution = 0.0f; /* best when zero. 0.0 is Bullet default */
rbo->margin = 0.04f; /* 0.04 (in meters) is Bullet default */
rbo->lin_sleep_thresh = 0.4f; /* 0.4 is half of Bullet default */
rbo->ang_sleep_thresh = 0.5f; /* 0.5 is half of Bullet default */
rbo->lin_damping = 0.04f;
rbo->ang_damping = 0.1f;
rbo->col_groups = 1;
/* use triangle meshes for passive objects
* use convex hulls for active objects since dynamic triangle meshes are very unstable
*/
if (type == RBO_TYPE_ACTIVE) {
rbo->shape = RB_SHAPE_CONVEXH;
}
else {
rbo->shape = RB_SHAPE_TRIMESH;
}
rbo->mesh_source = RBO_MESH_DEFORM;
/* set initial transform */
mat4_to_loc_quat(rbo->pos, rbo->orn, ob->object_to_world);
/* flag cache as outdated */
BKE_rigidbody_cache_reset(rbw);
rbo->flag |= (RBO_FLAG_NEEDS_VALIDATE | RBO_FLAG_NEEDS_RESHAPE);
/* return this object */
return rbo;
}
RigidBodyCon *BKE_rigidbody_create_constraint(Scene *scene, Object *ob, short type)
{
RigidBodyCon *rbc;
RigidBodyWorld *rbw = scene->rigidbody_world;
/* sanity checks
* - rigidbody world must exist
* - object must exist
* - cannot add constraint if it already exists
*/
if (ob == NULL || (ob->rigidbody_constraint != NULL)) {
return NULL;
}
/* create new settings data, and link it up */
rbc = MEM_callocN(sizeof(RigidBodyCon), "RigidBodyCon");
/* set default settings */
rbc->type = type;
rbc->ob1 = NULL;
rbc->ob2 = NULL;
rbc->flag |= RBC_FLAG_ENABLED;
rbc->flag |= RBC_FLAG_DISABLE_COLLISIONS;
rbc->flag |= RBC_FLAG_NEEDS_VALIDATE;
rbc->spring_type = RBC_SPRING_TYPE2;
rbc->breaking_threshold = 10.0f; /* no good default here, just use 10 for now */
rbc->num_solver_iterations = 10; /* 10 is Bullet default */
rbc->limit_lin_x_lower = -1.0f;
rbc->limit_lin_x_upper = 1.0f;
rbc->limit_lin_y_lower = -1.0f;
rbc->limit_lin_y_upper = 1.0f;
rbc->limit_lin_z_lower = -1.0f;
rbc->limit_lin_z_upper = 1.0f;
rbc->limit_ang_x_lower = -M_PI_4;
rbc->limit_ang_x_upper = M_PI_4;
rbc->limit_ang_y_lower = -M_PI_4;
rbc->limit_ang_y_upper = M_PI_4;
rbc->limit_ang_z_lower = -M_PI_4;
rbc->limit_ang_z_upper = M_PI_4;
rbc->spring_damping_x = 0.5f;
rbc->spring_damping_y = 0.5f;
rbc->spring_damping_z = 0.5f;
rbc->spring_damping_ang_x = 0.5f;
rbc->spring_damping_ang_y = 0.5f;
rbc->spring_damping_ang_z = 0.5f;
rbc->spring_stiffness_x = 10.0f;
rbc->spring_stiffness_y = 10.0f;
rbc->spring_stiffness_z = 10.0f;
rbc->spring_stiffness_ang_x = 10.0f;
rbc->spring_stiffness_ang_y = 10.0f;
rbc->spring_stiffness_ang_z = 10.0f;
rbc->motor_lin_max_impulse = 1.0f;
rbc->motor_lin_target_velocity = 1.0f;
rbc->motor_ang_max_impulse = 1.0f;
rbc->motor_ang_target_velocity = 1.0f;
/* flag cache as outdated */
BKE_rigidbody_cache_reset(rbw);
/* return this object */
return rbc;
}
void BKE_rigidbody_objects_collection_validate(Scene *scene, RigidBodyWorld *rbw)
{
if (rbw->group != NULL) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, object) {
if (object->type != OB_MESH || object->rigidbody_object != NULL) {
continue;
}
object->rigidbody_object = BKE_rigidbody_create_object(scene, object, RBO_TYPE_ACTIVE);
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
}
void BKE_rigidbody_constraints_collection_validate(Scene *scene, RigidBodyWorld *rbw)
{
if (rbw->constraints != NULL) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->constraints, object) {
if (object->rigidbody_constraint != NULL) {
continue;
}
object->rigidbody_constraint = BKE_rigidbody_create_constraint(
scene, object, RBC_TYPE_FIXED);
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
}
void BKE_rigidbody_main_collection_object_add(Main *bmain, Collection *collection, Object *object)
{
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
RigidBodyWorld *rbw = scene->rigidbody_world;
if (rbw == NULL) {
continue;
}
if (rbw->group == collection && object->type == OB_MESH && object->rigidbody_object == NULL) {
object->rigidbody_object = BKE_rigidbody_create_object(scene, object, RBO_TYPE_ACTIVE);
}
if (rbw->constraints == collection && object->rigidbody_constraint == NULL) {
object->rigidbody_constraint = BKE_rigidbody_create_constraint(
scene, object, RBC_TYPE_FIXED);
}
}
}
/* ************************************** */
/* Utilities API */
RigidBodyWorld *BKE_rigidbody_get_world(Scene *scene)
{
/* sanity check */
if (scene == NULL) {
return NULL;
}
return scene->rigidbody_world;
}
static bool rigidbody_add_object_to_scene(Main *bmain, Scene *scene, Object *ob)
{
/* Add rigid body world and group if they don't exist for convenience */
RigidBodyWorld *rbw = BKE_rigidbody_get_world(scene);
if (rbw == NULL) {
rbw = BKE_rigidbody_create_world(scene);
if (rbw == NULL) {
return false;
}
BKE_rigidbody_validate_sim_world(scene, rbw, false);
scene->rigidbody_world = rbw;
}
if (rbw->group == NULL) {
rbw->group = BKE_collection_add(bmain, NULL, "RigidBodyWorld");
id_us_plus(&rbw->group->id);
}
/* Add object to rigid body group. */
BKE_collection_object_add(bmain, rbw->group, ob);
BKE_rigidbody_cache_reset(rbw);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&rbw->group->id, ID_RECALC_COPY_ON_WRITE);
return true;
}
static bool rigidbody_add_constraint_to_scene(Main *bmain, Scene *scene, Object *ob)
{
/* Add rigid body world and group if they don't exist for convenience */
RigidBodyWorld *rbw = BKE_rigidbody_get_world(scene);
if (rbw == NULL) {
rbw = BKE_rigidbody_create_world(scene);
if (rbw == NULL) {
return false;
}
BKE_rigidbody_validate_sim_world(scene, rbw, false);
scene->rigidbody_world = rbw;
}
if (rbw->constraints == NULL) {
rbw->constraints = BKE_collection_add(bmain, NULL, "RigidBodyConstraints");
id_us_plus(&rbw->constraints->id);
}
/* Add object to rigid body group. */
BKE_collection_object_add(bmain, rbw->constraints, ob);
BKE_rigidbody_cache_reset(rbw);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&rbw->constraints->id, ID_RECALC_COPY_ON_WRITE);
return true;
}
void BKE_rigidbody_ensure_local_object(Main *bmain, Object *ob)
{
if (ob->rigidbody_object != NULL) {
/* Add newly local object to scene. */
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
if (BKE_scene_object_find(scene, ob)) {
rigidbody_add_object_to_scene(bmain, scene, ob);
}
}
}
if (ob->rigidbody_constraint != NULL) {
/* Add newly local object to scene. */
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
if (BKE_scene_object_find(scene, ob)) {
rigidbody_add_constraint_to_scene(bmain, scene, ob);
}
}
}
}
bool BKE_rigidbody_add_object(Main *bmain, Scene *scene, Object *ob, int type, ReportList *reports)
{
if (ob->type != OB_MESH) {
BKE_report(reports, RPT_ERROR, "Can't add Rigid Body to non mesh object");
return false;
}
/* Add object to rigid body world in scene. */
if (!rigidbody_add_object_to_scene(bmain, scene, ob)) {
BKE_report(reports, RPT_ERROR, "Can't create Rigid Body world");
return false;
}
/* make rigidbody object settings */
if (ob->rigidbody_object == NULL) {
ob->rigidbody_object = BKE_rigidbody_create_object(scene, ob, type);
}
ob->rigidbody_object->type = type;
ob->rigidbody_object->flag |= RBO_FLAG_NEEDS_VALIDATE;
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
return true;
}
void BKE_rigidbody_remove_object(Main *bmain, Scene *scene, Object *ob, const bool free_us)
{
RigidBodyWorld *rbw = scene->rigidbody_world;
RigidBodyCon *rbc;
int i;
if (rbw) {
/* remove object from array */
if (rbw->objects) {
for (i = 0; i < rbw->numbodies; i++) {
if (rbw->objects[i] == ob) {
rbw->objects[i] = NULL;
break;
}
}
}
/* remove object from rigid body constraints */
if (rbw->constraints) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->constraints, obt) {
if (obt && obt->rigidbody_constraint) {
rbc = obt->rigidbody_constraint;
if (rbc->ob1 == ob) {
rbc->ob1 = NULL;
DEG_id_tag_update(&obt->id, ID_RECALC_COPY_ON_WRITE);
}
if (rbc->ob2 == ob) {
rbc->ob2 = NULL;
DEG_id_tag_update(&obt->id, ID_RECALC_COPY_ON_WRITE);
}
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
/* Relying on user-count of the object should be OK, and it is much cheaper than looping in all
* collections to check whether the object is already in another one... */
if (ID_REAL_USERS(&ob->id) == 1) {
/* Some users seems to find it funny to use a view-layer instancing collection
* as RBW collection... Despite this being a bad (ab)use of the system, avoid losing objects
* when we remove them from RB simulation. */
BKE_collection_object_add(bmain, scene->master_collection, ob);
}
BKE_collection_object_remove(bmain, rbw->group, ob, free_us);
/* flag cache as outdated */
BKE_rigidbody_cache_reset(rbw);
/* Reset cache as the object order probably changed after freeing the object. */
PTCacheID pid;
BKE_ptcache_id_from_rigidbody(&pid, NULL, rbw);
BKE_ptcache_id_reset(scene, &pid, PTCACHE_RESET_OUTDATED);
}
/* remove object's settings */
BKE_rigidbody_free_object(ob, rbw);
/* Dependency graph update */
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
}
void BKE_rigidbody_remove_constraint(Main *bmain, Scene *scene, Object *ob, const bool free_us)
{
RigidBodyWorld *rbw = scene->rigidbody_world;
RigidBodyCon *rbc = ob->rigidbody_constraint;
if (rbw != NULL) {
/* Remove from RBW constraints collection. */
if (rbw->constraints != NULL) {
BKE_collection_object_remove(bmain, rbw->constraints, ob, free_us);
DEG_id_tag_update(&rbw->constraints->id, ID_RECALC_COPY_ON_WRITE);
}
/* remove from rigidbody world, free object won't do this */
if (rbw->shared->physics_world && rbc->physics_constraint) {
RB_dworld_remove_constraint(rbw->shared->physics_world, rbc->physics_constraint);
}
}
/* remove object's settings */
BKE_rigidbody_free_constraint(ob);
/* flag cache as outdated */
BKE_rigidbody_cache_reset(rbw);
}
/* ************************************** */
/* Simulation Interface - Bullet */
/* Update object array and rigid body count so they're in sync with the rigid body group */
static void rigidbody_update_ob_array(RigidBodyWorld *rbw)
{
if (rbw->group == NULL) {
rbw->numbodies = 0;
rbw->objects = realloc(rbw->objects, 0);
return;
}
int n = 0;
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, object) {
(void)object;
/* Ignore if this object is the direct child of an object with a compound shape */
if (object->parent == NULL || object->parent->rigidbody_object == NULL ||
object->parent->rigidbody_object->shape != RB_SHAPE_COMPOUND) {
n++;
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
if (rbw->numbodies != n) {
rbw->numbodies = n;
rbw->objects = realloc(rbw->objects, sizeof(Object *) * rbw->numbodies);
}
int i = 0;
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, object) {
/* Ignore if this object is the direct child of an object with a compound shape */
if (object->parent == NULL || object->parent->rigidbody_object == NULL ||
object->parent->rigidbody_object->shape != RB_SHAPE_COMPOUND) {
rbw->objects[i] = object;
i++;
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
static void rigidbody_update_sim_world(Scene *scene, RigidBodyWorld *rbw)
{
float adj_gravity[3];
/* adjust gravity to take effector weights into account */
if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) {
copy_v3_v3(adj_gravity, scene->physics_settings.gravity);
mul_v3_fl(adj_gravity,
rbw->effector_weights->global_gravity * rbw->effector_weights->weight[0]);
}
else {
zero_v3(adj_gravity);
}
/* update gravity, since this RNA setting is not part of RigidBody settings */
RB_dworld_set_gravity(rbw->shared->physics_world, adj_gravity);
/* update object array in case there are changes */
rigidbody_update_ob_array(rbw);
}
static void rigidbody_update_sim_ob(Depsgraph *depsgraph, Object *ob, RigidBodyOb *rbo)
{
/* only update if rigid body exists */
if (rbo->shared->physics_object == NULL) {
return;
}
const Scene *scene = DEG_get_input_scene(depsgraph);
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
BKE_view_layer_synced_ensure(scene, view_layer);
Base *base = BKE_view_layer_base_find(view_layer, ob);
const bool is_selected = base ? (base->flag & BASE_SELECTED) != 0 : false;
if (rbo->shape == RB_SHAPE_TRIMESH && rbo->flag & RBO_FLAG_USE_DEFORM) {
Mesh *mesh = ob->runtime.mesh_deform_eval;
if (mesh) {
float(*positions)[3] = BKE_mesh_vert_positions_for_write(mesh);
int totvert = mesh->totvert;
const BoundBox *bb = BKE_object_boundbox_get(ob);
RB_shape_trimesh_update(rbo->shared->physics_shape,
(float *)positions,
totvert,
sizeof(float[3]),
bb->vec[0],
bb->vec[6]);
}
}
if (!(rbo->flag & RBO_FLAG_KINEMATIC)) {
/* update scale for all non kinematic objects */
float new_scale[3], old_scale[3];
mat4_to_size(new_scale, ob->object_to_world);
RB_body_get_scale(rbo->shared->physics_object, old_scale);
/* Avoid updating collision shape AABBs if scale didn't change. */
if (!compare_size_v3v3(old_scale, new_scale, 0.001f)) {
RB_body_set_scale(rbo->shared->physics_object, new_scale);
/* compensate for embedded convex hull collision margin */
if (!(rbo->flag & RBO_FLAG_USE_MARGIN) && rbo->shape == RB_SHAPE_CONVEXH) {
RB_shape_set_margin(rbo->shared->physics_shape,
RBO_GET_MARGIN(rbo) * MIN3(new_scale[0], new_scale[1], new_scale[2]));
}
}
}
/* Make transformed objects temporarily kinmatic
* so that they can be moved by the user during simulation. */
if (is_selected && (G.moving & G_TRANSFORM_OBJ)) {
RB_body_set_kinematic_state(rbo->shared->physics_object, true);
RB_body_set_mass(rbo->shared->physics_object, 0.0f);
}
/* NOTE: no other settings need to be explicitly updated here,
* since RNA setters take care of the rest :)
*/
}
/**
* Updates and validates world, bodies and shapes.
*
* \param rebuild: Rebuild entire simulation
*/
static void rigidbody_update_simulation(Depsgraph *depsgraph,
Scene *scene,
RigidBodyWorld *rbw,
bool rebuild)
{
/* update world */
/* Note physics_world can get NULL when undoing the deletion of the last object in it (see
* T70667). */
if (rebuild || rbw->shared->physics_world == NULL) {
BKE_rigidbody_validate_sim_world(scene, rbw, rebuild);
/* We have rebuilt the world so we need to make sure the rest is rebuilt as well. */
rebuild = true;
}
rigidbody_update_sim_world(scene, rbw);
/* XXX TODO: For rebuild: remove all constraints first.
* Otherwise we can end up deleting objects that are still
* referenced by constraints, corrupting bullet's internal list.
*
* Memory management needs redesign here, this is just a dirty workaround.
*/
if (rebuild && rbw->constraints) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->constraints, ob) {
RigidBodyCon *rbc = ob->rigidbody_constraint;
if (rbc && rbc->physics_constraint) {
RB_dworld_remove_constraint(rbw->shared->physics_world, rbc->physics_constraint);
RB_constraint_delete(rbc->physics_constraint);
rbc->physics_constraint = NULL;
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
/* update objects */
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, ob) {
if (ob->type == OB_MESH) {
/* validate that we've got valid object set up here... */
RigidBodyOb *rbo = ob->rigidbody_object;
/* TODO: remove this whole block once we are sure we never get NULL rbo here anymore. */
/* This cannot be done in CoW evaluation context anymore... */
if (rbo == NULL) {
BLI_assert_msg(0,
"CoW object part of RBW object collection without RB object data, "
"should not happen.\n");
/* Since this object is included in the sim group but doesn't have
* rigid body settings (perhaps it was added manually), add!
* - assume object to be active? That is the default for newly added settings...
*/
ob->rigidbody_object = BKE_rigidbody_create_object(scene, ob, RBO_TYPE_ACTIVE);
rigidbody_validate_sim_object(rbw, ob, true);
rbo = ob->rigidbody_object;
}
else {
/* perform simulation data updates as tagged */
/* refresh object... */
if (rebuild) {
/* World has been rebuilt so rebuild object */
/* TODO(Sybren): rigidbody_validate_sim_object() can call rigidbody_validate_sim_shape(),
* but neither resets the RBO_FLAG_NEEDS_RESHAPE flag nor
* calls RB_body_set_collision_shape().
* This results in the collision shape being created twice, which is unnecessary. */
rigidbody_validate_sim_object(rbw, ob, true);
}
else if (rbo->flag & RBO_FLAG_NEEDS_VALIDATE) {
rigidbody_validate_sim_object(rbw, ob, false);
}
/* refresh shape... */
if (rbo->flag & RBO_FLAG_NEEDS_RESHAPE) {
/* mesh/shape data changed, so force shape refresh */
rigidbody_validate_sim_shape(rbw, ob, true);
/* now tell RB sim about it */
/* XXX: we assume that this can only get applied for active/passive shapes
* that will be included as rigidbodies. */
if (rbo->shared->physics_object != NULL && rbo->shared->physics_shape != NULL) {
RB_body_set_collision_shape(rbo->shared->physics_object, rbo->shared->physics_shape);
}
}
}
rbo->flag &= ~(RBO_FLAG_NEEDS_VALIDATE | RBO_FLAG_NEEDS_RESHAPE);
/* update simulation object... */
rigidbody_update_sim_ob(depsgraph, ob, rbo);
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
/* update constraints */
if (rbw->constraints == NULL) { /* no constraints, move on */
return;
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->constraints, ob) {
/* validate that we've got valid object set up here... */
RigidBodyCon *rbc = ob->rigidbody_constraint;
/* TODO: remove this whole block once we are sure we never get NULL rbo here anymore. */
/* This cannot be done in CoW evaluation context anymore... */
if (rbc == NULL) {
BLI_assert_msg(0,
"CoW object part of RBW constraints collection without RB constraint data, "
"should not happen.\n");
/* Since this object is included in the group but doesn't have
* constraint settings (perhaps it was added manually), add!
*/
ob->rigidbody_constraint = BKE_rigidbody_create_constraint(scene, ob, RBC_TYPE_FIXED);
rigidbody_validate_sim_constraint(rbw, ob, true);
rbc = ob->rigidbody_constraint;
}
else {
/* perform simulation data updates as tagged */
if (rebuild) {
/* World has been rebuilt so rebuild constraint */
rigidbody_validate_sim_constraint(rbw, ob, true);
}
else if (rbc->flag & RBC_FLAG_NEEDS_VALIDATE) {
rigidbody_validate_sim_constraint(rbw, ob, false);
}
}
rbc->flag &= ~RBC_FLAG_NEEDS_VALIDATE;
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
typedef struct KinematicSubstepData {
RigidBodyOb *rbo;
float old_pos[3];
float new_pos[3];
float old_rot[4];
float new_rot[4];
bool scale_changed;
float old_scale[3];
float new_scale[3];
} KinematicSubstepData;
static ListBase rigidbody_create_substep_data(RigidBodyWorld *rbw)
{
/* Objects that we want to update substep location/rotation for. */
ListBase substep_targets = {NULL, NULL};
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, ob) {
RigidBodyOb *rbo = ob->rigidbody_object;
/* only update if rigid body exists */
if (!rbo || rbo->shared->physics_object == NULL) {
continue;
}
if (rbo->flag & RBO_FLAG_KINEMATIC) {
float loc[3], rot[4], scale[3];
KinematicSubstepData *data = MEM_callocN(sizeof(KinematicSubstepData),
"RigidBody Substep data");
data->rbo = rbo;
RB_body_get_position(rbo->shared->physics_object, loc);
RB_body_get_orientation(rbo->shared->physics_object, rot);
RB_body_get_scale(rbo->shared->physics_object, scale);
copy_v3_v3(data->old_pos, loc);
copy_v4_v4(data->old_rot, rot);
copy_v3_v3(data->old_scale, scale);
mat4_decompose(loc, rot, scale, ob->object_to_world);
copy_v3_v3(data->new_pos, loc);
copy_v4_v4(data->new_rot, rot);
copy_v3_v3(data->new_scale, scale);
data->scale_changed = !compare_size_v3v3(data->old_scale, data->new_scale, 0.001f);
LinkData *ob_link = BLI_genericNodeN(data);
BLI_addtail(&substep_targets, ob_link);
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
return substep_targets;
}
static void rigidbody_update_kinematic_obj_substep(ListBase *substep_targets, float interp_fac)
{
LISTBASE_FOREACH (LinkData *, link, substep_targets) {
KinematicSubstepData *data = link->data;
RigidBodyOb *rbo = data->rbo;
float loc[3], rot[4];
interp_v3_v3v3(loc, data->old_pos, data->new_pos, interp_fac);
interp_qt_qtqt(rot, data->old_rot, data->new_rot, interp_fac);
RB_body_activate(rbo->shared->physics_object);
RB_body_set_loc_rot(rbo->shared->physics_object, loc, rot);
if (!data->scale_changed) {
/* Avoid having to rebuild the collision shape AABBs if scale didn't change. */
continue;
}
float scale[3];
interp_v3_v3v3(scale, data->old_scale, data->new_scale, interp_fac);
RB_body_set_scale(rbo->shared->physics_object, scale);
/* compensate for embedded convex hull collision margin */
if (!(rbo->flag & RBO_FLAG_USE_MARGIN) && rbo->shape == RB_SHAPE_CONVEXH) {
RB_shape_set_margin(rbo->shared->physics_shape,
RBO_GET_MARGIN(rbo) * MIN3(scale[0], scale[1], scale[2]));
}
}
}
static void rigidbody_update_external_forces(Depsgraph *depsgraph,
Scene *scene,
RigidBodyWorld *rbw)
{
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, ob) {
/* only update if rigid body exists */
RigidBodyOb *rbo = ob->rigidbody_object;
if (ob->type != OB_MESH || rbo->shared->physics_object == NULL) {
continue;
}
/* update influence of effectors - but don't do it on an effector */
/* only dynamic bodies need effector update */
if (rbo->type == RBO_TYPE_ACTIVE &&
((ob->pd == NULL) || (ob->pd->forcefield == PFIELD_NULL))) {
EffectorWeights *effector_weights = rbw->effector_weights;
EffectedPoint epoint;
ListBase *effectors;
/* get effectors present in the group specified by effector_weights */
effectors = BKE_effectors_create(depsgraph, ob, NULL, effector_weights, false);
if (effectors) {
float eff_force[3] = {0.0f, 0.0f, 0.0f};
float eff_loc[3], eff_vel[3];
/* create dummy 'point' which represents last known position of object as result of sim
*/
/* XXX: this can create some inaccuracies with sim position,
* but is probably better than using un-simulated values? */
RB_body_get_position(rbo->shared->physics_object, eff_loc);
RB_body_get_linear_velocity(rbo->shared->physics_object, eff_vel);
pd_point_from_loc(scene, eff_loc, eff_vel, 0, &epoint);
/* Calculate net force of effectors, and apply to sim object:
* - we use 'central force' since apply force requires a "relative position"
* which we don't have... */
BKE_effectors_apply(effectors, NULL, effector_weights, &epoint, eff_force, NULL, NULL);
if (G.f & G_DEBUG) {
printf("\tapplying force (%f,%f,%f) to '%s'\n",
eff_force[0],
eff_force[1],
eff_force[2],
ob->id.name + 2);
}
/* activate object in case it is deactivated */
if (!is_zero_v3(eff_force)) {
RB_body_activate(rbo->shared->physics_object);
}
RB_body_apply_central_force(rbo->shared->physics_object, eff_force);
}
else if (G.f & G_DEBUG) {
printf("\tno forces to apply to '%s'\n", ob->id.name + 2);
}
/* cleanup */
BKE_effectors_free(effectors);
}
/* NOTE: passive objects don't need to be updated since they don't move */
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
static void rigidbody_free_substep_data(ListBase *substep_targets)
{
LISTBASE_FOREACH (LinkData *, link, substep_targets) {
KinematicSubstepData *data = link->data;
MEM_freeN(data);
}
BLI_freelistN(substep_targets);
}
static void rigidbody_update_simulation_post_step(Depsgraph *depsgraph, RigidBodyWorld *rbw)
{
const Scene *scene = DEG_get_input_scene(depsgraph);
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
BKE_view_layer_synced_ensure(scene, view_layer);
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, ob) {
Base *base = BKE_view_layer_base_find(view_layer, ob);
RigidBodyOb *rbo = ob->rigidbody_object;
/* Reset kinematic state for transformed objects. */
if (rbo && base && (base->flag & BASE_SELECTED) && (G.moving & G_TRANSFORM_OBJ) &&
rbo->shared->physics_object) {
RB_body_set_kinematic_state(rbo->shared->physics_object,
rbo->flag & RBO_FLAG_KINEMATIC || rbo->flag & RBO_FLAG_DISABLED);
RB_body_set_mass(rbo->shared->physics_object, RBO_GET_MASS(rbo));
/* Deactivate passive objects so they don't interfere with deactivation of active objects. */
if (rbo->type == RBO_TYPE_PASSIVE) {
RB_body_deactivate(rbo->shared->physics_object);
}
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
bool BKE_rigidbody_check_sim_running(RigidBodyWorld *rbw, float ctime)
{
return (rbw && (rbw->flag & RBW_FLAG_MUTED) == 0 && ctime > rbw->shared->pointcache->startframe);
}
void BKE_rigidbody_sync_transforms(RigidBodyWorld *rbw, Object *ob, float ctime)
{
if (!BKE_rigidbody_is_affected_by_simulation(ob)) {
/* Don't sync transforms for objects that are not affected/changed by the simulation. */
return;
}
RigidBodyOb *rbo = ob->rigidbody_object;
/* use rigid body transform after cache start frame if objects is not being transformed */
if (BKE_rigidbody_check_sim_running(rbw, ctime) &&
!(ob->base_flag & BASE_SELECTED && G.moving & G_TRANSFORM_OBJ)) {
float mat[4][4], size_mat[4][4], size[3];
normalize_qt(rbo->orn); /* RB_TODO investigate why quaternion isn't normalized at this point */
quat_to_mat4(mat, rbo->orn);
copy_v3_v3(mat[3], rbo->pos);
mat4_to_size(size, ob->object_to_world);
size_to_mat4(size_mat, size);
mul_m4_m4m4(mat, mat, size_mat);
copy_m4_m4(ob->object_to_world, mat);
}
/* otherwise set rigid body transform to current obmat */
else {
mat4_to_loc_quat(rbo->pos, rbo->orn, ob->object_to_world);
}
}
void BKE_rigidbody_aftertrans_update(
Object *ob, float loc[3], float rot[3], float quat[4], float rotAxis[3], float rotAngle)
{
bool correct_delta = BKE_rigidbody_is_affected_by_simulation(ob);
RigidBodyOb *rbo = ob->rigidbody_object;
/* return rigid body and object to their initial states */
copy_v3_v3(rbo->pos, ob->loc);
copy_v3_v3(ob->loc, loc);
if (correct_delta) {
add_v3_v3(rbo->pos, ob->dloc);
}
if (ob->rotmode > 0) {
float qt[4];
eulO_to_quat(qt, ob->rot, ob->rotmode);
if (correct_delta) {
float dquat[4];
eulO_to_quat(dquat, ob->drot, ob->rotmode);
mul_qt_qtqt(rbo->orn, dquat, qt);
}
else {
copy_qt_qt(rbo->orn, qt);
}
copy_v3_v3(ob->rot, rot);
}
else if (ob->rotmode == ROT_MODE_AXISANGLE) {
float qt[4];
axis_angle_to_quat(qt, ob->rotAxis, ob->rotAngle);
if (correct_delta) {
float dquat[4];
axis_angle_to_quat(dquat, ob->drotAxis, ob->drotAngle);
mul_qt_qtqt(rbo->orn, dquat, qt);
}
else {
copy_qt_qt(rbo->orn, qt);
}
copy_v3_v3(ob->rotAxis, rotAxis);
ob->rotAngle = rotAngle;
}
else {
if (correct_delta) {
mul_qt_qtqt(rbo->orn, ob->dquat, ob->quat);
}
else {
copy_qt_qt(rbo->orn, ob->quat);
}
copy_qt_qt(ob->quat, quat);
}
if (rbo->shared->physics_object) {
/* allow passive objects to return to original transform */
if (rbo->type == RBO_TYPE_PASSIVE) {
RB_body_set_kinematic_state(rbo->shared->physics_object, true);
}
RB_body_set_loc_rot(rbo->shared->physics_object, rbo->pos, rbo->orn);
}
/* RB_TODO update rigid body physics object's loc/rot for dynamic objects here as well
* (needs to be done outside bullet's update loop). */
}
void BKE_rigidbody_cache_reset(RigidBodyWorld *rbw)
{
if (rbw) {
rbw->shared->pointcache->flag |= PTCACHE_OUTDATED;
}
}
/* ------------------ */
void BKE_rigidbody_rebuild_world(Depsgraph *depsgraph, Scene *scene, float ctime)
{
RigidBodyWorld *rbw = scene->rigidbody_world;
PointCache *cache;
PTCacheID pid;
int startframe, endframe;
BKE_ptcache_id_from_rigidbody(&pid, NULL, rbw);
BKE_ptcache_id_time(&pid, scene, ctime, &startframe, &endframe, NULL);
cache = rbw->shared->pointcache;
/* Flag cache as outdated if we don't have a world or number of objects
* in the simulation has changed. */
int n = 0;
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (rbw->group, object) {
(void)object;
/* Ignore if this object is the direct child of an object with a compound shape */
if (object->parent == NULL || object->parent->rigidbody_object == NULL ||
object->parent->rigidbody_object->shape != RB_SHAPE_COMPOUND) {
n++;
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
if (rbw->shared->physics_world == NULL || rbw->numbodies != n) {
cache->flag |= PTCACHE_OUTDATED;
}
if (ctime == startframe + 1 && rbw->ltime == startframe) {
if (cache->flag & PTCACHE_OUTDATED) {
BKE_ptcache_id_reset(scene, &pid, PTCACHE_RESET_OUTDATED);
rigidbody_update_simulation(depsgraph, scene, rbw, true);
BKE_ptcache_validate(cache, (int)ctime);
cache->last_exact = 0;
cache->flag &= ~PTCACHE_REDO_NEEDED;
}
}
}
void BKE_rigidbody_do_simulation(Depsgraph *depsgraph, Scene *scene, float ctime)
{
RigidBodyWorld *rbw = scene->rigidbody_world;
PointCache *cache;
PTCacheID pid;
int startframe, endframe;
BKE_ptcache_id_from_rigidbody(&pid, NULL, rbw);
BKE_ptcache_id_time(&pid, scene, ctime, &startframe, &endframe, NULL);
cache = rbw->shared->pointcache;
if (ctime <= startframe) {
rbw->ltime = startframe;
return;
}
/* make sure we don't go out of cache frame range */
if (ctime > endframe) {
ctime = endframe;
}
/* don't try to run the simulation if we don't have a world yet but allow reading baked cache */
if (rbw->shared->physics_world == NULL && !(cache->flag & PTCACHE_BAKED)) {
return;
}
if (rbw->objects == NULL) {
rigidbody_update_ob_array(rbw);
}
/* try to read from cache */
/* RB_TODO deal with interpolated, old and baked results */
bool can_simulate = (ctime == rbw->ltime + 1) && !(cache->flag & PTCACHE_BAKED);
if (BKE_ptcache_read(&pid, ctime, can_simulate) == PTCACHE_READ_EXACT) {
BKE_ptcache_validate(cache, (int)ctime);
rbw->ltime = ctime;
return;
}
if (!DEG_is_active(depsgraph)) {
/* When the depsgraph is inactive we should neither write to the cache
* nor run the simulation. */
return;
}
/* advance simulation, we can only step one frame forward */
if (compare_ff_relative(ctime, rbw->ltime + 1, FLT_EPSILON, 64)) {
/* write cache for first frame when on second frame */
if (rbw->ltime == startframe && (cache->flag & PTCACHE_OUTDATED || cache->last_exact == 0)) {
BKE_ptcache_write(&pid, startframe);
}
const float frame_diff = ctime - rbw->ltime;
/* calculate how much time elapsed since last step in seconds */
const float timestep = 1.0f / (float)FPS * frame_diff * rbw->time_scale;
const float substep = timestep / rbw->substeps_per_frame;
ListBase kinematic_substep_targets = rigidbody_create_substep_data(rbw);
const float interp_step = 1.0f / rbw->substeps_per_frame;
float cur_interp_val = interp_step;
/* update and validate simulation */
rigidbody_update_simulation(depsgraph, scene, rbw, false);
for (int i = 0; i < rbw->substeps_per_frame; i++) {
rigidbody_update_external_forces(depsgraph, scene, rbw);
rigidbody_update_kinematic_obj_substep(&kinematic_substep_targets, cur_interp_val);
RB_dworld_step_simulation(rbw->shared->physics_world, substep, 0, substep);
cur_interp_val += interp_step;
}
rigidbody_free_substep_data(&kinematic_substep_targets);
rigidbody_update_simulation_post_step(depsgraph, rbw);
/* write cache for current frame */
BKE_ptcache_validate(cache, (int)ctime);
BKE_ptcache_write(&pid, (uint)ctime);
rbw->ltime = ctime;
}
}
/* ************************************** */
#else /* WITH_BULLET */
/* stubs */
# if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
# endif
void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src, const int flag)
{
}
void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, bool rebuild)
{
}
void BKE_rigidbody_calc_volume(Object *ob, float *r_vol)
{
if (r_vol) {
*r_vol = 0.0f;
}
}
void BKE_rigidbody_calc_center_of_mass(Object *ob, float r_center[3])
{
zero_v3(r_center);
}
struct RigidBodyWorld *BKE_rigidbody_create_world(Scene *scene)
{
return NULL;
}
struct RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag)
{
return NULL;
}
void BKE_rigidbody_world_groups_relink(struct RigidBodyWorld *rbw)
{
}
void BKE_rigidbody_world_id_loop(struct RigidBodyWorld *rbw,
RigidbodyWorldIDFunc func,
void *userdata)
{
}
struct RigidBodyOb *BKE_rigidbody_create_object(Scene *scene, Object *ob, short type)
{
return NULL;
}
struct RigidBodyCon *BKE_rigidbody_create_constraint(Scene *scene, Object *ob, short type)
{
return NULL;
}
struct RigidBodyWorld *BKE_rigidbody_get_world(Scene *scene)
{
return NULL;
}
void BKE_rigidbody_ensure_local_object(Main *bmain, Object *ob)
{
}
bool BKE_rigidbody_add_object(Main *bmain, Scene *scene, Object *ob, int type, ReportList *reports)
{
BKE_report(reports, RPT_ERROR, "Compiled without Bullet physics engine");
return false;
}
void BKE_rigidbody_remove_object(struct Main *bmain, Scene *scene, Object *ob, const bool free_us)
{
}
void BKE_rigidbody_remove_constraint(Main *bmain, Scene *scene, Object *ob, const bool free_us)
{
}
void BKE_rigidbody_sync_transforms(RigidBodyWorld *rbw, Object *ob, float ctime)
{
}
void BKE_rigidbody_aftertrans_update(
Object *ob, float loc[3], float rot[3], float quat[4], float rotAxis[3], float rotAngle)
{
}
bool BKE_rigidbody_check_sim_running(RigidBodyWorld *rbw, float ctime)
{
return false;
}
void BKE_rigidbody_cache_reset(RigidBodyWorld *rbw)
{
}
void BKE_rigidbody_rebuild_world(Depsgraph *depsgraph, Scene *scene, float ctime)
{
}
void BKE_rigidbody_do_simulation(Depsgraph *depsgraph, Scene *scene, float ctime)
{
}
void BKE_rigidbody_objects_collection_validate(Scene *scene, RigidBodyWorld *rbw)
{
}
void BKE_rigidbody_constraints_collection_validate(Scene *scene, RigidBodyWorld *rbw)
{
}
void BKE_rigidbody_main_collection_object_add(Main *bmain, Collection *collection, Object *object)
{
}
# if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic pop
# endif
#endif /* WITH_BULLET */
/* -------------------- */
/* Depsgraph evaluation */
void BKE_rigidbody_rebuild_sim(Depsgraph *depsgraph, Scene *scene)
{
float ctime = DEG_get_ctime(depsgraph);
DEG_debug_print_eval_time(depsgraph, __func__, scene->id.name, scene, ctime);
/* rebuild sim data (i.e. after resetting to start of timeline) */
if (BKE_scene_check_rigidbody_active(scene)) {
BKE_rigidbody_rebuild_world(depsgraph, scene, ctime);
}
}
void BKE_rigidbody_eval_simulation(Depsgraph *depsgraph, Scene *scene)
{
float ctime = DEG_get_ctime(depsgraph);
DEG_debug_print_eval_time(depsgraph, __func__, scene->id.name, scene, ctime);
/* evaluate rigidbody sim */
if (!BKE_scene_check_rigidbody_active(scene)) {
return;
}
BKE_rigidbody_do_simulation(depsgraph, scene, ctime);
}
void BKE_rigidbody_object_sync_transforms(Depsgraph *depsgraph, Scene *scene, Object *ob)
{
RigidBodyWorld *rbw = scene->rigidbody_world;
float ctime = DEG_get_ctime(depsgraph);
DEG_debug_print_eval_time(depsgraph, __func__, ob->id.name, ob, ctime);
/* read values pushed into RBO from sim/cache... */
BKE_rigidbody_sync_transforms(rbw, ob, ctime);
}