I'm hoping someone out there might have an idea on how I could better solve this problem I've created for myself :) I'm currently looking for a way to program this logic:
Imagine I have a control slider, that a user controls and goes from 0 to 255, and imagine I have a timing slider, from 0 milliseconds to 20,000 milliseconds.
Now, if I set the timing slider to 20,000, and move the control slider from 0 to 255, I would expect that the code will output a transition point between 0 to 255 over 20 seconds. I have code that does this fine, and it will be attached below.
However, lets say 10 seconds into that transition period the user moves the control slider from the 255 back to 0. At 10 seconds in, the transition point, x, should be at 127. What I want to happen is for the x value to move over the remaining 10 seconds to the new control slider point, in this case, 0. Ideally this should work for any number of movements over that 20 second period.
Once the x reaches the control slider point, the transitioning code deactivates until the next movement.
Here's the code that handles the first part of the problem I'm trying to solve:
class Fader {
public:
float newFaderValueSetTime;
float newFaderValue;
bool transitionInMotion;
float lastReturnedValueWhenNewFaderValueWasSet;
bool newFaderValueSet;
float lastOutputValue;
Fader(void) {
lastReturnedValueWhenNewFaderValueWasSet = 0;
newFaderValue = 0;
lastOutputValue = 0;
transitionInMotion = false;
}
int getValue(float delayAmount) {
float currentTime = ofGetElapsedTimeMillis() ;
float timePassedSinceNewFaderValue = currentTime - newFaderValueSetTime;
if(timePassedSinceNewFaderValue >= delayAmount) {
transitionInMotion = false;
}
if(transitionInMotion) {
lastOutputValue = ofMap(timePassedSinceNewFaderValue, 0, delayAmount, lastReturnedValueWhenNewFaderValueWasSet, newFaderValue);
} else {
lastOutputValue = newFaderValue;
}
return lastOutputValue;
}
void setFaderValue(int val, float delayAmount) {
if(delayAmount > 0 && !transitionInMotion) {
transitionInMotion = true;
newFaderValueSetTime = ofGetElapsedTimeMillis();
lastReturnedValueWhenNewFaderValueWasSet = lastOutputValue;
}
newFaderValue = val;
}
};
This is in c++ using OpenFrameworks, hence the of prefix for some of the functions. Anyway, I hope I've been specific enough about the problem.
The main point of code that is at issue, I think is the way the mapping of value ranges functions - for example, take this line:
lastOutputValue = ofMap(timePassedSinceNewFaderValue, 0, delayAmount, lastReturnedValueWhenNewFaderValueWasSet, newFaderValue);
This line takes the amount of time passed as the temporal position, makes it relative to delay amount, then remaps its the value from the lastReturnedValueWhenNewFaderValueWasSet to the newFaderValue.. e.g.,
If at time of transition, the fader value was at 0, and moves to 255, then
lastReturnedValueWhenNewFaderValueWasSet = 0, and newFaderValue = 255;
However, at the 10 second mark, the lastOutputValue will be 127, and if I then move the newFaderValue from 255 to 0, then lastReturnedValueWhenNewFaderValueWasSet will still be 0, and the mapping will be from 0 to 0, rather than from the current position of the transition point, x.
I hope this explains the logic a bit better. Cheers!
I've solved the problem I set out. Here is a rundown of the logic and the code for those looking for a solution to this sort of problem.
x will increase if the destination is greater than x
x will decrease if the destination is less than x
x is defined as the value set before the timer is initiated
if the timer is active if and only if
timer is greater than zero
The destination is greater than or less than x
The increase amount is defined as the distance between x, the destination, divided by the time left to reach the destination multiplied by the difference since the last time x was set.
e.g.
lets say x is 127, and destination is 200. The time remaining is 10,000 milliseconds
positive difference = 200-127 = 73 over 10,000
divided by the time remaining, multiplied by the time change since last update.
(200-127 / 10,000) * 60 (milliseconds since last update)
= amount to increment...
lets say x = 200 and the destination is 90. The time remaining is 10,000 milliseconds
positive difference is 200-90 = 110.
(110 / 10,000) * 60 (milliseconds since last update) = 0.66.. which is to be removed from x...
and because x is decreasing in this case, an additional calculation of (0.66 * -1) to produce the negative value.
And the code:
class Fader {
public:
float newFaderValueSetTime;
float destination;
float lastUpdateTime;
float x;
bool transitionInMotion;
Fader(void) {
transitionInMotion = false;
lastUpdateTime = -1;
x= 0;
destination = 0;
}
float positiveDifference(float x1, float y1) {
if(x1>y1) {
return x1-y1;
} else {
return y1-x1;
}
}
int getValue(float delayAmount) {
float currentTime = ofGetElapsedTimeMillis();
float timePassedSinceNewFaderValue = currentTime - newFaderValueSetTime;
if(timePassedSinceNewFaderValue >= delayAmount) {
transitionInMotion = false;
}
if(transitionInMotion) {
float timeRemaining = delayAmount - timePassedSinceNewFaderValue;
float diff = positiveDifference(x, destination);
float tempX = (diff / timeRemaining);
if(lastUpdateTime == -1) {
lastUpdateTime = currentTime;
} else {
tempX = tempX * (currentTime - lastUpdateTime);
}
if(destination > x) {
x = x + tempX;
} else if (destination < x) {
x = x + (tempX*-1);
}
} else {
x = destination;
}
if(x > 0) {
ofLogNotice("Output Value of fader is: " + ofToString(x));
}
lastUpdateTime = currentTime;
return x;
}
void setFaderValue(int val, float delayAmount) {
if(delayAmount > 0 && !transitionInMotion) {
transitionInMotion = true;
newFaderValueSetTime = ofGetElapsedTimeMillis();
lastUpdateTime = -1;
}
destination = val;
}
};
Related
I am kinda stuck with my basic voxel physics right now. It's very, very choppy and I am pretty sure my maths is broken somewhere, but let's see what you have to say:
// SOMEWHERE AT CLASS LEVEL (so not being reinstantiated every frame, but persisted instead!)
glm::vec3 oldPos;
// ACTUAL IMPL
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
if (!entity->grounded) {
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) {
glm::vec3 dir = entityPosition - oldPos; // Actually no need to normalize as we check for lesser, bigger or equal to 0
std::cout << "falling dir: " << glm::to_string(dir) << std::endl;
// Calculate offset (where to put after hit)
int x = dir.x;
int y = dir.y;
int z = dir.z;
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
if (dir.y >= 0) {
y = -1;
} else if (dir.y < 0) {
y = 1;
}
if (dir.z >= 0) {
z = -1;
} else if (dir.z < 0) {
z = 1;
}
glm::vec3 newPos = oldPos + glm::vec3(x, y, z);
this->entity->setPosition(newPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
} else {
oldPos = entity->getPosition();
this->entity->setPosition(distanceToGravityCenter);
}
}
Basic idea was to determine from which direction entityt would hit the surface and then just position it one "unit" back into that direction. But obviously I am doing something wrong as that will always move entity back to the point where it came from, effectively holding it at the spawn point.
Also this could probably be much easier and I am overthinking it.
As #CompuChip already pointed out, your ifs could be further simplified.
But what is more important is one logical issue that would explain the "choppiness" you describe (Sadly you did not provide any footage, so this is my best guess)
From the code you posted:
First you check if entity is grounded. If so you continue with checking if there is a collision and lastly, if there is not, you set the position.
You have to invert that a bit.
Save old position
Check if grounded
Set the position already to the new one!
Do collision detection
Reset to old position IF you registered a collision!
So basically:
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
oldPos = entity->getPosition(); // 1.
if (!entity->grounded) { // 2.
this->fallingStar->setPosition(distanceToGravityPoint); // 3
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) { // 4, 5
this->entity->setPosition(oldPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
}
}
This should get you started :)
I want to elaborate a bit more:
If you check for collision first and then set position you create an "infinite loop" upon first collision/hit as you collide, then if there is a collision (which there is) you set back to the old position. Basically just mathematic inaccuracy will make you move, as on every check you are set back to the old position.
Consider the if-statements for one of your coordinates:
if (dir.x >= 0) {
x = -1;
}
if (dir.x < 0) {
x = 1;
}
Suppose that dir.x < 0. Then you will skip the first if, enter the second, and x will be set to 1.
If dir.x >= 0, you will enter the first if and x will be set to -1. Now x < 0 is true, so you will enter the second if as well, and x gets set to 1 again.
Probably what you want is to either set x to 1 or to -1, depending on dir.x. You should only execute the second if when the first one was not entered, so you need an else if:
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
which can be condensed, if you so please, into
x = (dir.x >= 0) ? -1 : 1;
I am reading in a temperature value every 1 second/minute (this rate is not crucial). I want to measure this temperature so that if it begins to rise rapidly above a certain threshold I perform an action.
If the temperature rises above 30 degrees ( at any rate ) I increase the fan speed.
I think I must do something like set old temperature to new temp and then each time it loops set old temp to the current temp of the engine. But I am not sure if I need to use arrays for the engine temp or not.
Of course you can store just one old sample, then check difference like in:
bool isHot(int sample) {
static int oldSample = sample;
return ((sample > 30) || (sample - oldSample > threshold));
}
It's OK from C point of view, but very bad from metrology point of view. You should consider some conditioning of your signal (in this case temperature) to smothen out any spikes.
Of course you can add signal conditioning letter on. For (easy) example look at Simple Moving Avarage: https://en.wikipedia.org/wiki/Moving_average
If you want control the fan speed "right way" you should consider learning a bit about PID controller: https://en.wikipedia.org/wiki/PID_controller
Simple discrete PID:
PidController.h:
class PidController
{
public:
PidController();
double sim(double y);
void UpdateParams(double kp, double ki, double kd);
void setSP(double setPoint) { m_setPoint = setPoint; } //set current value of r(t)
private:
double m_setPoint; //current value of r(t)
double m_kp;
double m_ki;
double m_kd;
double m_outPrev;
double m_errPrev[2];
};
PidController.cpp
#include "PidController.h"
PidController::PidController():ControllerObject()
{
m_errPrev[0] = 0;
m_errPrev[1] = 0;
m_outPrev = 0;
}
void PidController::UpdateParams(double kp, double ki, double kd)
{
m_kp = kp;
m_ki = ki;
m_kd = kd;
}
//calculates PID output
//y - sample of y(t)
//returns sample of u(t)
double PidController::sim(double y)
{
double out; //u(t) sample
double e = m_setPoint - y; //error
out = m_outPrev + m_kp * (e - m_errPrev[0] + m_kd * (e - 2 * m_errPrev[0] + m_errPrev[1]) + m_ki * e);
m_outPrev = out; //store previous output
//store previous errors
m_errPrev[1] = m_errPrev[0];
m_errPrev[0] = e;
return out;
}
I have a program that is taking input from an external source (right now, a joystick), and then plotting it on a graph. The graph displays the last 60 frames of data, which is about 1-2 seconds.
here is the data input:
nextDataPoint(double x){
if (x > max){ max = x; }
if (x < min){ min = x; }
dataInput.enqueue(x) //dataInput is a QQueue<double>
while (dataInput.size() > 60){
dataInput.dequeue();
}
update(); //this triggers the paint event
}
here is the graphing function
graph function:
//this draws the min line and the max line
QPainter painter(this);
int lineDist = 25;
QPen myPen(Qt::black, 3);
QPoint maxText(10,20);
painter.drawText(maxText, "max");
QPoint maxLineLeft(0, lineDist);
QPoint maxLineRight(width(), lineDist);
painter.drawLine(maxLineLeft, maxLineRight);
QPoint minText(10, height()-10);
painter.drawText(minText, "min");
QPoint minLineLeft(0, height()-lineDist);
QPoint minLineRight(width(), height()-lineDist);
//this draws the actual graph
myPen.setColor(Qt::blue);
myPen.setWidth(2);
painter.setPen(myPen);
double dist = (double)(heigh() - 2*lineDist);
int stepSize = (int)((double)width() / 60.0);
int heightStep = (max-min)/dist;
double x;
QPoint lastPoint(0,0);
QPoint nextPoint(0,0);
int i = 0;
if (!dataInput.empty()){ //checks that there is data
if (dataInput.size() < 60) { //ignoring for sake of brevety
} else {
x = dataInput.at(i);
x = max - x; // this inverts the data, necessary because (0,0) is the upper left corner
x = (x-min)/heightStep;
nextPoint.setX(0);
nextPoint.setY(x+lineDist);
for (i = 1; i < 60; i++){
x = dataInput.at(i);
x = max - x;
x = (x-min)/heightStep;
lastPoint = nextPoint;
nextPoint.setX(i*stepSize);
nextPoint.setY(x+lineDist);
painter.drawLine(lastPoint, nextPoint);
}
}
}
So, with one joystick that I am testing with, this program works without issue. As I move the joystick, the program draws the blue line within the bounds listed on the screen. x is input as a number between 0 and 65535, and after moving the joystick around a little bit the system recognizes that as the max and the min, and then proceeds to function as expected.
The second "joystick" is less of a joystick and more of a pressure sensor. This outputs either x = 1023 or x is some number between 5 and 9. In this case however, the blue line appears on the same level as the max line, or when the data output is x in the single digits, the blue line appears below min. I haven't been able to figure out why this would be the case, the first joystick can also produce results of 0, but it never goes below the minimum line. What might be causing this particular issue?
Two changes were needed to correct the issues.
First, stepSize and heightStep were both changed from int to double.
second, the section
x = dataInput.at(i);
x = max - x;
x = (x-min)/heightStep;
nextPoint.setX(0);
nextPoint.setY(x+lineDist);
for (i = 1; i < 60; i++){
x = dataInput.at(i);
x = max - x;
x = (x-min)/heightStep;
lastPoint = nextPoint;
nextPoint.setX(i*stepSize);
nextPoint.setY(x+lineDist);
painter.drawLine(lastPoint, nextPoint);
}
was changed to:
x = dataInput.at(i);
x = (x-min)/heightStep;
nextPoint.setX(0);
nextPoint.setY(dist-x+lineDist);
for (i = 1; i < 60; i++){
x = dataInput.at(i);
x = (x-min)/heightStep;
lastPoint = nextPoint;
nextPoint.setX(i*stepSize);
nextPoint.setY(dist-x+lineDist);
painter.drawLine(lastPoint, nextPoint);
}
This solved the issue and made the system always draw the data between the min and max lines.
I am working on an opengl assignment where I have to make a creature (I chose a snowman) move around some terrain. I am trying to make it move around, and I am getting the strangest errors. After printing the numbers out, I frequently get "-1.#QNAN0" as a number. I don't even know what that means. Below is the snowman's update function, constructor, and the header file. I am trying to get 2 numbers to use as velocity and add them to the position while it is set to animate (randomly changing), but I don't understand what errors are causing me to not get numbers out of rand().
Each time that the probability check succeeds, it prints out:
DEBUG: probability check succeeded
-1.#QNAN0 0.000000
or
DEBUG: probability check succeeded
0.000000 0.000000
with about 50% chance of each.
From Snowman.cpp
void Snowman::update(canvas_t texture){
//randomly toggle the walking variable
int probability = rand() % 100;
//printf("DEBUG: probability = %d\n", probability);
if(probability <= 10){
printf("DEBUG: probability check succeeded\n");
walking = !walking;
dx = static_cast<float>(( (rand() % 10) - 5));
dy = static_cast<float>(( (rand() % 10) - 5));
printf("%f %f\n", dx, dy);
}
//code to control movement
if(walking){
animate = true;
x += dx;
y += dy;
constrain(x, 0, texture.width);
constrain(y, 0, texture.height);
}else{
animate = false;
}
//set the height after x and y are resolved
z = getHeight(texture);
}
Snowman::Snowman(canvas_t terrain)
{
wireFrame = false;
animate = false;
armSegments = 2;
animationFrameNumber = 0;
manualUserOffset = 0;
//set its initial position
x = rand() % terrain.width;
y = rand() % terrain.height;
dx = 0;
dy = 0;
}
From Snowman.h
class Snowman
{
public:
Snowman(canvas_t);
~Snowman(void);
void setWireframe(bool);
void toggleWireframe(void);
void setAnimate(bool);
void toggleAnimate(void);
void setArmSegments(int);
void addArmSegment(void);
void subtractArmSegment(void);
void update(canvas_t);
void draw(void);
private:
bool wireFrame;
bool animate;
bool walking;
int armSegments;
int animationFrameNumber;
float manualUserOffset;
float x, y, z;
int dx, dy;
inline float f(void);
inline void drawMouth(int headRadius);
inline void drawFace(int headRadius);
void drawArm(int remainingSegments);
inline void drawBody();
inline float getHeight(canvas_t);
};
dx and dy are ints, but your format specifier %f requires a double or a float. So you have undefined behaviour.
I'm coding a simple roguelike game in C++ using SDL library, and I have some problems moving my character on the screen. Each time a frame needs to be rendered, I update the position of the sprite using the update() function, which does nothing if the player is standing still. To issue the movement command, and thus starting the animation, I use the step() function called only once per each player movement from one tile to another. Upon receiving the "up" command, the game behaves fine and the character moves smoothly in one second to the new position. However, when the "down" command is given, he moves at about half the speed, and obviously after one second has passed, he is instantly "teleported" to the final position, with a sudden flicker. The code for the movement is basically identical, but for the fact that in one case the delta movement is summed to the y position, in the other case is subtracted. Maybe the fact that the position is an integer and the delta is a double is causing problems? Does sum and subract behave differently (maybe different rounding)? Here is the relevant code (sorry for the length):
void Player::step(Player::Direction dir)
{
if(m_status != STANDING) // no animation while standing
return;
switch(dir)
{
case UP:
if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
// if next tile is not a wall, set up animation
m_status = WALKING_UP;
m_yDelta = m_currMap->tileHeight(); // sprite have to move by a tile
m_yVel = m_currMap->tileHeight() / 1000.0f; // in one second
m_yNext = m_yPos - m_currMap->tileHeight(); // store final destination
}
break;
case DOWN:
if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
m_status = WALKING_DOWN;
m_yDelta = m_currMap->tileHeight();
m_yVel = m_currMap->tileHeight() / 1000.0f;
m_yNext = m_yPos + m_currMap->tileHeight();
}
break;
//...
default:
break;
}
m_animTimer = SDL_GetTicks();
}
void Player::update()
{
m_animTimer = SDL_GetTicks() - m_animTimer; // get the ms passed since last update
switch(m_status)
{
case WALKING_UP:
m_yPos -= m_yVel * m_animTimer; // update position
m_yDelta -= m_yVel * m_animTimer; // update the remaining space
break;
case WALKING_DOWN:
m_yPos += m_yVel * m_animTimer;
m_yDelta -= m_yVel * m_animTimer;
break;
//...
default:
break;
}
if(m_xDelta <= 0 && m_yDelta <= 0) // if i'm done moving
{
m_xPos = m_xNext; // adjust position
m_yPos = m_yNext;
m_status = STANDING; // and stop
}
else
m_animTimer = SDL_GetTicks(); // else update timer
}
EDIT: I removed some variables and only left the elapsed time, the speed and the final position. Now it moves without flickering, but the down and right movements are visibly slower than the up and left ones. Still wonder why...
EDIT 2: Ok, I figured out why this is happening. As I supposed in the first place, there is a different rounding from double to integer when it comes to sum and subtraction. If I perform a cast like this:
m_xPos += (int)(m_xVel * m_animTimer);
the animation speed is the same, and the problem is solved.
Consider the following:
#include <iostream>
void main()
{
int a = 1, b = 1;
a += 0.1f;
b -= 0.1f;
std::cout << a << std::endl;
std::cout << b << std::endl;
}
During the implicit conversion of float to int when a and b are assigned, everything past the decimal point will be truncated and not rounded. The result of this program is:
1
0
You've said that m_yPos is an integer and m_yVel is a double. Consider what happens in Player::update if the result of m_yVel * m_animTimer is less than 1. In the UP case, the result will be that your sprite moves down one pixel, but in the DOWN case, your sprite won't move at all, because if you add less than one to an integer, nothing will happen. Try storing your positions as doubles and only converting them to integers when you need to pass them to the drawing functions.
A trick you can do to ensure rounding instead of truncation during conversion is to always add 0.5 to the floating point value during assignment to an integer.
For example:
double d1 = 1.2;
double d2 = 1.6;
int x = d1 + 0.5;
int y = d2 + 0.5;
In this case, x will become 1, while y will become 2.
I'd rather not do incremental calculations. This is simpler, will give correct results even if you go back in time, doesn't suffer from lost of precision, and will be just as fast, if not faster, on modern hardware:
void Player::step(Player::Direction dir)
{
// ...
case UP:
if(m_currMap->tileAt(m_xPos, m_yPos - m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
// if next tile is not a wall, set up animation
m_status = WALKING_UP;
m_yStart = m_yPos;
m_yDelta = -m_currMap->tileHeight(); // sprite have to move by a tile
m_tStart = SDL_GetTicks(); // Started now
m_tDelta = 1000.0f; // in one second
}
break;
case DOWN:
if(m_currMap->tileAt(m_xPos, m_yPos + m_currMap->tileHeight())->m_type == Tile::FLOOR)
{
m_status = WALKING_DOWN;
m_yStart = m_yPos;
m_yDelta = m_currMap->tileHeight();
m_tStart = SDL_GetTicks(); // Started now
m_tDelta = 1000.0f; // in one second
}
break;
// ...
}
void Player::update()
{
auto tDelta = SDL_GetTicks() - m_tStart;
switch(m_status)
{
case WALKING_UP:
case WALKING_DOWN:
m_yPos = m_yStart + m_yDelta*tDelta/m_tDelta; // update position
break;
default:
break;
}
if(tDelta >= m_tDelta) // if i'm done moving
{
m_xPos = m_xStart + m_xDelta; // adjust position
m_yPos = m_yStart + m_yDelta;
m_status = STANDING; // and stop
}
}