Skip to content

Instantly share code, notes, and snippets.

@JSandusky
Created February 22, 2019 00:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JSandusky/d0e077b5527a07400bf11cfaad67a30e to your computer and use it in GitHub Desktop.
Save JSandusky/d0e077b5527a07400bf11cfaad67a30e to your computer and use it in GitHub Desktop.
Motion Wheel ... WTFPL
MotionWheel::MotionWheel(Context* ctx) : Component(ctx),
traveledDistance_(0.0f),
lastPosition_(Vector3(M_INFINITY, M_INFINITY, M_INFINITY))
{
SubscribeToEvent(E_SCENEPOSTUPDATE, URHO3D_HANDLER(MotionWheel, OnUpdate));
}
MotionWheel::~MotionWheel()
{
}
void MotionWheel::RegisterObject(Context* context)
{
context->RegisterFactory<MotionWheel>(IK_CATEGORY);
URHO3D_ATTRIBUTE("Diameter", float, wheelDiameter_, 1.0f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Recording Dir", Vector3, recordingOrientation_, Vector3::FORWARD, AM_DEFAULT);
URHO3D_ATTRIBUTE("Bidirectional", bool, bidirectional_, true, AM_DEFAULT);
URHO3D_ATTRIBUTE("Is Constrained", bool, isConstrained_, true, AM_DEFAULT);
URHO3D_ATTRIBUTE("Ticks", unsigned, ticks_, 4, AM_DEFAULT);
URHO3D_ATTRIBUTE("Debug Offset", Vector3, debugDrawOffset_, Vector3::ZERO, AM_DEFAULT);
URHO3D_ATTRIBUTE("Traveled", float, traveledDistance_, 0.0f, AM_EDIT);
// diagnostic only
URHO3D_ATTRIBUTE("Delta", float, lastChange_, 0.0f, 0);
}
void MotionWheel::Reset(float traveled)
{
SetTraveledDistance(traveled);
lastPosition_ = Vector3(M_INFINITY, M_INFINITY, M_INFINITY);
lastChange_ = 0.0f;
}
float MotionWheel::GetRevolutions() const
{
return traveledDistance_ / (wheelDiameter_ * M_PI);
}
float MotionWheel::GetRevolutionFraction() const
{
float baseVal = GetRevolutions();
return baseVal - floorf(baseVal);
}
void MotionWheel::ResizeWheel(float newDiameter)
{
const float fract = GetRevolutionFraction();
wheelDiameter_ = newDiameter;
traveledDistance_ = GetCircumference() * fract;
}
void MotionWheel::OnUpdate(StringHash eventID, VariantMap& eventData)
{
if (!IsEnabledEffective())
return;
// record old distance so we can track that
const float oldDist = traveledDistance_;
auto worldPos = GetNode()->GetWorldPosition();
if (lastPosition_.x_ != M_INFINITY)
{
auto traveledVec = (worldPos - lastPosition_);
auto localTravelVec = GetNode()->GetWorldRotation().Inverse() * traveledVec;
const float len = localTravelVec.Length();
localTravelVec.Normalize();
recordingOrientation_.Normalize();
float travelAccuracy = 1.0f;
if (isConstrained_)
travelAccuracy = bidirectional_ ? localTravelVec.AbsDotProduct(recordingOrientation_) : localTravelVec.DotProduct(recordingOrientation_);
if (len > M_LARGE_EPSILON && travelAccuracy > M_LARGE_EPSILON)
{
travelAccuracy = Clamp01(travelAccuracy);
traveledDistance_ += len * travelAccuracy;
}
}
lastChange_ = traveledDistance_ - oldDist;
lastPosition_ = worldPos;
}
void MotionWheel::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
{
if (!IsEnabledEffective())
return;
recordingOrientation_.Normalize();
// find implicit tangent space
auto norm = recordingOrientation_;
Vector3 tangent, binormal;
if (norm.x_ > 0.5f || norm.y_ > 0.5f || norm.x_ < -0.5f || norm.y_ < -0.5f)
tangent = Vector3(norm.y_, -norm.x_, 0.0f);
else
tangent = Vector3(-norm.z_, 0.0f, norm.x_);
tangent.Normalize();
auto worldRot = GetNode()->GetWorldRotation();
norm = worldRot * norm;
auto worldPos = GetNode()->GetWorldPosition();
worldPos += debugDrawOffset_;
auto worldRight = worldRot * tangent;
debug->AddCircle(worldPos, worldRight, wheelDiameter_ / 2.0f, Color::YELLOW, 32, false);
float startRot = GetRevolutionFraction() * 360.0f;
float stepPerTick = 360.0f / ticks_;
auto worldDown = norm;
for (unsigned i = 0; i < ticks_; ++i)
{
float rot = startRot + stepPerTick * (i + 1);
Quaternion q; q.FromAngleAxis(rot, worldRight);
Vector3 lineVec = q * worldDown;
debug->AddLine(worldPos, worldPos + lineVec * wheelDiameter_, Color::YELLOW, false);
}
}
/// Wolfire style survey-wheel tracking. Doesn't manage animation in any way, only records traveled distance
/// and provides an easy way to get the fraction or total number of wheel revolutions.
/// Ticks are merely a visual aid to slice the wheel starting at with neutral 0 degrees at down.
class URHO3D_API MotionWheel : public Component
{
URHO3D_OBJECT(MotionWheel, Component);
public:
MotionWheel(Context*);
virtual ~MotionWheel();
static void RegisterObject(Context*);
void Reset(float traveled = 0.0f);
unsigned GetRevolutionsInt() const { return (int)floorf(GetRevolutions()); }
float GetRevolutions() const;
float GetRevolutionFraction() const;
float GetCircumference() const { return wheelDiameter_ * M_PI; }
float GetWheelDiameter() const { return wheelDiameter_; }
void SetWheelDiameter(float value) { wheelDiameter_ = value; }
/// Will change the size but remap the contained traveleddistance in order to match the correct fractional amount of the current value.
void ResizeWheel(float newDiameter);
float GetTraveledDistance() const { return traveledDistance_; }
void SetTraveledDistance(float dist) { traveledDistance_ = dist; }
unsigned GetTicks() const { return ticks_; }
void SetTicks(unsigned value) { ticks_ = value; }
Vector3 GetDebugDrawOffset() const { return debugDrawOffset_; }
void SetDebugDrawOffset(const Vector3& value) { debugDrawOffset_ = value; }
bool IsBidirectional() const { return bidirectional_; }
void SetBidirectional(bool value) { bidirectional_ = value; }
bool IsConstrained() const { return isConstrained_; }
void SetConstrained(bool value) { isConstrained_ = value; }
bool TravelHasChanged() const { return lastChange_ > M_LARGE_EPSILON; }
virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest) override;
private:
void OnUpdate(StringHash eventID, VariantMap& eventData);
/// Last recorded sample position.
Vector3 lastPosition_ = Vector3(M_INFINITY, M_INFINITY, M_INFINITY);
/// Local vector of travel to use, Forward records forward motion, Right records rightward motion, etc.
Vector3 recordingOrientation_ = Vector3::FORWARD;
/// Offset to apply to the wheel when debug-render is drawn.
Vector3 debugDrawOffset_ = Vector3::ZERO;
/// Diameter of the surveyers wheel.
float wheelDiameter_ = 2.0f;
/// Accumulated travel distance.
float traveledDistance_ = 0.0f;
/// Amount of change is the last update.
float lastChange_ = 0.0f;
/// Number of spokes to draw radiating out.
unsigned ticks_ = 4;
/// Does moving in reverse of the recording direction all count?
bool bidirectional_ = false;
/// Are we constrained to the recording direction?
bool isConstrained_ = true;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment