This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/boids.c

1526 lines
43 KiB
C
Raw Normal View History

Initial code for boids v2 Too many new features to list! But here are the biggies: - Boids can move on air and/or land, or climb a goal object. - Proper interaction with collision objects. * Closest collision object in negative z direction is considered as ground. * Other collision objects are obstacles and boids collide with them. - Boid behavior rules are now added to a dynamic list. * Many new rules and many still not implemented. * Different rule evaluation modes (fuzzy, random, average). - Only particle systems defined by per system "boid relations" are considered for simulation of that system. * This is in addition to the boids own system of course. * Relations define other systems as "neutral", "friend" or "enemy". - All effectors now effect boid physics, not boid brains. * This allows forcing boids somewhere. * Exception to this is new "boid" effector, which defines boid predators (positive strength) and goals (negative strength). Known issue: - Boid health isn't yet stored in pointcache so simulations with "fight" rule are not be read from cache properly. - Object/Group visualization object's animation is not played in "particle time". This is definately the wanted behavior, but isn't possible with the current state of dupliobject code. Other new features: - Particle systems can now be named separately from particle settings. * Default name for particle settings is now "ParticleSettings" instead of "PSys" - Per particle system list of particle effector weights. * Enables different effection strengths for particles from different particle systems with without messing around with effector group setting. Other code changes: - KDTree now supports range search as it's needed for new boids. - "Keyed particle targets" renamed as general "particle targets", as they're needed for boids too. (this might break some files saved with new keyed particles) Bug fixes: - Object & group visualizations didn't work. - Interpolating pointcache didn't do rotation.
2009-07-20 23:52:53 +00:00
/* boids.c
*
*
* $Id$
*
* ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* The Original Code is Copyright (C) 2009 by Janne Karhu.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): none yet.
*
* ***** END GPL LICENSE BLOCK *****
*/
#include <string.h>
#include <math.h>
#include "MEM_guardedalloc.h"
#include "DNA_particle_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_force.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_boid_types.h"
#include "DNA_listBase.h"
#include "BLI_rand.h"
#include "BLI_arithb.h"
#include "BLI_blenlib.h"
#include "BLI_kdtree.h"
#include "BLI_kdopbvh.h"
#include "BKE_effect.h"
#include "BKE_boids.h"
#include "BKE_particle.h"
#include "BKE_utildefines.h"
#include "BKE_modifier.h"
#include "RNA_enum_types.h"
typedef struct BoidValues {
float max_speed, max_acc;
float max_ave, min_speed;
float personal_space, jump_speed;
} BoidValues;
static int apply_boid_rule(BoidBrainData *bbd, BoidRule *rule, BoidValues *val, ParticleData *pa, float fuzziness);
static int rule_none(BoidRule *rule, BoidBrainData *data, BoidValues *val, ParticleData *pa)
{
return 0;
}
static int rule_goal_avoid(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
BoidRuleGoalAvoid *gabr = (BoidRuleGoalAvoid*) rule;
BoidSettings *boids = bbd->part->boids;
ParticleEffectorCache *ec;
Object *priority_ob = NULL;
float vec[3] = {0.0f, 0.0f, 0.0f}, loc[3] = {0.0f, 0.0f, 0.0f};
float mul = (rule->type == eBoidRuleType_Avoid ? 1.0 : -1.0);
float priority = 0.0f, len;
int ret = 0;
/* first find out goal/predator with highest priority */
/* if rule->ob specified use it */
if(gabr->ob && (rule->type != eBoidRuleType_Goal || gabr->ob != pa->stick_ob)) {
PartDeflect *pd = gabr->ob->pd;
float vec_to_part[3];
if(pd && pd->forcefield == PFIELD_BOID) {
effector_find_co(bbd->scene, pa->prev_state.co, NULL, gabr->ob, pd, loc, vec, NULL, NULL);
VecSubf(vec_to_part, pa->prev_state.co, loc);
priority = mul * pd->f_strength * effector_falloff(pd, vec, vec_to_part);
}
else
priority = 1.0;
priority = 1.0;
priority_ob = gabr->ob;
}
else for(ec=bbd->psys->effectors.first; ec; ec=ec->next) {
if(ec->type & PSYS_EC_EFFECTOR) {
Object *eob = ec->ob;
PartDeflect *pd = eob->pd;
/* skip current object */
if(rule->type == eBoidRuleType_Goal && eob == pa->stick_ob)
continue;
if(pd->forcefield == PFIELD_BOID && mul * pd->f_strength > 0.0f) {
float vec_to_part[3], temp;
effector_find_co(bbd->scene, pa->prev_state.co, NULL, eob, pd, loc, vec, NULL, NULL);
VecSubf(vec_to_part, pa->prev_state.co, loc);
temp = mul * pd->f_strength * effector_falloff(pd, vec, vec_to_part);
if(temp == 0.0f)
; /* do nothing */
else if(temp > priority) {
priority = temp;
priority_ob = eob;
len = VecLength(vec_to_part);
}
/* choose closest object with same priority */
else if(temp == priority) {
float len2 = VecLength(vec_to_part);
if(len2 < len) {
priority_ob = eob;
len = len2;
}
}
}
}
}
/* then use that effector */
if(priority > (rule->type==eBoidRuleType_Avoid ? gabr->fear_factor : 0.0f)) { /* with avoid, factor is "fear factor" */
Object *eob = priority_ob;
PartDeflect *pd = eob->pd;
float vec_to_part[3];
float surface = 0.0f;
float nor[3];
if(gabr->options & BRULE_GOAL_AVOID_PREDICT) {
/* estimate future location of target */
surface = (float)effector_find_co(bbd->scene, pa->prev_state.co, NULL, eob, pd, loc, nor, vec, NULL);
VecSubf(vec_to_part, pa->prev_state.co, loc);
len = Normalize(vec_to_part);
VecMulf(vec, len / (val->max_speed * bbd->timestep));
VecAddf(loc, loc, vec);
VecSubf(vec_to_part, pa->prev_state.co, loc);
}
else {
surface = (float)effector_find_co(bbd->scene, pa->prev_state.co, NULL, eob, pd, loc, nor, NULL, NULL);
VecSubf(vec_to_part, pa->prev_state.co, loc);
len = VecLength(vec_to_part);
}
if(rule->type == eBoidRuleType_Goal && boids->options & BOID_ALLOW_CLIMB && surface!=0.0f) {
if(!bbd->goal_ob || bbd->goal_priority < priority) {
bbd->goal_ob = eob;
VECCOPY(bbd->goal_co, loc);
VECCOPY(bbd->goal_nor, nor);
}
}
else if(rule->type == eBoidRuleType_Avoid && pa->boid->mode == eBoidMode_Climbing &&
priority > 2.0f * gabr->fear_factor) {
/* detach from surface and try to fly away from danger */
VECCOPY(vec_to_part, pa->r_ve);
VecMulf(vec_to_part, -1.0f);
}
VECCOPY(bbd->wanted_co, vec_to_part);
VecMulf(bbd->wanted_co, mul);
bbd->wanted_speed = val->max_speed * priority;
/* with goals factor is approach velocity factor */
if(rule->type == eBoidRuleType_Goal && boids->landing_smoothness > 0.0f) {
float len2 = 2.0f*VecLength(pa->prev_state.vel);
surface *= pa->size * boids->height;
if(len2 > 0.0f && len - surface < len2) {
len2 = (len - surface)/len2;
bbd->wanted_speed *= pow(len2, boids->landing_smoothness);
}
}
ret = 1;
}
return ret;
}
static int rule_avoid_collision(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
BoidRuleAvoidCollision *acbr = (BoidRuleAvoidCollision*) rule;
KDTreeNearest *ptn = NULL;
ParticleEffectorCache *ec;
ParticleTarget *pt;
float vec[3] = {0.0f, 0.0f, 0.0f}, loc[3] = {0.0f, 0.0f, 0.0f};
float co1[3], vel1[3], co2[3], vel2[3];
float len, t, inp, t_min = 2.0f;
int n, neighbors = 0, nearest = 0;
int ret = 0;
//check deflector objects first
if(acbr->options & BRULE_ACOLL_WITH_DEFLECTORS) {
ParticleCollision col;
BVHTreeRayHit hit;
float radius = val->personal_space * pa->size, ray_dir[3];
VECCOPY(col.co1, pa->prev_state.co);
VecAddf(col.co2, pa->prev_state.co, pa->prev_state.vel);
VecSubf(ray_dir, col.co2, col.co1);
VecMulf(ray_dir, acbr->look_ahead);
col.t = 0.0f;
hit.index = -1;
hit.dist = col.ray_len = VecLength(ray_dir);
/* find out closest deflector object */
for(ec=bbd->psys->effectors.first; ec; ec=ec->next) {
if(ec->type & PSYS_EC_DEFLECT) {
Object *eob = ec->ob;
/* don't check with current ground object */
if(eob == pa->stick_ob)
continue;
col.md = ( CollisionModifierData * ) ( modifiers_findByType ( eob, eModifierType_Collision ) );
col.ob_t = eob;
if(col.md && col.md->bvhtree)
BLI_bvhtree_ray_cast(col.md->bvhtree, col.co1, ray_dir, radius, &hit, particle_intersect_face, &col);
}
}
/* then avoid that object */
if(hit.index>=0) {
/* TODO: not totally happy with this part */
t = hit.dist/col.ray_len;
VECCOPY(bbd->wanted_co, col.nor);
VecMulf(bbd->wanted_co, (1.0f - t) * val->personal_space * pa->size);
bbd->wanted_speed = sqrt(t) * VecLength(pa->prev_state.vel);
return 1;
}
}
//check boids in own system
if(acbr->options & BRULE_ACOLL_WITH_BOIDS)
{
neighbors = BLI_kdtree_range_search(bbd->psys->tree, acbr->look_ahead * VecLength(pa->prev_state.vel), pa->prev_state.co, pa->prev_state.ave, &ptn);
if(neighbors > 1) for(n=1; n<neighbors; n++) {
VECCOPY(co1, pa->prev_state.co);
VECCOPY(vel1, pa->prev_state.vel);
VECCOPY(co2, (bbd->psys->particles + ptn[n].index)->prev_state.co);
VECCOPY(vel2, (bbd->psys->particles + ptn[n].index)->prev_state.vel);
VecSubf(loc, co1, co2);
VecSubf(vec, vel1, vel2);
inp = Inpf(vec,vec);
/* velocities not parallel */
if(inp != 0.0f) {
t = -Inpf(loc, vec)/inp;
/* cpa is not too far in the future so investigate further */
if(t > 0.0f && t < t_min) {
VECADDFAC(co1, co1, vel1, t);
VECADDFAC(co2, co2, vel2, t);
VecSubf(vec, co2, co1);
len = Normalize(vec);
/* distance of cpa is close enough */
if(len < 2.0f * val->personal_space * pa->size) {
t_min = t;
VecMulf(vec, VecLength(vel1));
VecMulf(vec, (2.0f - t)/2.0f);
VecSubf(bbd->wanted_co, vel1, vec);
bbd->wanted_speed = VecLength(bbd->wanted_co);
ret = 1;
}
}
}
}
}
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
/* check boids in other systems */
for(pt=bbd->psys->targets.first; pt; pt=pt->next) {
ParticleSystem *epsys = psys_get_target_system(bbd->ob, pt);
if(epsys) {
neighbors = BLI_kdtree_range_search(epsys->tree, acbr->look_ahead * VecLength(pa->prev_state.vel), pa->prev_state.co, pa->prev_state.ave, &ptn);
if(neighbors > 0) for(n=0; n<neighbors; n++) {
VECCOPY(co1, pa->prev_state.co);
VECCOPY(vel1, pa->prev_state.vel);
VECCOPY(co2, (epsys->particles + ptn[n].index)->prev_state.co);
VECCOPY(vel2, (epsys->particles + ptn[n].index)->prev_state.vel);
VecSubf(loc, co1, co2);
VecSubf(vec, vel1, vel2);
inp = Inpf(vec,vec);
/* velocities not parallel */
if(inp != 0.0f) {
t = -Inpf(loc, vec)/inp;
/* cpa is not too far in the future so investigate further */
if(t > 0.0f && t < t_min) {
VECADDFAC(co1, co1, vel1, t);
VECADDFAC(co2, co2, vel2, t);
VecSubf(vec, co2, co1);
len = Normalize(vec);
/* distance of cpa is close enough */
if(len < 2.0f * val->personal_space * pa->size) {
t_min = t;
VecMulf(vec, VecLength(vel1));
VecMulf(vec, (2.0f - t)/2.0f);
VecSubf(bbd->wanted_co, vel1, vec);
bbd->wanted_speed = VecLength(bbd->wanted_co);
ret = 1;
}
}
}
}
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
}
}
if(ptn && nearest==0)
MEM_freeN(ptn);
return ret;
}
static int rule_separate(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
KDTreeNearest *ptn = NULL;
ParticleTarget *pt;
float len = 2.0f * val->personal_space * pa->size + 1.0f;
float vec[3] = {0.0f, 0.0f, 0.0f};
int neighbors = BLI_kdtree_range_search(bbd->psys->tree, 2.0f * val->personal_space * pa->size, pa->prev_state.co, NULL, &ptn);
int ret = 0;
if(neighbors > 1 && ptn[1].dist!=0.0f) {
VecSubf(vec, pa->prev_state.co, bbd->psys->particles[ptn[1].index].state.co);
VecMulf(vec, (2.0f * val->personal_space * pa->size - ptn[1].dist) / ptn[1].dist);
VecAddf(bbd->wanted_co, bbd->wanted_co, vec);
bbd->wanted_speed = val->max_speed;
len = ptn[1].dist;
ret = 1;
}
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
/* check other boid systems */
for(pt=bbd->psys->targets.first; pt; pt=pt->next) {
ParticleSystem *epsys = psys_get_target_system(bbd->ob, pt);
if(epsys) {
neighbors = BLI_kdtree_range_search(epsys->tree, 2.0f * val->personal_space * pa->size, pa->prev_state.co, NULL, &ptn);
if(neighbors > 0 && ptn[0].dist < len) {
VecSubf(vec, pa->prev_state.co, ptn[0].co);
VecMulf(vec, (2.0f * val->personal_space * pa->size - ptn[0].dist) / ptn[1].dist);
VecAddf(bbd->wanted_co, bbd->wanted_co, vec);
bbd->wanted_speed = val->max_speed;
len = ptn[0].dist;
ret = 1;
}
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
}
}
return ret;
}
static int rule_flock(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
KDTreeNearest ptn[11];
float vec[3] = {0.0f, 0.0f, 0.0f}, loc[3] = {0.0f, 0.0f, 0.0f};
int neighbors = BLI_kdtree_find_n_nearest(bbd->psys->tree, 11, pa->state.co, pa->prev_state.ave, ptn);
int n, nearest = 1;
int ret = 0;
if(neighbors > 1) {
for(n=1; n<neighbors; n++) {
VecAddf(loc, loc, bbd->psys->particles[ptn[n].index].prev_state.co);
VecAddf(vec, vec, bbd->psys->particles[ptn[n].index].prev_state.vel);
}
VecMulf(loc, 1.0f/((float)neighbors - 1.0f));
VecMulf(vec, 1.0f/((float)neighbors - 1.0f));
VecSubf(loc, loc, pa->prev_state.co);
VecSubf(vec, vec, pa->prev_state.vel);
VecAddf(bbd->wanted_co, bbd->wanted_co, vec);
VecAddf(bbd->wanted_co, bbd->wanted_co, loc);
bbd->wanted_speed = VecLength(bbd->wanted_co);
ret = 1;
}
return ret;
}
static int rule_follow_leader(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
BoidRuleFollowLeader *flbr = (BoidRuleFollowLeader*) rule;
float vec[3] = {0.0f, 0.0f, 0.0f}, loc[3] = {0.0f, 0.0f, 0.0f};
float mul, len;
int n = (flbr->queue_size <= 1) ? bbd->psys->totpart : flbr->queue_size;
int i, ret = 0, p = pa - bbd->psys->particles;
if(flbr->ob) {
float vec2[3], t;
/* first check we're not blocking the leader*/
VecSubf(vec, flbr->loc, flbr->oloc);
VecMulf(vec, 1.0f/bbd->timestep);
VecSubf(loc, pa->prev_state.co, flbr->oloc);
mul = Inpf(vec, vec);
/* leader is not moving */
if(mul < 0.01) {
len = VecLength(loc);
/* too close to leader */
if(len < 2.0f * val->personal_space * pa->size) {
VECCOPY(bbd->wanted_co, loc);
bbd->wanted_speed = val->max_speed;
return 1;
}
}
else {
t = Inpf(loc, vec)/mul;
/* possible blocking of leader in near future */
if(t > 0.0f && t < 3.0f) {
VECCOPY(vec2, vec);
VecMulf(vec2, t);
VecSubf(vec2, loc, vec2);
len = VecLength(vec2);
if(len < 2.0f * val->personal_space * pa->size) {
VECCOPY(bbd->wanted_co, vec2);
bbd->wanted_speed = val->max_speed * (3.0f - t)/3.0f;
return 1;
}
}
}
/* not blocking so try to follow leader */
if(p && flbr->options & BRULE_LEADER_IN_LINE) {
VECCOPY(vec, bbd->psys->particles[p-1].prev_state.vel);
VECCOPY(loc, bbd->psys->particles[p-1].prev_state.co);
}
else {
VECCOPY(loc, flbr->oloc);
VecSubf(vec, flbr->loc, flbr->oloc);
VecMulf(vec, 1.0/bbd->timestep);
}
/* fac is seconds behind leader */
VECADDFAC(loc, loc, vec, -flbr->distance);
VecSubf(bbd->wanted_co, loc, pa->prev_state.co);
bbd->wanted_speed = VecLength(bbd->wanted_co);
ret = 1;
}
else if(p % n) {
float vec2[3], t, t_min = 3.0f;
/* first check we're not blocking any leaders */
for(i = 0; i< bbd->psys->totpart; i+=n){
VECCOPY(vec, bbd->psys->particles[i].prev_state.vel);
VecSubf(loc, pa->prev_state.co, bbd->psys->particles[i].prev_state.co);
mul = Inpf(vec, vec);
/* leader is not moving */
if(mul < 0.01) {
len = VecLength(loc);
/* too close to leader */
if(len < 2.0f * val->personal_space * pa->size) {
VECCOPY(bbd->wanted_co, loc);
bbd->wanted_speed = val->max_speed;
return 1;
}
}
else {
t = Inpf(loc, vec)/mul;
/* possible blocking of leader in near future */
if(t > 0.0f && t < t_min) {
VECCOPY(vec2, vec);
VecMulf(vec2, t);
VecSubf(vec2, loc, vec2);
len = VecLength(vec2);
if(len < 2.0f * val->personal_space * pa->size) {
t_min = t;
VECCOPY(bbd->wanted_co, loc);
bbd->wanted_speed = val->max_speed * (3.0f - t)/3.0f;
ret = 1;
}
}
}
}
if(ret) return 1;
/* not blocking so try to follow leader */
if(flbr->options & BRULE_LEADER_IN_LINE) {
VECCOPY(vec, bbd->psys->particles[p-1].prev_state.vel);
VECCOPY(loc, bbd->psys->particles[p-1].prev_state.co);
}
else {
VECCOPY(vec, bbd->psys->particles[p - p%n].prev_state.vel);
VECCOPY(loc, bbd->psys->particles[p - p%n].prev_state.co);
}
/* fac is seconds behind leader */
VECADDFAC(loc, loc, vec, -flbr->distance);
VecSubf(bbd->wanted_co, loc, pa->prev_state.co);
bbd->wanted_speed = VecLength(bbd->wanted_co);
ret = 1;
}
return ret;
}
static int rule_average_speed(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
BoidRuleAverageSpeed *asbr = (BoidRuleAverageSpeed*)rule;
float vec[3] = {0.0f, 0.0f, 0.0f};
if(asbr->wander > 0.0f) {
/* abuse pa->r_ave for wandering */
pa->r_ave[0] += asbr->wander * (-1.0f + 2.0f * BLI_frand());
pa->r_ave[1] += asbr->wander * (-1.0f + 2.0f * BLI_frand());
pa->r_ave[2] += asbr->wander * (-1.0f + 2.0f * BLI_frand());
Normalize(pa->r_ave);
VECCOPY(vec, pa->r_ave);
QuatMulVecf(pa->prev_state.rot, vec);
VECCOPY(bbd->wanted_co, pa->prev_state.ave);
VecMulf(bbd->wanted_co, 1.1f);
VecAddf(bbd->wanted_co, bbd->wanted_co, vec);
/* leveling */
if(asbr->level > 0.0f) {
Projf(vec, bbd->wanted_co, bbd->psys->part->acc);
VecMulf(vec, asbr->level);
VecSubf(bbd->wanted_co, bbd->wanted_co, vec);
}
}
else {
VECCOPY(bbd->wanted_co, pa->prev_state.ave);
/* may happen at birth */
if(Inp2f(bbd->wanted_co,bbd->wanted_co)==0.0f) {
bbd->wanted_co[0] = 2.0f*(0.5f - BLI_frand());
bbd->wanted_co[1] = 2.0f*(0.5f - BLI_frand());
bbd->wanted_co[2] = 2.0f*(0.5f - BLI_frand());
}
/* leveling */
if(asbr->level > 0.0f) {
Projf(vec, bbd->wanted_co, bbd->psys->part->acc);
VecMulf(vec, asbr->level);
VecSubf(bbd->wanted_co, bbd->wanted_co, vec);
}
}
bbd->wanted_speed = asbr->speed * val->max_speed;
return 1;
}
static int rule_fight(BoidRule *rule, BoidBrainData *bbd, BoidValues *val, ParticleData *pa)
{
BoidRuleFight *fbr = (BoidRuleFight*)rule;
KDTreeNearest *ptn = NULL;
ParticleTarget *pt;
ParticleData *epars;
ParticleData *enemy_pa;
/* friends & enemies */
float closest_enemy[3] = {0.0f,0.0f,0.0f};
float closest_dist = fbr->distance + 1.0f;
float f_strength = 0.0f, e_strength = 0.0f;
float health = 0.0f;
int n, ret = 0;
/* calculate own group strength */
int neighbors = BLI_kdtree_range_search(bbd->psys->tree, fbr->distance, pa->prev_state.co, NULL, &ptn);
for(n=0; n<neighbors; n++)
health += bbd->psys->particles[ptn[n].index].boid->health;
f_strength += bbd->part->boids->strength * health;
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
/* add other friendlies and calculate enemy strength and find closest enemy */
for(pt=bbd->psys->targets.first; pt; pt=pt->next) {
ParticleSystem *epsys = psys_get_target_system(bbd->ob, pt);
if(epsys) {
epars = epsys->particles;
neighbors = BLI_kdtree_range_search(epsys->tree, fbr->distance, pa->prev_state.co, NULL, &ptn);
health = 0.0f;
for(n=0; n<neighbors; n++) {
health += epars[ptn[n].index].boid->health;
if(n==0 && pt->mode==PTARGET_MODE_ENEMY && ptn[n].dist < closest_dist) {
VECCOPY(closest_enemy, ptn[n].co);
closest_dist = ptn[n].dist;
enemy_pa = epars + ptn[n].index;
}
}
if(pt->mode==PTARGET_MODE_ENEMY)
e_strength += epsys->part->boids->strength * health;
else if(pt->mode==PTARGET_MODE_FRIEND)
f_strength += epsys->part->boids->strength * health;
if(ptn){ MEM_freeN(ptn); ptn=NULL; }
}
}
/* decide action if enemy presence found */
if(e_strength > 0.0f) {
VecSubf(bbd->wanted_co, closest_enemy, pa->prev_state.co);
/* attack if in range */
if(closest_dist <= bbd->part->boids->range + pa->size + enemy_pa->size) {
float damage = BLI_frand();
float enemy_dir[3] = {bbd->wanted_co[0],bbd->wanted_co[1],bbd->wanted_co[2]};
Normalize(enemy_dir);
/* fight mode */
bbd->wanted_speed = 0.0f;
/* must face enemy to fight */
if(Inpf(pa->prev_state.ave, enemy_dir)>0.5f) {
enemy_pa->boid->health -= bbd->part->boids->strength * bbd->timestep * ((1.0f-bbd->part->boids->accuracy)*damage + bbd->part->boids->accuracy);
}
}
else {
/* approach mode */
bbd->wanted_speed = val->max_speed;
}
/* check if boid doesn't want to fight */
if(pa->boid->health/bbd->part->boids->health * bbd->part->boids->aggression < e_strength / f_strength) {
/* decide to flee */
if(closest_dist < fbr->flee_distance * fbr->distance) {
VecMulf(bbd->wanted_co, -1.0f);
bbd->wanted_speed = val->max_speed;
}
else { /* wait for better odds */
bbd->wanted_speed = 0.0f;
}
}
ret = 1;
}
return ret;
}
typedef int (*boid_rule_cb)(BoidRule *rule, BoidBrainData *data, BoidValues *val, ParticleData *pa);
static boid_rule_cb boid_rules[] = {
rule_none,
rule_goal_avoid,
rule_goal_avoid,
rule_avoid_collision,
rule_separate,
rule_flock,
rule_follow_leader,
rule_average_speed,
rule_fight,
//rule_help,
//rule_protect,
//rule_hide,
//rule_follow_path,
//rule_follow_wall
};
static void set_boid_values(BoidValues *val, BoidSettings *boids, ParticleData *pa)
{
if(ELEM(pa->boid->mode, eBoidMode_OnLand, eBoidMode_Climbing)) {
val->max_speed = boids->land_max_speed * pa->boid->health/boids->health;
val->max_acc = boids->land_max_acc * val->max_speed;
val->max_ave = boids->land_max_ave * M_PI * pa->boid->health/boids->health;
val->min_speed = 0.0f; /* no minimum speed on land */
val->personal_space = boids->land_personal_space;
val->jump_speed = boids->land_jump_speed * pa->boid->health/boids->health;
}
else {
val->max_speed = boids->air_max_speed * pa->boid->health/boids->health;
val->max_acc = boids->air_max_acc * val->max_speed;
val->max_ave = boids->air_max_ave * M_PI * pa->boid->health/boids->health;
val->min_speed = boids->air_min_speed * boids->air_max_speed;
val->personal_space = boids->air_personal_space;
val->jump_speed = 0.0f; /* no jumping in air */
}
}
static Object *boid_find_ground(BoidBrainData *bbd, ParticleData *pa, float *ground_co, float *ground_nor)
{
if(pa->boid->mode == eBoidMode_Climbing) {
SurfaceModifierData *surmd = NULL;
float x[3], v[3];
surmd = (SurfaceModifierData *)modifiers_findByType ( pa->stick_ob, eModifierType_Surface );
/* take surface velocity into account */
effector_find_co(bbd->scene, pa->state.co, surmd, NULL, NULL, x, NULL, v, NULL);
VecAddf(x, x, v);
/* get actual position on surface */
effector_find_co(bbd->scene, x, surmd, NULL, NULL, ground_co, ground_nor, NULL, NULL);
return pa->stick_ob;
}
else {
float zvec[3] = {0.0f, 0.0f, 2000.0f};
ParticleCollision col;
BVHTreeRayHit hit;
ParticleEffectorCache *ec;
float radius = 0.0f, t, ray_dir[3];
VECCOPY(col.co1, pa->state.co);
VECCOPY(col.co2, pa->state.co);
VecAddf(col.co1, col.co1, zvec);
VecSubf(col.co2, col.co2, zvec);
VecSubf(ray_dir, col.co2, col.co1);
col.t = 0.0f;
hit.index = -1;
hit.dist = col.ray_len = VecLength(ray_dir);
/* find out upmost deflector object */
for(ec=bbd->psys->effectors.first; ec; ec=ec->next) {
if(ec->type & PSYS_EC_DEFLECT) {
Object *eob = ec->ob;
col.md = ( CollisionModifierData * ) ( modifiers_findByType ( eob, eModifierType_Collision ) );
col.ob_t = eob;
if(col.md && col.md->bvhtree)
BLI_bvhtree_ray_cast(col.md->bvhtree, col.co1, ray_dir, radius, &hit, particle_intersect_face, &col);
}
}
/* then use that object */
if(hit.index>=0) {
t = hit.dist/col.ray_len;
VecLerpf(ground_co, col.co1, col.co2, t);
VECCOPY(ground_nor, col.nor);
Normalize(ground_nor);
return col.ob;
}
else {
/* default to z=0 */
VECCOPY(ground_co, pa->state.co);
ground_co[2] = 0;
ground_nor[0] = ground_nor[1] = 0.0f;
ground_nor[2] = 1.0f;
return NULL;
}
}
}
static int boid_rule_applies(ParticleData *pa, BoidSettings *boids, BoidRule *rule)
{
if(rule==NULL)
return 0;
if(ELEM(pa->boid->mode, eBoidMode_OnLand, eBoidMode_Climbing) && rule->flag & BOIDRULE_ON_LAND)
return 1;
if(pa->boid->mode==eBoidMode_InAir && rule->flag & BOIDRULE_IN_AIR)
return 1;
return 0;
}
void boids_precalc_rules(ParticleSettings *part, float cfra)
{
BoidState *state = part->boids->states.first;
BoidRule *rule;
for(; state; state=state->next) {
for(rule = state->rules.first; rule; rule=rule->next) {
if(rule->type==eBoidRuleType_FollowLeader) {
BoidRuleFollowLeader *flbr = (BoidRuleFollowLeader*) rule;
if(flbr->ob && flbr->cfra != cfra) {
/* save object locations for velocity calculations */
VECCOPY(flbr->oloc, flbr->loc);
VECCOPY(flbr->loc, flbr->ob->obmat[3]);
flbr->cfra = cfra;
}
}
}
}
}
static void boid_climb(BoidSettings *boids, ParticleData *pa, float *surface_co, float *surface_nor)
{
float nor[3], vel[3];
VECCOPY(nor, surface_nor);
/* gather apparent gravity to r_ve */
VECADDFAC(pa->r_ve, pa->r_ve, surface_nor, -1.0);
Normalize(pa->r_ve);
/* raise boid it's size from surface */
VecMulf(nor, pa->size * boids->height);
VecAddf(pa->state.co, surface_co, nor);
/* remove normal component from velocity */
Projf(vel, pa->state.vel, surface_nor);
VecSubf(pa->state.vel, pa->state.vel, vel);
}
static float boid_goal_signed_dist(float *boid_co, float *goal_co, float *goal_nor)
{
float vec[3];
VecSubf(vec, boid_co, goal_co);
return Inpf(vec, goal_nor);
}
/* wanted_co is relative to boid location */
static int apply_boid_rule(BoidBrainData *bbd, BoidRule *rule, BoidValues *val, ParticleData *pa, float fuzziness)
{
if(rule==NULL)
return 0;
if(boid_rule_applies(pa, bbd->part->boids, rule)==0)
return 0;
if(boid_rules[rule->type](rule, bbd, val, pa)==0)
return 0;
if(fuzziness < 0.0f || VecLenCompare(bbd->wanted_co, pa->prev_state.vel, fuzziness * VecLength(pa->prev_state.vel))==0)
return 1;
else
return 0;
}
static BoidState *get_boid_state(BoidSettings *boids, ParticleData *pa) {
BoidState *state = boids->states.first;
for(; state; state=state->next) {
if(state->id==pa->boid->state_id)
return state;
}
/* for some reason particle isn't at a valid state */
state = boids->states.first;
if(state)
pa->boid->state_id = state->id;
return state;
}
//static int boid_condition_is_true(BoidCondition *cond) {
// /* TODO */
// return 0;
//}
/* determines the velocity the boid wants to have */
void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa)
{
ParticleData *pars=bbd->psys->particles;
ParticleEffectorCache *ec=0;
BoidRule *rule;
BoidSettings *boids = bbd->part->boids;
BoidValues val;
BoidState *state = get_boid_state(boids, pa);
//BoidCondition *cond;
if(pa->boid->health <= 0.0f) {
pa->alive = PARS_DYING;
return;
}
//planned for near future
//cond = state->conditions.first;
//for(; cond; cond=cond->next) {
// if(boid_condition_is_true(cond)) {
// pa->boid->state_id = cond->state_id;
// state = get_boid_state(boids, pa);
// break; /* only first true condition is used */
// }
//}
bbd->wanted_co[0]=bbd->wanted_co[1]=bbd->wanted_co[2]=bbd->wanted_speed=0.0f;
/* create random seed for every particle & frame */
BLI_srandom(bbd->psys->seed + p + (int)bbd->cfra + (int)(1000*pa->r_rot[0]));
set_boid_values(&val, bbd->part->boids, pa);
/* go through rules */
switch(state->ruleset_type) {
case eBoidRulesetType_Fuzzy:
{
for(rule = state->rules.first; rule; rule = rule->next) {
if(apply_boid_rule(bbd, rule, &val, pa, state->rule_fuzziness))
break; /* only first nonzero rule that comes through fuzzy rule is applied */
}
break;
}
case eBoidRulesetType_Random:
{
/* use random rule for each particle (allways same for same particle though) */
rule = BLI_findlink(&state->rules, (int)(1000.0f * pa->r_rot[1]) % BLI_countlist(&state->rules));
apply_boid_rule(bbd, rule, &val, pa, -1.0);
}
case eBoidRulesetType_Average:
{
float wanted_co[3] = {0.0f, 0.0f, 0.0f}, wanted_speed = 0.0f;
int n = 0;
for(rule = state->rules.first; rule; rule=rule->next) {
if(apply_boid_rule(bbd, rule, &val, pa, -1.0f)) {
VecAddf(wanted_co, wanted_co, bbd->wanted_co);
wanted_speed += bbd->wanted_speed;
n++;
bbd->wanted_co[0]=bbd->wanted_co[1]=bbd->wanted_co[2]=bbd->wanted_speed=0.0f;
}
}
if(n > 1) {
VecMulf(wanted_co, 1.0f/(float)n);
wanted_speed /= (float)n;
}
VECCOPY(bbd->wanted_co, wanted_co);
bbd->wanted_speed = wanted_speed;
break;
}
}
/* decide on jumping & liftoff */
if(pa->boid->mode == eBoidMode_OnLand) {
/* fuzziness makes boids capable of misjudgement */
float mul = 1.0 + state->rule_fuzziness;
if(boids->options & BOID_ALLOW_FLIGHT && bbd->wanted_co[2] > 0.0f) {
float cvel[3], dir[3];
VECCOPY(dir, pa->prev_state.ave);
Normalize2(dir);
VECCOPY(cvel, bbd->wanted_co);
Normalize2(cvel);
if(Inp2f(cvel, dir) > 0.95 / mul)
pa->boid->mode = eBoidMode_Liftoff;
}
else if(val.jump_speed > 0.0f) {
float jump_v[3];
int jump = 0;
/* jump to get to a location */
if(bbd->wanted_co[2] > 0.0f) {
float cvel[3], dir[3];
float z_v, ground_v, cur_v;
float len;
VECCOPY(dir, pa->prev_state.ave);
Normalize2(dir);
VECCOPY(cvel, bbd->wanted_co);
Normalize2(cvel);
len = Vec2Length(pa->prev_state.vel);
/* first of all, are we going in a suitable direction? */
/* or at a suitably slow speed */
if(Inp2f(cvel, dir) > 0.95f / mul || len <= state->rule_fuzziness) {
/* try to reach goal at highest point of the parabolic path */
cur_v = Vec2Length(pa->prev_state.vel);
z_v = sasqrt(-2.0f * bbd->part->acc[2] * bbd->wanted_co[2]);
ground_v = Vec2Length(bbd->wanted_co)*sasqrt(-0.5f * bbd->part->acc[2] / bbd->wanted_co[2]);
len = sasqrt((ground_v-cur_v)*(ground_v-cur_v) + z_v*z_v);
if(len < val.jump_speed * mul || bbd->part->boids->options & BOID_ALLOW_FLIGHT) {
jump = 1;
len = MIN2(len, val.jump_speed);
VECCOPY(jump_v, dir);
jump_v[2] = z_v;
VecMulf(jump_v, ground_v);
Normalize(jump_v);
VecMulf(jump_v, len);
Vec2Addf(jump_v, jump_v, pa->prev_state.vel);
}
}
}
/* jump to go faster */
if(jump == 0 && val.jump_speed > val.max_speed && bbd->wanted_speed > val.max_speed) {
}
if(jump) {
VECCOPY(pa->prev_state.vel, jump_v);
pa->boid->mode = eBoidMode_Falling;
}
}
}
}
/* tries to realize the wanted velocity taking all constraints into account */
void boid_body(BoidBrainData *bbd, ParticleData *pa)
{
BoidSettings *boids = bbd->part->boids;
BoidValues val;
float acc[3] = {0.0f, 0.0f, 0.0f}, tan_acc[3], nor_acc[3];
float dvec[3], bvec[3];
float new_dir[3], new_speed;
float old_dir[3], old_speed;
float wanted_dir[3];
float q[4], mat[3][3]; /* rotation */
float ground_co[3] = {0.0f, 0.0f, 0.0f}, ground_nor[3] = {0.0f, 0.0f, 1.0f};
float force[3] = {0.0f, 0.0f, 0.0f}, tvel[3] = {0.0f, 0.0f, 1.0f};
float pa_mass=bbd->part->mass, dtime=bbd->dfra*bbd->timestep;
int p = pa - bbd->psys->particles;
set_boid_values(&val, boids, pa);
/* make sure there's something in new velocity, location & rotation */
copy_particle_key(&pa->state,&pa->prev_state,0);
if(bbd->part->flag & PART_SIZEMASS)
pa_mass*=pa->size;
/* if boids can't fly they fall to the ground */
if((boids->options & BOID_ALLOW_FLIGHT)==0 && ELEM(pa->boid->mode, eBoidMode_OnLand, eBoidMode_Climbing)==0 && bbd->part->acc[2] != 0.0f)
pa->boid->mode = eBoidMode_Falling;
if(pa->boid->mode == eBoidMode_Falling) {
/* Falling boids are only effected by gravity. */
acc[2] = bbd->part->acc[2];
}
else {
/* figure out acceleration */
float landing_level = 2.0f;
float level = landing_level + 1.0f;
float new_vel[3];
if(pa->boid->mode == eBoidMode_Liftoff) {
pa->boid->mode = eBoidMode_InAir;
pa->stick_ob = boid_find_ground(bbd, pa, ground_co, ground_nor);
}
else if(pa->boid->mode == eBoidMode_InAir && boids->options & BOID_ALLOW_LAND) {
/* auto-leveling & landing if close to ground */
pa->stick_ob = boid_find_ground(bbd, pa, ground_co, ground_nor);
/* level = how many particle sizes above ground */
level = (pa->prev_state.co[2] - ground_co[2])/(2.0f * pa->size) - 0.5;
landing_level = - boids->landing_smoothness * pa->prev_state.vel[2] * pa_mass;
if(pa->prev_state.vel[2] < 0.0f) {
if(level < 1.0f) {
bbd->wanted_co[0] = bbd->wanted_co[1] = bbd->wanted_co[2] = 0.0f;
bbd->wanted_speed = 0.0f;
pa->boid->mode = eBoidMode_Falling;
}
else if(level < landing_level) {
bbd->wanted_speed *= (level - 1.0f)/landing_level;
bbd->wanted_co[2] *= (level - 1.0f)/landing_level;
}
}
}
VECCOPY(old_dir, pa->prev_state.ave);
VECCOPY(wanted_dir, bbd->wanted_co);
new_speed = Normalize(wanted_dir);
/* first check if we have valid direction we want to go towards */
if(new_speed == 0.0f) {
VECCOPY(new_dir, old_dir);
}
else {
float old_dir2[2], wanted_dir2[2], nor[3], angle;
Vec2Copyf(old_dir2, old_dir);
Normalize2(old_dir2);
Vec2Copyf(wanted_dir2, wanted_dir);
Normalize2(wanted_dir2);
/* choose random direction to turn if wanted velocity */
/* is directly behind regardless of z-coordinate */
if(Inp2f(old_dir2, wanted_dir2) < -0.99f) {
wanted_dir[0] = 2.0f*(0.5f - BLI_frand());
wanted_dir[1] = 2.0f*(0.5f - BLI_frand());
wanted_dir[2] = 2.0f*(0.5f - BLI_frand());
Normalize(wanted_dir);
}
/* constrain direction with maximum angular velocity */
angle = saacos(Inpf(old_dir, wanted_dir));
angle = MIN2(angle, val.max_ave);
Crossf(nor, old_dir, wanted_dir);
VecRotToQuat(nor, angle, q);
VECCOPY(new_dir, old_dir);
QuatMulVecf(q, new_dir);
Normalize(new_dir);
/* save direction in case resulting velocity too small */
VecRotToQuat(nor, angle*dtime, q);
VECCOPY(pa->state.ave, old_dir);
QuatMulVecf(q, pa->state.ave);
Normalize(pa->state.ave);
}
/* constrain speed with maximum acceleration */
old_speed = VecLength(pa->prev_state.vel);
if(bbd->wanted_speed < old_speed)
new_speed = MAX2(bbd->wanted_speed, old_speed - val.max_acc);
else
new_speed = MIN2(bbd->wanted_speed, old_speed + val.max_acc);
/* combine direction and speed */
VECCOPY(new_vel, new_dir);
VecMulf(new_vel, new_speed);
/* maintain minimum flying velocity if not landing */
if(level >= landing_level) {
float len2 = Inp2f(new_vel,new_vel);
float root;
len2 = MAX2(len2, val.min_speed*val.min_speed);
root = sasqrt(new_speed*new_speed - len2);
new_vel[2] = new_vel[2] < 0.0f ? -root : root;
Normalize2(new_vel);
Vec2Mulf(new_vel, sasqrt(len2));
}
/* finally constrain speed to max speed */
new_speed = Normalize(new_vel);
VecMulf(new_vel, MIN2(new_speed, val.max_speed));
/* get acceleration from difference of velocities */
VecSubf(acc, new_vel, pa->prev_state.vel);
/* break acceleration to components */
Projf(tan_acc, acc, pa->prev_state.ave);
VecSubf(nor_acc, acc, tan_acc);
}
/* account for effectors */
do_effectors(p, pa, &pa->state, bbd->scene, bbd->ob, bbd->psys, pa->state.co, force, tvel, bbd->dfra, bbd->cfra);
if(ELEM(pa->boid->mode, eBoidMode_OnLand, eBoidMode_Climbing)) {
float length = Normalize(force);
length = MAX2(0.0f, length - boids->land_stick_force);
VecMulf(force, length);
}
VecAddf(acc, acc, force);
/* store smoothed acceleration for nice banking etc. */
VECADDFAC(pa->boid->acc, pa->boid->acc, acc, dtime);
VecMulf(pa->boid->acc, 1.0f / (1.0f + dtime));
/* integrate new location & velocity */
/* by regarding the acceleration as a force at this stage we*/
/* can get better control allthough it's a bit unphysical */
VecMulf(acc, 1.0f/pa_mass);
VECCOPY(dvec, acc);
VecMulf(dvec, dtime*dtime*0.5f);
VECCOPY(bvec, pa->prev_state.vel);
VecMulf(bvec, dtime);
VecAddf(dvec, dvec, bvec);
VecAddf(pa->state.co, pa->state.co, dvec);
VECADDFAC(pa->state.vel, pa->state.vel, acc, dtime);
if(pa->boid->mode != eBoidMode_InAir)
pa->stick_ob = boid_find_ground(bbd, pa, ground_co, ground_nor);
/* change modes, constrain movement & keep track of down vector */
switch(pa->boid->mode) {
case eBoidMode_InAir:
{
float grav[3] = {0.0f, 0.0f, bbd->part->acc[2] < 0.0f ? -1.0f : 0.0f};
/* don't take forward acceleration into account (better banking) */
if(Inpf(pa->boid->acc, pa->state.vel) > 0.0f) {
Projf(dvec, pa->boid->acc, pa->state.vel);
VecSubf(dvec, pa->boid->acc, dvec);
}
else {
VECCOPY(dvec, pa->boid->acc);
}
/* gather apparent gravity to r_ve */
VECADDFAC(pa->r_ve, grav, dvec, -boids->banking);
Normalize(pa->r_ve);
/* stick boid on goal when close enough */
if(bbd->goal_ob && boid_goal_signed_dist(pa->state.co, bbd->goal_co, bbd->goal_nor) <= pa->size * boids->height) {
pa->boid->mode = eBoidMode_Climbing;
pa->stick_ob = bbd->goal_ob;
boid_find_ground(bbd, pa, ground_co, ground_nor);
boid_climb(boids, pa, ground_co, ground_nor);
}
/* land boid when belowg ground */
else if(boids->options & BOID_ALLOW_LAND && pa->state.co[2] <= ground_co[2] + pa->size * boids->height) {
pa->state.co[2] = ground_co[2] + pa->size * boids->height;
pa->state.vel[2] = 0.0f;
pa->boid->mode = eBoidMode_OnLand;
}
break;
}
case eBoidMode_Falling:
{
float zvec[3] = {0.0f,0.0f,1.0f};
float grav[3] = {0.0f, 0.0f, bbd->part->acc[2] < 0.0f ? -1.0f : 0.0f};
/* gather apparent gravity to r_ve */
VECADDFAC(pa->r_ve, pa->r_ve, grav, dtime);
Normalize(pa->r_ve);
if(boids->options & BOID_ALLOW_LAND) {
/* stick boid on goal when close enough */
if(bbd->goal_ob && boid_goal_signed_dist(pa->state.co, bbd->goal_co, bbd->goal_nor) <= pa->size * boids->height) {
pa->boid->mode = eBoidMode_Climbing;
pa->stick_ob = bbd->goal_ob;
boid_find_ground(bbd, pa, ground_co, ground_nor);
boid_climb(boids, pa, ground_co, ground_nor);
}
/* land boid when really near ground */
else if(pa->state.co[2] <= ground_co[2] + 1.01 * pa->size * boids->height){
pa->state.co[2] = ground_co[2] + pa->size * boids->height;
pa->state.vel[2] = 0.0f;
pa->boid->mode = eBoidMode_OnLand;
}
/* if we're falling, can fly and want to go upwards lets fly */
else if(boids->options & BOID_ALLOW_FLIGHT && bbd->wanted_co[2] > 0.0f)
pa->boid->mode = eBoidMode_InAir;
}
else
pa->boid->mode = eBoidMode_InAir;
break;
}
case eBoidMode_Climbing:
{
boid_climb(boids, pa, ground_co, ground_nor);
//float nor[3];
//VECCOPY(nor, ground_nor);
///* gather apparent gravity to r_ve */
//VECADDFAC(pa->r_ve, pa->r_ve, ground_nor, -1.0);
//Normalize(pa->r_ve);
///* raise boid it's size from surface */
//VecMulf(nor, pa->size * boids->height);
//VecAddf(pa->state.co, ground_co, nor);
///* remove normal component from velocity */
//Projf(v, pa->state.vel, ground_nor);
//VecSubf(pa->state.vel, pa->state.vel, v);
break;
}
case eBoidMode_OnLand:
{
/* stick boid on goal when close enough */
if(bbd->goal_ob && boid_goal_signed_dist(pa->state.co, bbd->goal_co, bbd->goal_nor) <= pa->size * boids->height) {
pa->boid->mode = eBoidMode_Climbing;
pa->stick_ob = bbd->goal_ob;
boid_find_ground(bbd, pa, ground_co, ground_nor);
boid_climb(boids, pa, ground_co, ground_nor);
}
/* ground is too far away so boid falls */
else if(pa->state.co[2]-ground_co[2] > 1.1 * pa->size * boids->height)
pa->boid->mode = eBoidMode_Falling;
else {
/* constrain to surface */
pa->state.co[2] = ground_co[2] + pa->size * boids->height;
pa->state.vel[2] = 0.0f;
}
if(boids->banking > 0.0f) {
float grav[3];
/* Don't take gravity's strength in to account, */
/* otherwise amount of banking is hard to control. */
VECCOPY(grav, ground_nor);
VecMulf(grav, -1.0f);
Projf(dvec, pa->boid->acc, pa->state.vel);
VecSubf(dvec, pa->boid->acc, dvec);
/* gather apparent gravity to r_ve */
VECADDFAC(pa->r_ve, grav, dvec, -boids->banking);
Normalize(pa->r_ve);
}
else {
/* gather negative surface normal to r_ve */
VECADDFAC(pa->r_ve, pa->r_ve, ground_nor, -1.0f);
Normalize(pa->r_ve);
}
break;
}
}
/* save direction to state.ave unless the boid is falling */
/* (boids can't effect their direction when falling) */
if(pa->boid->mode!=eBoidMode_Falling && VecLength(pa->state.vel) > 0.1*pa->size) {
VECCOPY(pa->state.ave, pa->state.vel);
Normalize(pa->state.ave);
}
/* apply damping */
if(ELEM(pa->boid->mode, eBoidMode_OnLand, eBoidMode_Climbing))
VecMulf(pa->state.vel, 1.0f - 0.2f*bbd->part->dampfac);
/* calculate rotation matrix based on forward & down vectors */
if(pa->boid->mode == eBoidMode_InAir) {
VECCOPY(mat[0], pa->state.ave);
Projf(dvec, pa->r_ve, pa->state.ave);
VecSubf(mat[2], pa->r_ve, dvec);
Normalize(mat[2]);
}
else {
Projf(dvec, pa->state.ave, pa->r_ve);
VecSubf(mat[0], pa->state.ave, dvec);
Normalize(mat[0]);
VECCOPY(mat[2], pa->r_ve);
}
VecMulf(mat[2], -1.0f);
Crossf(mat[1], mat[2], mat[0]);
/* apply rotation */
Mat3ToQuat_is_ok(mat, q);
QuatCopy(pa->state.rot, q);
}
BoidRule *boid_new_rule(int type)
{
BoidRule *rule = NULL;
if(type <= 0)
return NULL;
switch(type) {
case eBoidRuleType_Goal:
case eBoidRuleType_Avoid:
rule = MEM_callocN(sizeof(BoidRuleGoalAvoid), "BoidRuleGoalAvoid");
break;
case eBoidRuleType_AvoidCollision:
rule = MEM_callocN(sizeof(BoidRuleAvoidCollision), "BoidRuleAvoidCollision");
((BoidRuleAvoidCollision*)rule)->look_ahead = 2.0f;
break;
case eBoidRuleType_FollowLeader:
rule = MEM_callocN(sizeof(BoidRuleFollowLeader), "BoidRuleFollowLeader");
((BoidRuleFollowLeader*)rule)->distance = 1.0f;
break;
case eBoidRuleType_AverageSpeed:
rule = MEM_callocN(sizeof(BoidRuleAverageSpeed), "BoidRuleAverageSpeed");
((BoidRuleAverageSpeed*)rule)->speed = 0.5f;
break;
case eBoidRuleType_Fight:
rule = MEM_callocN(sizeof(BoidRuleFight), "BoidRuleFight");
((BoidRuleFight*)rule)->distance = 100.0f;
((BoidRuleFight*)rule)->flee_distance = 100.0f;
break;
default:
rule = MEM_callocN(sizeof(BoidRule), "BoidRule");
break;
}
rule->type = type;
rule->flag |= BOIDRULE_IN_AIR|BOIDRULE_ON_LAND;
strcpy(rule->name, boidrule_type_items[type-1].name);
return rule;
}
void boid_default_settings(BoidSettings *boids)
{
boids->air_max_speed = 10.0f;
boids->air_max_acc = 0.5f;
boids->air_max_ave = 0.5f;
boids->air_personal_space = 1.0f;
boids->land_max_speed = 5.0f;
boids->land_max_acc = 0.5f;
boids->land_max_ave = 0.5f;
boids->land_personal_space = 1.0f;
boids->options = BOID_ALLOW_FLIGHT;
boids->landing_smoothness = 3.0f;
boids->banking = 1.0f;
boids->height = 1.0f;
boids->health = 1.0f;
boids->accuracy = 1.0f;
boids->aggression = 2.0f;
boids->range = 1.0f;
boids->strength = 0.1f;
}
BoidState *boid_new_state(BoidSettings *boids)
{
BoidState *state = MEM_callocN(sizeof(BoidState), "BoidState");
state->id = boids->last_state_id++;
if(state->id)
sprintf(state->name, "State %i", state->id);
else
strcpy(state->name, "State");
state->rule_fuzziness = 0.5;
state->volume = 1.0f;
state->channels |= ~0;
return state;
}
BoidState *boid_duplicate_state(BoidSettings *boids, BoidState *state) {
BoidState *staten = MEM_dupallocN(state);
BLI_duplicatelist(&staten->rules, &state->rules);
BLI_duplicatelist(&staten->conditions, &state->conditions);
BLI_duplicatelist(&staten->actions, &state->actions);
staten->id = boids->last_state_id++;
return staten;
}
void boid_free_settings(BoidSettings *boids)
{
if(boids) {
BoidState *state = boids->states.first;
for(; state; state=state->next) {
BLI_freelistN(&state->rules);
BLI_freelistN(&state->conditions);
BLI_freelistN(&state->actions);
}
BLI_freelistN(&boids->states);
MEM_freeN(boids);
}
}
BoidSettings *boid_copy_settings(BoidSettings *boids)
{
BoidSettings *nboids = NULL;
if(boids) {
BoidState *state;
BoidState *nstate;
nboids = MEM_dupallocN(boids);
BLI_duplicatelist(&nboids->states, &boids->states);
state = boids->states.first;
nstate = nboids->states.first;
for(; state; state=state->next, nstate=nstate->next) {
BLI_duplicatelist(&nstate->rules, &state->rules);
BLI_duplicatelist(&nstate->conditions, &state->conditions);
BLI_duplicatelist(&nstate->actions, &state->actions);
}
}
return nboids;
}
BoidState *boid_get_current_state(BoidSettings *boids)
{
BoidState *state = boids->states.first;
for(; state; state=state->next) {
if(state->flag & BOIDSTATE_CURRENT)
break;
}
return state;
}