forked from townforge/townforge
game: add boids for aesthetic purposes
This commit is contained in:
parent
6d6a83687b
commit
370f18e612
BIN
GameData/Bird/Bird.mdl
Normal file
BIN
GameData/Bird/Bird.mdl
Normal file
Binary file not shown.
8
GameData/Bird/Bird.xml
Normal file
8
GameData/Bird/Bird.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<material>
|
||||
<technique name="Techniques/Diff.xml" />
|
||||
<texture unit="diffuse" name="Bird/Bird_Asset_Texture0.jpg" />
|
||||
<parameter name="MatDiffColor" value="0.8 0.8 0.8 1" />
|
||||
<parameter name="MatSpecColor" value="0 0 0 300" />
|
||||
<parameter name="MatEmissiveColor" value="0 0 0 1" />
|
||||
</material>
|
BIN
GameData/Bird/Bird_Asset_Texture0.jpg
Normal file
BIN
GameData/Bird/Bird_Asset_Texture0.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 181 KiB |
BIN
GameData/Bird/Bird_Take 001.ani
Normal file
BIN
GameData/Bird/Bird_Take 001.ani
Normal file
Binary file not shown.
@ -87,6 +87,14 @@ TBScrollContainer: adapt-content: 1, scroll-mode: "auto", size: "available"
|
||||
TBEditField: readonly: 1, gravity: "left right", adapt-to-content: 1, skin: "TBEditField.credits", is-focusable: 0, multiline: 1, text-align: "center"
|
||||
text: "Andre Wharn - https://freesound.org/people/AndreWharn/"
|
||||
|
||||
TBLayout: axis: x
|
||||
TBEditField: readonly: 1, gravity: "left right", adapt-to-content: 1, skin: "TBEditField.credits-title", is-focusable: 0
|
||||
text: "Models:"
|
||||
|
||||
TBLayout: axis: x
|
||||
TBEditField: readonly: 1, gravity: "left right", adapt-to-content: 1, skin: "TBEditField.credits", is-focusable: 0, multiline: 1, text-align: "center"
|
||||
text: "Charlie Tinley - https://sketchfab.com/Tnkii"
|
||||
|
||||
TBTextField: text: "__________________________", text-align: "center"
|
||||
|
||||
TBLayout: axis: x
|
||||
|
2
external/urho3d
vendored
2
external/urho3d
vendored
@ -1 +1 @@
|
||||
Subproject commit 135dd6df4d73a3df9969adc7b88eb01949612af9
|
||||
Subproject commit d05f70ff8b6d21e42b60dcbabbe8f847ea6c4198
|
@ -59,6 +59,7 @@ set(game_sources
|
||||
UTBRendererBatcher.cpp
|
||||
audio.cc
|
||||
block_model.cpp
|
||||
boid.cc
|
||||
camera-controller.cc
|
||||
citymesh-urho3d.cc
|
||||
cloud-cover.cc
|
||||
@ -129,6 +130,7 @@ set(game_headers
|
||||
UTBRendererBatcher.h
|
||||
audio.h
|
||||
block_model.h
|
||||
boid.h
|
||||
caching-source-builder.h
|
||||
camera-controller.h
|
||||
citymesh-urho3d.h
|
||||
|
254
src/game/boid.cc
Normal file
254
src/game/boid.cc
Normal file
@ -0,0 +1,254 @@
|
||||
#include <stdio.h>
|
||||
#include <Urho3D/Resource/ResourceCache.h>
|
||||
#include <Urho3D/Core/Context.h>
|
||||
#include <Urho3D/Core/CoreEvents.h>
|
||||
#include <Urho3D/Scene/Scene.h>
|
||||
#include <Urho3D/Scene/Node.h>
|
||||
#include <Urho3D/Graphics/Material.h>
|
||||
#include <Urho3D/Graphics/Model.h>
|
||||
#include <Urho3D/Graphics/StaticModel.h>
|
||||
#include <Urho3D/Graphics/Animation.h>
|
||||
#include <Urho3D/Graphics/AnimatedModel.h>
|
||||
#include <Urho3D/Graphics/AnimationController.h>
|
||||
#include <Urho3D/Graphics/Octree.h>
|
||||
#include <Urho3D/Graphics/OctreeQuery.h>
|
||||
#include <crypto/crypto.h>
|
||||
#include "boid.h"
|
||||
|
||||
using namespace Urho3D;
|
||||
|
||||
#define FLIGHT_ANIMATION "Bird/Bird_Take 001.ani"
|
||||
#define DRAWABLE_FLAG_BOID 0x80
|
||||
#define BOID_PERCEPTION_RADIUS 100.0f
|
||||
#define SLERP_SPEED 1.0f
|
||||
#define FLIGHT_SPEED 75.0f
|
||||
|
||||
Vector3 Boid::camera_position = Vector3::ZERO;
|
||||
|
||||
BoidModel::BoidModel(Urho3D::Context *ctx): AnimatedModel(ctx)
|
||||
{
|
||||
drawableFlags_ |= DRAWABLE_FLAG_BOID;
|
||||
}
|
||||
|
||||
void BoidModel::RegisterObject(Context* context)
|
||||
{
|
||||
context->RegisterFactory<BoidModel>("Geometry");
|
||||
URHO3D_COPY_BASE_ATTRIBUTES(AnimatedModel);
|
||||
}
|
||||
|
||||
Boid::Boid(Urho3D::Context *ctx): Component(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void Boid::RegisterObject(Context* context)
|
||||
{
|
||||
context->RegisterFactory<Boid>("Logic");
|
||||
URHO3D_COPY_BASE_ATTRIBUTES(Component);
|
||||
}
|
||||
|
||||
void Boid::SetCameraPosition(const Urho3D::Vector3 &pos)
|
||||
{
|
||||
camera_position = pos;
|
||||
}
|
||||
|
||||
void Boid::SetDestination(const Urho3D::Vector3 &pos)
|
||||
{
|
||||
destination = pos;
|
||||
}
|
||||
|
||||
void Boid::UnsetDestination()
|
||||
{
|
||||
destination = boost::none;
|
||||
}
|
||||
|
||||
void Boid::Init(const Urho3D::Vector3 &pos, bool auto_update)
|
||||
{
|
||||
ResourceCache* cache = context_->GetSubsystem<ResourceCache>();
|
||||
|
||||
modelNode = GetNode()->CreateChild("model");
|
||||
AnimatedModel *model = modelNode->CreateComponent<BoidModel>();
|
||||
model->SetModel(cache->GetResource<Model>("Bird/Bird.mdl"));
|
||||
model->SetMaterial(cache->GetResource<Material>("Bird/Bird.xml"));
|
||||
model->SetCastShadows(false); // too high
|
||||
|
||||
Quaternion half_turn;
|
||||
half_turn.FromAngleAxis(180.0f, Vector3::UP);
|
||||
modelNode->SetRotation(half_turn * modelNode->GetRotation());
|
||||
|
||||
AnimationController *controller = modelNode->CreateComponent<AnimationController>();
|
||||
controller->PlayExclusive(FLIGHT_ANIMATION, 0, true, 0.0f);
|
||||
controller->SetTime(FLIGHT_ANIMATION, controller->GetLength(FLIGHT_ANIMATION) * crypto::rand<uint16_t>() / (float)0xffff);
|
||||
controller->SetSpeed(FLIGHT_ANIMATION, 8.0f);
|
||||
|
||||
GetNode()->SetPosition(pos);
|
||||
|
||||
if (auto_update)
|
||||
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Boid, HandleUpdate));
|
||||
}
|
||||
|
||||
void Boid::SetAutoRemove(float d)
|
||||
{
|
||||
auto_remove_distance_squared = d * d;
|
||||
}
|
||||
|
||||
void Boid::Accumulate(Urho3D::Vector3 &request, Urho3D::Vector3 v)
|
||||
{
|
||||
const float l1 = v.Length();
|
||||
if (l1 == 0.0f)
|
||||
return;
|
||||
const float l0 = request.Length();
|
||||
if (l0 + l1 > 1.0f)
|
||||
v *= (1.0 - l0) / l1;
|
||||
request += v;
|
||||
}
|
||||
|
||||
Urho3D::Vector3 Boid::UpdateAvoidance(const PODVector<Drawable*> &boids) const
|
||||
{
|
||||
const Vector3 position = GetNode()->GetPosition();
|
||||
const Vector3 direction = GetNode()->GetDirection();
|
||||
Scene *scene = modelNode->GetScene();
|
||||
Ray ray(position, direction);
|
||||
PODVector<RayQueryResult> results;
|
||||
RayOctreeQuery query(results, ray, RAY_TRIANGLE, BOID_PERCEPTION_RADIUS, DRAWABLE_GEOMETRY);
|
||||
scene->GetComponent<Octree>()->Raycast(query);
|
||||
const Urho3D::RayQueryResult *hit = NULL;
|
||||
for (const auto &r: results)
|
||||
{
|
||||
if (r.distance_ < 0.0f)
|
||||
continue;
|
||||
if (r.drawable_->GetNode() == modelNode)
|
||||
continue;
|
||||
hit = &r;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hit)
|
||||
return Vector3::ZERO;
|
||||
|
||||
// start sending rays around to find a way through
|
||||
for (float pitch = 15.0f; pitch <= 90.0f; pitch += 30.0f)
|
||||
{
|
||||
for (int pitchsign = 0; pitchsign < 2; ++pitchsign)
|
||||
{
|
||||
const float actual_pitch = pitch * (pitchsign ? 1.0 : -1.0f);
|
||||
for (float yaw = 15.0f; yaw < 90.0f; yaw += 30.0f)
|
||||
{
|
||||
for (int yawsign = 0; yawsign < 2; ++yawsign)
|
||||
{
|
||||
const float actual_yaw = yaw * (yawsign ? 1.0 : -1.0f);
|
||||
|
||||
const Quaternion q(actual_pitch, actual_yaw, 0.0f);
|
||||
const Vector3 dir = q * direction;
|
||||
|
||||
PODVector<RayQueryResult> results;
|
||||
Ray ray(position, dir);
|
||||
RayOctreeQuery query(results, ray, RAY_TRIANGLE, BOID_PERCEPTION_RADIUS, DRAWABLE_GEOMETRY);
|
||||
scene->GetComponent<Octree>()->Raycast(query);
|
||||
bool hit = false;
|
||||
for (const auto &r: results)
|
||||
{
|
||||
if (r.distance_ < 0.0f)
|
||||
continue;
|
||||
if (r.drawable_->GetNode() == modelNode)
|
||||
continue;
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
if (!hit)
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Vector3::ZERO;
|
||||
}
|
||||
|
||||
Urho3D::Vector3 Boid::UpdateMatching(const PODVector<Drawable*> &boids) const
|
||||
{
|
||||
Vector3 direction = Vector3::ZERO;
|
||||
unsigned int nboids = 0;
|
||||
for (const Drawable *boid: boids)
|
||||
{
|
||||
if (boid->GetNode() == modelNode)
|
||||
continue;
|
||||
direction += boid->GetNode()->GetDirection();
|
||||
++nboids;
|
||||
}
|
||||
if (nboids == 0)
|
||||
return Vector3::ZERO;
|
||||
return direction.NormalizedApproximateFast() * .25f;;
|
||||
}
|
||||
|
||||
Urho3D::Vector3 Boid::UpdateCentering(const PODVector<Drawable*> &boids) const
|
||||
{
|
||||
Vector3 centroid = Vector3::ZERO;
|
||||
float nboids = 0.0f;
|
||||
for (const Drawable *boid: boids)
|
||||
{
|
||||
if (boid->GetNode() == modelNode)
|
||||
continue;
|
||||
centroid += boid->GetNode()->GetPosition();
|
||||
nboids += 1.0f;
|
||||
}
|
||||
if (nboids == 0.0f)
|
||||
return Vector3::ZERO;
|
||||
centroid /= nboids;
|
||||
return (centroid - GetNode()->GetPosition()).NormalizedApproximateFast() * .2f;;
|
||||
}
|
||||
|
||||
Urho3D::Vector3 Boid::UpdateDestination() const
|
||||
{
|
||||
if (!destination)
|
||||
return Vector3::ZERO;
|
||||
return (*destination - GetNode()->GetPosition()).NormalizedApproximateFast() * .75f;
|
||||
}
|
||||
|
||||
void Boid::Update(float timeStep)
|
||||
{
|
||||
PODVector<Drawable*> boids;
|
||||
SphereOctreeQuery query(boids, Sphere(GetNode()->GetPosition(), BOID_PERCEPTION_RADIUS), DRAWABLE_FLAG_BOID);
|
||||
GetNode()->GetScene()->GetComponent<Octree>()->GetDrawables(query);
|
||||
|
||||
Vector3 request = Vector3::ZERO;
|
||||
Accumulate(request, UpdateAvoidance(boids));
|
||||
Accumulate(request, UpdateMatching(boids));
|
||||
Accumulate(request, UpdateDestination());
|
||||
Accumulate(request, UpdateCentering(boids));
|
||||
Accumulate(request, GetNode()->GetDirection());
|
||||
Apply(request, timeStep);
|
||||
Move(timeStep);
|
||||
}
|
||||
|
||||
void Boid::Apply(Vector3 request, float timeStep)
|
||||
{
|
||||
request.NormalizeApproximateFast();
|
||||
Quaternion q(Vector3::FORWARD, request);
|
||||
const float t = std::min<float>(timeStep * SLERP_SPEED, 1.0f);
|
||||
q = GetNode()->GetRotation().Slerp(q, t);
|
||||
Vector3 direction = q * Vector3::FORWARD;
|
||||
GetNode()->SetRotation(Quaternion(Vector3::FORWARD, direction));
|
||||
}
|
||||
|
||||
void Boid::Move(float timeStep)
|
||||
{
|
||||
Vector3 dpos = GetNode()->GetRotation() * Vector3::FORWARD * timeStep * FLIGHT_SPEED;
|
||||
GetNode()->SetPosition(GetNode()->GetPosition() + dpos);
|
||||
}
|
||||
|
||||
void Boid::UpdateRemoval()
|
||||
{
|
||||
if (!auto_remove_distance_squared)
|
||||
return;
|
||||
const float d = (camera_position - GetNode()->GetPosition()).LengthSquared();
|
||||
if (d < *auto_remove_distance_squared)
|
||||
return;
|
||||
GetNode()->Remove();
|
||||
}
|
||||
|
||||
void Boid::HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
const float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
|
||||
Update(timeStep);
|
||||
UpdateRemoval();
|
||||
}
|
53
src/game/boid.h
Normal file
53
src/game/boid.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <Urho3D/Math/Vector3.h>
|
||||
#include <Urho3D/Container/Vector.h>
|
||||
#include <Urho3D/Graphics/AnimatedModel.h>
|
||||
|
||||
#define BOID_BASE_HEIGHT 1200.0f
|
||||
|
||||
class BoidModel: public Urho3D::AnimatedModel
|
||||
{
|
||||
URHO3D_OBJECT(BoidModel, AnimatedModel);
|
||||
|
||||
public:
|
||||
BoidModel(Urho3D::Context *ctx);
|
||||
|
||||
static void RegisterObject(Urho3D::Context* context);
|
||||
};
|
||||
|
||||
class Boid: public Urho3D::Component
|
||||
{
|
||||
URHO3D_OBJECT(Boid, Component);
|
||||
|
||||
public:
|
||||
explicit Boid(Urho3D::Context *ctx);
|
||||
|
||||
static void RegisterObject(Urho3D::Context* context);
|
||||
|
||||
void Init(const Urho3D::Vector3 &pos, bool auto_update = true);
|
||||
void SetAutoRemove(float d);
|
||||
void SetDestination(const Urho3D::Vector3 &pos);
|
||||
void UnsetDestination();
|
||||
void Update(float timeStep);
|
||||
void HandleUpdate(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
|
||||
|
||||
static void SetCameraPosition(const Urho3D::Vector3 &pos);
|
||||
|
||||
private:
|
||||
void Accumulate(Urho3D::Vector3 &request, Urho3D::Vector3 v);
|
||||
Urho3D::Vector3 UpdateAvoidance(const Urho3D::PODVector<Urho3D::Drawable*> &boids) const;
|
||||
Urho3D::Vector3 UpdateMatching(const Urho3D::PODVector<Urho3D::Drawable*> &boids) const;
|
||||
Urho3D::Vector3 UpdateCentering(const Urho3D::PODVector<Urho3D::Drawable*> &boids) const;
|
||||
Urho3D::Vector3 UpdateDestination() const;
|
||||
void Apply(Urho3D::Vector3 request, float timeStep);
|
||||
void Move(float timeStep);
|
||||
void UpdateRemoval();
|
||||
|
||||
private:
|
||||
Urho3D::SharedPtr<Urho3D::Node> modelNode;
|
||||
boost::optional<Urho3D::Vector3> destination;
|
||||
boost::optional<float> auto_remove_distance_squared;
|
||||
static Urho3D::Vector3 camera_position;
|
||||
};
|
@ -59,6 +59,7 @@
|
||||
#include "audio.h"
|
||||
#include "block_model.h"
|
||||
#include "custom-model.h"
|
||||
#include "boid.h"
|
||||
#include "UTBRendererBatcher.h"
|
||||
#include "ui-file-selector.h"
|
||||
#include "ui-tb-message-box.h"
|
||||
@ -360,6 +361,7 @@ private:
|
||||
void OnNewBlock();
|
||||
float GetSnowiness() const;
|
||||
float GetSummerness() const;
|
||||
void UpdateBoids();
|
||||
|
||||
template<typename T> T GetConfigValue(const String &filename, SharedPtr<JSONFile> &config, const char *section, const char *key, const T &default_value);
|
||||
template<typename T> T GetConfigValue(const char *section, const char *key, const T &default_value);
|
||||
@ -440,6 +442,8 @@ private:
|
||||
SharedPtr<JSONFile> config;
|
||||
FileWatcher config_watcher;
|
||||
|
||||
Urho3D::Timer boidUpdateTimer_;
|
||||
|
||||
std::list<std::shared_ptr<QueuedCommand>> queued_commands;
|
||||
|
||||
struct AddBlockCommand: public QueuedCommand
|
||||
@ -520,7 +524,8 @@ CryptoCityUrho3D::CryptoCityUrho3D(Context *ctx):
|
||||
audio(ctx),
|
||||
play_sfx_on_new_block(true),
|
||||
config_watcher(ctx),
|
||||
newCity_(true)
|
||||
newCity_(true),
|
||||
boidUpdateTimer_()
|
||||
{
|
||||
}
|
||||
|
||||
@ -644,6 +649,8 @@ void CryptoCityUrho3D::Setup(void)
|
||||
ProcSky::RegisterObject(context_);
|
||||
BlockModel::RegisterObject(context_);
|
||||
CustomModel::RegisterObject(context_);
|
||||
BoidModel::RegisterObject(context_);
|
||||
Boid::RegisterObject(context_);
|
||||
|
||||
const auto path = GetPath(config_filename);
|
||||
if (!config_watcher.StartWatching(path, false))
|
||||
@ -1381,6 +1388,8 @@ void CryptoCityUrho3D::CreateScene()
|
||||
Node *horizonNode = scene_->CreateChild("HorizonNode");
|
||||
horizon.Init(horizonNode, 2500, 1600, 1900);
|
||||
|
||||
scene_->CreateChild("boids");
|
||||
|
||||
uint32_t ox, oy;
|
||||
cc::get_city_origin(0, ox, oy);
|
||||
|
||||
@ -1835,6 +1844,43 @@ void CryptoCityUrho3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
audio.Update(timeStep);
|
||||
|
||||
cityMesh->SetSnowiness(GetSnowiness());
|
||||
|
||||
UpdateBoids();
|
||||
}
|
||||
|
||||
void CryptoCityUrho3D::UpdateBoids()
|
||||
{
|
||||
Boid::SetCameraPosition(cameraNode_->GetWorldPosition());
|
||||
|
||||
const unsigned ms = boidUpdateTimer_.GetMSec(false);
|
||||
if (ms < 5000)
|
||||
return;
|
||||
|
||||
if ((crypto::rand<uint32_t>() & 63) != 0)
|
||||
return;
|
||||
|
||||
// pick a point behind the camera and a destination in front of it
|
||||
const Vector3 campos = cameraNode_->GetPosition();
|
||||
const Vector3 camdir = cameraNode_->GetDirection();
|
||||
const int32_t angle = crypto::rand<uint32_t>() % 160 - 80;
|
||||
const Quaternion rotation(angle, Vector3::UP);
|
||||
const Vector3 hcamdir = rotation * Vector3(camdir.x_, 0.0f, camdir.z_).NormalizedApproximateFast();
|
||||
const Vector3 refpos = campos + hcamdir * 250;
|
||||
const Vector3 origin = refpos - hcamdir * 2000.0f + Vector3::UP * BOID_BASE_HEIGHT;
|
||||
const Vector3 destination = refpos + hcamdir * 250000.0f + Vector3::UP * BOID_BASE_HEIGHT;
|
||||
const unsigned r = crypto::rand<uint32_t>() & 7;
|
||||
const unsigned int num_boids = r == 0 ? (8 + (crypto::rand<uint32_t>() & 15)) : r == 1 ? 1 : (1 + (crypto::rand<uint32_t>() & 7));
|
||||
|
||||
Node *boidsNode = scene_->GetChild("boids");
|
||||
for (unsigned int i = 0; i < num_boids; ++i)
|
||||
{
|
||||
SharedPtr<Node> boidNode(boidsNode->CreateChild("boid"));
|
||||
Boid *boid = boidNode->CreateComponent<Boid>();
|
||||
boid->Init(origin + Vector3((crypto::rand<uint32_t>() & 127) - 64.0f, (crypto::rand<uint32_t>() & 127) - 64.0f, (crypto::rand<uint32_t>() & 127) - 64.0f));
|
||||
boid->SetDestination(destination);
|
||||
boid->SetAutoRemove(5000.0f);
|
||||
}
|
||||
boidUpdateTimer_.Reset();
|
||||
}
|
||||
|
||||
float CryptoCityUrho3D::GetSnowiness() const
|
||||
|
Loading…
Reference in New Issue
Block a user