diff options
author | Janne Karhu <jhkarh@gmail.com> | 2009-07-21 03:52:53 +0400 |
---|---|---|
committer | Janne Karhu <jhkarh@gmail.com> | 2009-07-21 03:52:53 +0400 |
commit | b4353a84439085ac7d4bb7da2daed983637a75cc (patch) | |
tree | 01e560f80b4c190cddae071c7ca41cd5840fe1ec /source/blender/blenkernel/intern/boids.c | |
parent | 01b787636b468a1d37a27bb7601de5b0dea9c4b0 (diff) |
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.
Diffstat (limited to 'source/blender/blenkernel/intern/boids.c')
-rw-r--r-- | source/blender/blenkernel/intern/boids.c | 1526 |
1 files changed, 1526 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/boids.c b/source/blender/blenkernel/intern/boids.c new file mode 100644 index 00000000000..931ad0b3043 --- /dev/null +++ b/source/blender/blenkernel/intern/boids.c @@ -0,0 +1,1526 @@ +/* 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; +}
\ No newline at end of file |