/* * ***** BEGIN GPL LICENSE BLOCK ***** * * 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) 2013 Blender Foundation * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): Joshua Leung, Sergej Reich * * ***** END GPL LICENSE BLOCK ***** */ /** \file rigidbody.c * \ingroup blenkernel * \brief Blender-side interface and methods for dealing with Rigid Body simulations */ #include #include #include #include #include #include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "RBI_api.h" #include "DNA_anim_types.h" #include "DNA_group_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_object_force.h" #include "DNA_rigidbody_types.h" #include "DNA_scene_types.h" #include "BKE_animsys.h" #include "BKE_cdderivedmesh.h" #include "BKE_effect.h" #include "BKE_group.h" #include "BKE_object.h" #include "BKE_mesh.h" #include "BKE_pointcache.h" #include "BKE_rigidbody.h" #include "BKE_global.h" #include "BKE_utildefines.h" #include "RNA_access.h" /* ************************************** */ /* Memory Management */ /* Freeing Methods --------------------- */ /* Free rigidbody world */ void BKE_rigidbody_free_world(RigidBodyWorld *rbw) { GroupObject *go; /* sanity check */ if (!rbw) return; if (rbw->physics_world) { /* free physics references, we assume that all physics objects in will have been added to the world */ if (rbw->group) { for (go = rbw->group->gobject.first; go; go = go->next) { if (go->ob && go->ob->rigidbody_object) { RigidBodyOb *rbo = go->ob->rigidbody_object; if (rbo->physics_object) RB_dworld_remove_body(rbw->physics_world, rbo->physics_object); } } } /* free dynamics world */ RB_dworld_delete(rbw->physics_world); } if (rbw->objects) free(rbw->objects); /* free rigidbody world itself */ MEM_freeN(rbw); } /* Free RigidBody settings and sim instances */ void BKE_rigidbody_free_object(Object *ob) { RigidBodyOb *rbo = (ob) ? ob->rigidbody_object : NULL; /* sanity check */ if (rbo == NULL) return; /* free physics references */ if (rbo->physics_object) { RB_body_delete(rbo->physics_object); rbo->physics_object = NULL; } if (rbo->physics_shape) { RB_shape_delete(rbo->physics_shape); rbo->physics_shape = NULL; } /* free data itself */ MEM_freeN(rbo); ob->rigidbody_object = NULL; } /* 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... */ RigidBodyOb *BKE_rigidbody_copy_object(Object *ob) { RigidBodyOb *rboN = NULL; if (ob->rigidbody_object) { /* just duplicate the whole struct first (to catch all the settings) */ rboN = MEM_dupallocN(ob->rigidbody_object); /* tag object as needing to be verified */ rboN->flag |= RBO_FLAG_NEEDS_VALIDATE; /* clear out all the fields which need to be revalidated later */ rboN->physics_object = NULL; rboN->physics_shape = NULL; } /* return new copy of settings */ return rboN; } /* ************************************** */ /* Setup Utilities - Validate Sim Instances */ /* 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 *me = NULL; if (ob->type == OB_MESH && ob->data) { me = ob->data; } else { printf("ERROR: cannot make Convex Hull collision shape for non-Mesh object\n"); } if (me && me->totvert) { shape = RB_shape_new_convex_hull((float *)me->mvert, sizeof(MVert), me->totvert, margin, can_embed); } else { printf("ERROR: no vertices to define Convex Hull collision shape with\n"); } 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) { DerivedMesh *dm = CDDM_from_mesh(ob->data, ob); MVert *mvert; MFace *mface; int totvert; int totface; /* ensure mesh validity, then grab data */ DM_ensure_tessface(dm); mvert = (dm) ? dm->getVertArray(dm) : NULL; totvert = (dm) ? dm->getNumVerts(dm) : 0; mface = (dm) ? dm->getTessFaceArray(dm) : NULL; totface = (dm) ? dm->getNumTessFaces(dm) : 0; /* sanity checking - potential case when no data will be present */ if ((totvert == 0) || (totface == 0)) { printf("WARNING: no geometry data converted for Mesh Collision Shape (ob = %s)\n", ob->id.name + 2); } else { rbMeshData *mdata; int i; /* init mesh data for collision shape */ mdata = RB_trimesh_data_new(); /* loop over all faces, adding them as triangles to the collision shape * (so for some faces, more than triangle will get added) */ for (i = 0; (i < totface) && (mface) && (mvert); i++, mface++) { /* add first triangle - verts 1,2,3 */ { MVert *va = (IN_RANGE(mface->v1, 0, totvert)) ? (mvert + mface->v1) : (mvert); MVert *vb = (IN_RANGE(mface->v2, 0, totvert)) ? (mvert + mface->v2) : (mvert); MVert *vc = (IN_RANGE(mface->v3, 0, totvert)) ? (mvert + mface->v3) : (mvert); RB_trimesh_add_triangle(mdata, va->co, vb->co, vc->co); } /* add second triangle if needed - verts 1,3,4 */ if (mface->v4) { MVert *va = (IN_RANGE(mface->v1, 0, totvert)) ? (mvert + mface->v1) : (mvert); MVert *vb = (IN_RANGE(mface->v3, 0, totvert)) ? (mvert + mface->v3) : (mvert); MVert *vc = (IN_RANGE(mface->v4, 0, totvert)) ? (mvert + mface->v4) : (mvert); RB_trimesh_add_triangle(mdata, va->co, vb->co, vc->co); } } /* 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); } } /* cleanup temp data */ if (dm) { dm->release(dm); } } else { printf("ERROR: cannot make Triangular Mesh collision shape for non-Mesh object\n"); } return shape; } /* Create new physics sim collision shape for object and store it, * or remove the existing one first and replace... */ void BKE_rigidbody_validate_sim_shape(Object *ob, short rebuild) { RigidBodyOb *rbo = ob->rigidbody_object; rbCollisionShape *new_shape = NULL; BoundBox *bb = 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; /* 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->physics_shape && !rebuild) return; /* 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 centralised in boundbox) */ // XXX: all dimensions are auto-determined now... later can add stored settings for this /* get object dimensions without scaling */ 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 (ELEM3(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 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 emged collision margin */ if (!(rbo->flag & RBO_FLAG_USE_MARGIN)) 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) ? 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; } /* assign new collision shape if creation was successful */ if (new_shape) { if (rbo->physics_shape) RB_shape_delete(rbo->physics_shape); rbo->physics_shape = new_shape; RB_shape_set_margin(rbo->physics_shape, RBO_GET_MARGIN(rbo)); } } /* --------------------- */ /* Create physics sim representation of object given RigidBody settings * < rebuild: even if an instance already exists, replace it */ void BKE_rigidbody_validate_sim_object(RigidBodyWorld *rbw, Object *ob, short 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 */ if (rbo->physics_shape == NULL || rebuild) BKE_rigidbody_validate_sim_shape(ob, true); if (rbo->physics_object) { if (rebuild == false) RB_dworld_remove_body(rbw->physics_world, rbo->physics_object); } if (!rbo->physics_object || rebuild) { /* remove rigid body if it already exists before creating a new one */ if (rbo->physics_object) { RB_body_delete(rbo->physics_object); } mat4_to_loc_quat(loc, rot, ob->obmat); rbo->physics_object = RB_body_new(rbo->physics_shape, loc, rot); RB_body_set_friction(rbo->physics_object, rbo->friction); RB_body_set_restitution(rbo->physics_object, rbo->restitution); RB_body_set_damping(rbo->physics_object, rbo->lin_damping, rbo->ang_damping); RB_body_set_sleep_thresh(rbo->physics_object, rbo->lin_sleep_thresh, rbo->ang_sleep_thresh); RB_body_set_activation_state(rbo->physics_object, rbo->flag & RBO_FLAG_USE_DEACTIVATION); if (rbo->type == RBO_TYPE_PASSIVE || rbo->flag & RBO_FLAG_START_DEACTIVATED) RB_body_deactivate(rbo->physics_object); RB_body_set_linear_factor(rbo->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->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->physics_object, RBO_GET_MASS(rbo)); RB_body_set_kinematic_state(rbo->physics_object, rbo->flag & RBO_FLAG_KINEMATIC || rbo->flag & RBO_FLAG_DISABLED); } if (rbw && rbw->physics_world) RB_dworld_add_body(rbw->physics_world, rbo->physics_object, rbo->col_groups); } /* --------------------- */ /* Create physics sim world given RigidBody world settings */ // NOTE: this does NOT update object references that the scene uses, in case those aren't ready yet! void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, short rebuild) { /* sanity checks */ if (rbw == NULL) return; /* create new sim world */ if (rebuild || rbw->physics_world == NULL) { if (rbw->physics_world) RB_dworld_delete(rbw->physics_world); rbw->physics_world = RB_dworld_new(scene->physics_settings.gravity); } RB_dworld_set_solver_iterations(rbw->physics_world, rbw->num_solver_iterations); RB_dworld_set_split_impulse(rbw->physics_world, rbw->flag & RBW_FLAG_USE_SPLIT_IMPULSE); } /* ************************************** */ /* Setup Utilities - Create Settings Blocks */ /* Set up RigidBody world */ 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"); /* set default settings */ rbw->ltime = PSFRA; rbw->time_scale = 1.0f; rbw->steps_per_second = 60; /* Bullet default (60 Hz) */ rbw->num_solver_iterations = 10; /* 10 is bullet default */ /* return this sim world */ return rbw; } /* Add rigid body settings to the specified object */ 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 || (ob->rigidbody_object != NULL)) return NULL; /* create new settings data, and link it up */ rbo = MEM_callocN(sizeof(RigidBodyOb), "RigidBodyOb"); /* 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; /* 0.04 is game engine default */ rbo->ang_damping = 0.1f; /* 0.1 is game engine default */ 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; /* set initial transform */ mat4_to_loc_quat(rbo->pos, rbo->orn, ob->obmat); /* flag cache as outdated */ BKE_rigidbody_cache_reset(rbw); /* return this object */ return rbo; } /* ************************************** */ /* Utilities API */ /* Get RigidBody world for the given scene, creating one if needed * < scene: Scene to find active Rigid Body world for */ RigidBodyWorld *BKE_rigidbody_get_world(Scene *scene) { /* sanity check */ if (scene == NULL) return NULL; return scene->rigidbody_world; } void BKE_rigidbody_remove_object(Scene *scene, Object *ob) { RigidBodyWorld *rbw = scene->rigidbody_world; RigidBodyOb *rbo = ob->rigidbody_object; GroupObject *go; int i; if (rbw) { /* remove from rigidbody world, free object won't do this */ if (rbw->physics_world && rbo->physics_object) RB_dworld_remove_body(rbw->physics_world, rbo->physics_object); /* remove object from array */ if (rbw && rbw->objects) { for (i = 0; i < rbw->numbodies; i++) { if (rbw->objects[i] == ob) { rbw->objects[i] = NULL; break; } } } } /* remove object's settings */ BKE_rigidbody_free_object(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) { GroupObject *go; int i, n; n = BLI_countlist(&rbw->group->gobject); if (rbw->numbodies != n) { rbw->numbodies = n; rbw->objects = realloc(rbw->objects, sizeof(Object *) * rbw->numbodies); } for (go = rbw->group->gobject.first, i = 0; go; go = go->next, i++) { Object *ob = go->ob; rbw->objects[i] = ob; } } 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); } else { zero_v3(adj_gravity); } /* update gravity, since this RNA setting is not part of RigidBody settings */ RB_dworld_set_gravity(rbw->physics_world, adj_gravity); /* update object array in case there are changes */ rigidbody_update_ob_array(rbw); } static void rigidbody_update_sim_ob(Scene *scene, RigidBodyWorld *rbw, Object *ob, RigidBodyOb *rbo) { float loc[3]; float rot[4]; float scale[3]; /* only update if rigid body exists */ if (rbo->physics_object == NULL) return; mat4_decompose(loc, rot, scale, ob->obmat); /* update scale for all objects */ RB_body_set_scale(rbo->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->physics_shape, RBO_GET_MARGIN(rbo) * MIN3(scale[0], scale[1], scale[2])); /* update rigid body location and rotation for kinematic bodies */ if (rbo->flag & RBO_FLAG_KINEMATIC) { RB_body_activate(rbo->physics_object); RB_body_set_loc_rot(rbo->physics_object, loc, rot); } /* NOTE: passive objects don't need to be updated since they don't move */ /* 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. * < rebuild: rebuild entire simulation */ static void rigidbody_update_simulation(Scene *scene, RigidBodyWorld *rbw, int rebuild) { GroupObject *go; /* update world */ if (rebuild) BKE_rigidbody_validate_sim_world(scene, rbw, true); rigidbody_update_sim_world(scene, rbw); /* update objects */ for (go = rbw->group->gobject.first; go; go = go->next) { Object *ob = go->ob; if (ob && ob->type == OB_MESH) { /* validate that we've got valid object set up here... */ RigidBodyOb *rbo = ob->rigidbody_object; /* update transformation matrix of the object so we don't get a frame of lag for simple animations */ BKE_object_where_is_calc(scene, ob); if (rbo == NULL) { /* 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); BKE_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 */ BKE_rigidbody_validate_sim_object(rbw, ob, true); } else if (rbo->flag & RBO_FLAG_NEEDS_VALIDATE) { BKE_rigidbody_validate_sim_object(rbw, ob, false); } /* refresh shape... */ if (rbo->flag & RBO_FLAG_NEEDS_RESHAPE) { /* mesh/shape data changed, so force shape refresh */ BKE_rigidbody_validate_sim_shape(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 RB_body_set_collision_shape(rbo->physics_object, rbo->physics_shape); } rbo->flag &= ~(RBO_FLAG_NEEDS_VALIDATE | RBO_FLAG_NEEDS_RESHAPE); } /* update simulation object... */ rigidbody_update_sim_ob(scene, rbw, ob, rbo); } } } /* Sync rigid body and object transformations */ void BKE_rigidbody_sync_transforms(Scene *scene, Object *ob, float ctime) { // RB_TODO implement this } void BKE_rigidbody_cache_reset(RigidBodyWorld *rbw) { // RB_TODO implement this } /* ------------------ */ /* Run RigidBody simulation for the specified physics world */ void BKE_rigidbody_do_simulation(Scene *scene, float ctime) { // RB_TODO implement this } /* ************************************** */