game: add boids for aesthetic purposes

This commit is contained in:
Crypto City 2020-08-10 11:55:21 +00:00
parent 6d6a83687b
commit 370f18e612
10 changed files with 373 additions and 2 deletions

BIN
GameData/Bird/Bird.mdl Normal file

Binary file not shown.

8
GameData/Bird/Bird.xml Normal file
View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

View File

@ -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

@ -1 +1 @@
Subproject commit 135dd6df4d73a3df9969adc7b88eb01949612af9
Subproject commit d05f70ff8b6d21e42b60dcbabbe8f847ea6c4198

View File

@ -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
View 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
View 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;
};

View File

@ -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