I'm trying to make a simple "falling sand" cellular automata, following these two guides:
https://www.youtube.com/watch?v=5Ka3tbbT-9E&t=29s
https://www.youtube.com/watch?v=VLZjd_Y1gJ8&t=62s
I'm using compute shader, along with Unity to calculate the position of next frame's particles.
#pragma kernel UpTex
#define AIR -1
#define SAND 0
struct blockInfo
{
int id;
int lifeTime;
float velocity;
float4 col;
int updated;
};
struct cellInf
{
int xPos;
int yPos;
};
RWStructuredBuffer<blockInfo> readBlock, writeBlock;
int sizeX, sizeY;
RWStructuredBuffer<cellInf> location;
int ToOneDi(int i, int j)
{
int newX = sizeY - 1 - j, newY = i;
return (sizeX * newX + (newY));
}
[numthreads(16,1,1)]
void UpTex (uint3 id : SV_DispatchThreadID)
{
int2 pos = int2(location[id.x].xPos, location[id.x].yPos);
int cur = ToOneDi(pos.x, pos.y);
blockInfo inf = readBlock[cur];
if (inf.updated == 1) {return; } //updated then skip
inf.updated = 1;
writeBlock[cur] = inf; //preemptive sets
if (pos.y == 0 || pos.x == 0 || pos.x == sizeX - 1) {return;} //base level
if (inf.id == SAND)
{
int left = ToOneDi(pos.x-1, pos.y-1);
int mid = ToOneDi(pos.x, pos.y-1);
int right = ToOneDi(pos.x+1, pos.y-1);
blockInfo leftBlock = readBlock[left];
blockInfo midBlock = readBlock[mid];
blockInfo rightBlock = readBlock[right];
//checking -> double check
if (midBlock.id == AIR)
{
writeBlock[mid] = inf;
writeBlock[cur] = midBlock;
}
else if (leftBlock.id == AIR)
{
writeBlock[left] = inf;
writeBlock[cur] = leftBlock;
}
else if (rightBlock.id == AIR)
{
writeBlock[right] = inf;
writeBlock[cur] = rightBlock;
}
else
{
writeBlock[cur] = inf;
}
}
//writeBlock[cur].col = float4(1, 0, 0, 1); //test
}
Currently I'm only simulating air and sand. readBlock is the previous frame info, writeBlock is current frame info (which would be utilized in another compute shader to apply to a texture and location just contained the 2D position of the cell to be updated.
With this code, it's clear that 2 sand block of the previous frame can cause a race condition by accessing the same cell (when falling into that cell), causing the color displayed to be changed.
I've tried to fix the problem by creating different groups to be processed linearly, and the cells in each groups separated enough to not cause a race condition to each other.
The new problem is, as the video shown, if I tried to move a particle (like sand) more than one block at a time (basically adding velocity to it), the grouping would have to be changed again. At some point it'd probably cause some form of diminishing returns if I have to create too many groups.
Any ideas how to fix the race conditions other than extra groupings?
Further note:
Both youtubers linked above and even the devs of the game that they use as reference (Noita - https://www.youtube.com/watch?v=prXuyMCgbTc) seems to agree that brute-force with a single thread, then using chunking and other techniques for optimization is just more bug-free and less headache.
The 2 recreations use C/Java, and the devs create their own custom engine with C++ (and probably OpenGL from what I can piece together) which from my experience already ran much faster. As such I haven't been able to find any other fix to my problem.
Links:
https://www.reddit.com/r/Games/comments/d7cqjz/comment/f0z8hu8/
https://www.reddit.com/r/gamedev/comments/d93op6/noita_pixel_simulation_any_tip_about_how_is_it/
Related
//The following game has been designed as an educational resource
//for Key Stage 1 and 2 children. Children are the future of
//civil engineering, and to inspire them to get involved in the
//industry is important for innovation. However, today the
//national curriculum is very structured, and many children
//can find themselves falling behind even at the age of 7 or 8.
//It is essential that children can be supported with material
//they find difficult, and given the resources to learn in a
//fun and engaging manner.
//One of the topics that many children struggle to grasp is
//fractions. It is necessary to prevent young children feeling
//like STEM subjects are too difficult for them, so that they
//have the opportunity and confidence to explore science and
//engineering subjects as they move into secondary education and
//careers.
//This game intends to set a precedent for teaching complex
//subjects to children in a simple, but fun and interactive
//manner. It will show them that fractions can be fun, and that
//they are capable, building confidence once they return to
//the classroom.
//The game will work by challenging the user to split a group
//of balls into three buckets depending on the fraction
//displayed on the bucket.
int number_of_balls;
float bucket_1, bucket_2, bucket_3;
int bucket_1_correct, bucket_2_correct, bucket_3_correct;
PVector basket_position, basket_dimensions;
Ball[] array_of_balls;
int linethickness;
//Random generator to give number of balls, ensuring that
//they can be divided into the number of buckets available.
void setup()
{
size(500,500);
linethickness = 4;
number_of_balls = int(random(1,11))*6;
println(number_of_balls);
bucket_1 = 1/6;
bucket_2 = 1/2;
bucket_3 = 1/3;
//Working out the correct answers
bucket_1_correct = number_of_balls*bucket_1;
bucket_2_correct = number_of_balls*bucket_2;
bucket_3_correct = number_of_balls*bucket_3;
println (bucket_1, bucket_2, bucket_3);
println (bucket_1_correct, bucket_2_correct, bucket_3_correct);
//Creating the basket
basket_position = new PVector(width/4, height/8);
basket_dimensions = new PVector(width/2, height/4);
//Creating the balls & placing inside basket
array_of_balls = new Ball[number_of_balls];
for (int index=0; index<number_of_balls; index++)
{
array_of_balls[index] = new Ball();
}
}
//Drawing the balls and basket outline
void draw()
{
background (125,95,225);
for (int index=0; index<number_of_balls; index++)
{
array_of_balls[index].Draw();
}
noFill();
stroke(180,0,0);
strokeWeight(linethickness);
rect(basket_position.x, basket_position.y, basket_dimensions.x, basket_dimensions.y);
}
void mouseDragged()
{
if ((mouseX >= (ball_position.x - radius)) && (mouseX <= (ball_position.x + radius)) && (mouseY >= (ball_position.y - radius)) && (mouseY <= (ball_position.y + radius)))
{
ball_position = new PVector (mouseX, mouseY);
}
}
//Ball_class
int radius;
Ball()
{
radius = 10;
ball_position = new PVector (random(basket_position.x + radius + linethickness, basket_position.x + basket_dimensions.x - radius - linethickness), random(basket_position.y + radius + linethickness, basket_position.y + basket_dimensions.y - radius - linethickness));
colour = color(random(255), random(255), random(255));
}
void Draw()
{
noStroke();
fill(colour);
ellipse(ball_position.x,ball_position.y,radius*2,radius*2);
}
}
Thanks in advance for your help! I am using Processing 2.2.1 which I know is very out of date, so struggling to find help.
I have a piece of code that has created a number of balls, and I would like to be able to 'drag and drop' these to a different location on the screen as part of an educational game. I've tried playing around with mousePressed() and mouseDragged() but no luck yet. Any advice would be appreciated!
There are a lot of ways to approach this, but one way I could suggest is doing something like this:
// "Ellipse" object
function Ellipse (x, y, width, height) {
// Each Ellipse object has their own x, y, width, height, and "selected" values
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.selected = false;
// You can call the draw function whenever you want something done with the object
this.draw = function() {
// Draw ellipse
ellipse(this.x, this.y, this.width, this.height);
// Check if mouse is touching the ellipse using math
// https://www.desmos.com/calculator/7a9u1bpfvt
var xDistance = this.x - mouseX;
var yDistance = this.y - mouseY;
// Ellipse formula: (x^2)/a + (y^2)/b = r^2
// Assuming r = 1 and y = 0:
// 0 + (x^2)/a = 1 Substitute values
// ((width / 2)^2)/a = 1 x = width / 2 when y = 0
// a = (width / 2)^2 Move numbers around
// a = (width^2) / 4 Evaluate
var a = Math.pow(this.width, 2) / 4;
// Assuming r = 1 and x = 0:
// 0 + (y^2)/b = 1 Substitute values
// ((height / 2)^2)/b = 1 y = height / 2 when x = 0
// b = (height / 2)^2 Move numbers around
// b = (height^2) / 4 Evaluate
var b = Math.pow(this.height, 2) / 4;
// x^2
var x2 = Math.pow(xDistance, 2);
// y^2
var y2 = Math.pow(yDistance, 2);
// Check if coordinate is inside ellipse and mouse is pressed
if(x2 / a + y2 / b < 1 && mouseIsPressed) {
this.selected = true;
}
// If mouse is released, deselect the ellipse
if(!mouseIsPressed) {
this.selected = false;
}
// If selected, then move the ellipse
if(this.selected) {
// Moves ellipse with mouse
this.x += mouseX - pmouseX;
this.y += mouseY - pmouseY;
}
};
}
// New Ellipse object
var test = new Ellipse(100, 100, 90, 60);
draw = function() {
background(255);
// Do everything associated with that object
test.draw();
};
The math is a bit funky, and I might not be using the right version of Processing, but hopefully you found this at least slightly helpful :)
I'm kind of confused about what language you're using. Processing is a wrapper for Java, not JavaScript. Processing.js went up to version 1.6.6 and then was succeeded by p5.js. I'm going to assume you're using p5.js.
I don't know if this is a new thing in p5.js, but for easy, but not very user-friendly click-and-drag functionality I like to use the built-in variable mouseIsPressed.
If the ellipse coordinates are stored in an array of vectors, you might do something like this:
let balls = [];
let radius = 10;
function setup() {
createCanvas(400, 400);
for (let i = 0; i < 10; i++) {
balls.push(createVector(random(width), random(height)));
}
}
function draw() {
background(220);
for (let i = 0; i < balls.length && mouseIsPressed; i++) {
if (dist(mouseX, mouseY, balls[i].x, balls[i].y) < radius) {
balls[i] = createVector(mouseX, mouseY);
i = balls.length;
}
}
for (let i = 0; i < balls.length; i++) {
ellipse(balls[i].x, balls[i].y,
2 * radius, 2 * radius
);
}
}
This is the quickest way I could think of, but there are better ways to do it (at least, there are in p5.js). You could make a Ball class which has numbers for x, y, and radius, as well as a boolean for whether it's being dragged. In that class, you could make a method mouseOn() which detects whether the cursor is within the radius (if it's not a circle, you can use two radii: sq((this.x - mouseX)/r1) + sq((this.y - mouseY)/r2) < 1).
When the mouse is pressed, you can cycle through all the balls in the array of balls, and test each of them with mouseOn(), and set their drag boolean to true. When the mouse is released, you can set all of their drag booleans to false. Here's what it looks like in the current version of p5.js:
function mousePressed() {
for (let i = 0; i < balls.length; i++) {
balls[i].drag = balls[i].mouseOn();
if (balls[i].drag) {
i = balls.length;
}
}
}
function mouseReleased() {
for (let i = 0; i < balls.length; i++) {
balls[i].drag = false;
}
}
I hope this helps.
The way your code is right now doesn't work in the current version of Processing either, but it's a pretty quick fix. I'm going to show you a way to fix that, and hopefully it'll work in the earlier version.
Here's where I think the problem is: when you use mouseDragged(), you try to change ball_position, but you don't specify which ball's position. Here's one solution, changing the mouseDragged() block and the Ball class:
void mouseDragged() {
for (int i = 0; i < array_of_balls.length; i++) {
if ((mouseX > (array_of_balls[i].ball_position.x - array_of_balls[i].radius)) &&
(mouseX < (array_of_balls[i].ball_position.x + array_of_balls[i].radius)) &&
(mouseY > (array_of_balls[i].ball_position.y - array_of_balls[i].radius)) &&
(mouseY < (array_of_balls[i].ball_position.y + array_of_balls[i].radius))
) {
array_of_balls[i].ball_position = new PVector (mouseX, mouseY);
i = array_of_balls.length;
}
}
}
//Ball_class
class Ball {
int radius;
PVector ball_position;
color colour;
Ball() {
radius = 10;
ball_position = new PVector (random(basket_position.x + radius + linethickness, basket_position.x + basket_dimensions.x - radius - linethickness), random(basket_position.y + radius + linethickness, basket_position.y + basket_dimensions.y - radius - linethickness));
colour = color(random(255), random(255), random(255));
}
void Draw() {
noStroke();
fill(colour);
ellipse(ball_position.x, ball_position.y, radius*2, radius*2);
}
}
P.S. Since you're using a language based in Java, you should probably adhere to the finnicky parts of the language:
data types are very strict in Java. Avoid assigning anything that could possibly be a float to a variable that is declared as an int. For example, in your setup() block, you say bucket_1_correct = number_of_balls*bucket_1;. This might seem like not an issue, since number_of_balls*bucket_1 is always going to be a whole number. But since the computer rounds when saving bucket_1 = 1/6, multiplying it by 6 doesn't necessarily give a whole number. In this case, you can just use round(): bucket_1_correct = round(number_of_balls*bucket_1);
Regarding data types, you should always declare your variables with their data type. It's a little hard for me to tell, but it looks to me like you never declared ball_position or colour in your Ball class, and you never opened up the class with the typical class Ball {. This might have been a copy/paste error, though.
When I set an unreachable target position with SetDestination() for my NavMeshAgent and Debug.Log() the NavMeshAgent.remainingDistance every frame, I get Infinity for some of the path until it starts returning floats (21.21864, 21.0846, 20.95449...) until it gets as close to the destination as possible, which returns 0.
As of Unity 2019.3, NavMeshAgent.remainingDistance is still calculated only after the penultimate corner of the path has been reached, and the agent is traversing the last segment. Before that, remainingDistance will return infinity. Sadly, this is undocumented.
Here is a NavMeshAgent extension method to get the remaining distance at any moment, or any point of the path:
public static class ExtensionMethods
{
public static float GetPathRemainingDistance(this NavMeshAgent navMeshAgent)
{
if (navMeshAgent.pathPending ||
navMeshAgent.pathStatus == NavMeshPathStatus.PathInvalid ||
navMeshAgent.path.corners.Length == 0)
return -1f;
float distance = 0.0f;
for (int i = 0; i < navMeshAgent.path.corners.Length - 1; ++i)
{
distance += Vector3.Distance(navMeshAgent.path.corners[i], navMeshAgent.path.corners[i + 1]);
}
return distance;
}
}
So instead of using NavMeshAgent.remainingDistance, you can use NavMeshAgent.GetPathRemainingDistance(). Be aware though this can be performance expensive depending the situation, so have that in mind when using it.
As for the second part of your question, we would need more contextual information of your setup, but sounds like your target position may have an offset towards the up vector, while the agent is constrained to the x, z plane, but this is only especulation.
I made this simple script that measures all agent path corner points and adds up the distances. Or just returns agent.remaingDistance if the remaining path is straight.
public float GetRemainingDistance()
{
float distance = 0;
Vector3[] corners = m_navMeshAgent.path.corners;
if (corners.Length > 2)
{
for (int i = 1; i < corners.Length; i++)
{
Vector2 previous = new Vector2(corners[i - 1].x, corners[i - 1].z);
Vector2 current = new Vector2(corners[i].x, corners[i].z);
distance += Vector2.Distance(previous, current);
}
}
else
{
distance = m_navMeshAgent.remainingDistance;
}
return distance;
}
Works well in my project.
I've been struggling to create a game with a competent (but fair) racing AI, and I have several constraints that I'm trying to meet. Here's the conditions in order:
1.) The AI logic and player controls BOTH share the same car controller to drive and turn, etc. The AI vehicle simply passes a clamped (-1 to 1) value depending on how far the car is trying to turn and the throttle is engaged. Both the AI and player share the boolean to brake, and drift turning with the SHIFT key
2.) The AI needs to make smart, informed decisions that the player would in applying the right amount of turn, when to coast, etc.
I originally used a plain waypoint increment system to have the AI continue lapping around the track depending on the waypoints. With the right wheel friction curve values (which I happened to luckily find online) this actually works OK. However, while I was struggling more with the wheel friction values especially, and still want to have a smoother path following and turning logic for the AI, it was suggested to me to use cubic Bezier splines. I found the Catlike Coding tutorial fascinating (I'm sure many of you know this, but here's the link below for anyone interested):
https://catlikecoding.com/unity/tutorials/curves-and-splines/
I then got the idea of using the Spline Walker from this tutorial to be the ONE waypoint for the AI, and effectively "tease" the car to follow the spline walker, like this video:
https://www.youtube.com/watch?v=UcA4K2rmX-U#action=share
However, here's my big problem - if I want to have my car FOLLOW the spline walker, while keeping the spline walker always in front, I have to ensure the "progress" that the spline walker follows is relative to the position of the following car, with the spline walker remaining a LITTLE in front so that the car doesn't decide to slow down and stop.
I've been looking for examples to do this, and I can't say I've been too successful.
Here's my current progress calculating code snippets - I'm trying to store distances between positions on the spline right now, via the old waypoint objects which happen to share the same transform coordinates:
private void Start()
{
Rigidbody rb = chasingCar.GetComponent<Rigidbody>();
if(path)
{
nodes = path.GetComponentsInChildren<Transform>();
}
Array.Resize(ref distances, nodes.Length-1);
for (int i = 0; i < nodes.Length-1; i++)
{
//start storing the distances between two successive waypoints on the spline
distances[i] = Vector3.Distance(nodes[i].position, nodes[i + 1].position);
totalDistance += distances[i];
}
Debug.Log("First distance value is " + distances[0] + " and overall distance est is " + totalDistance);
Debug.Log("Second distance value is " + distances[1] + " and overall distance est is " + totalDistance);
Debug.Log("Fifth distance value is " + distances[4] + " and overall distance est is " + totalDistance);
}
This is in the update function for the spline walker when the chasing car and it's old waypoint path have been provided to the spline:
Vector3 position;
if (chasingCar && path)
{
float distFromCar = Vector3.Distance(transform.position, chasingCar.transform.position);
Debug.Log("Distance from car " + distFromCar);
if(distFromCar < 35)
{
//get current spline waypoint
//int splineIdx = GetSplineIndex(progress);
int splineIdx = chasingCar.GetComponent<CarEngine>().GetCurrentNodeTarget();
//declare next spline waypoint
int splineIdxNext = splineIdx + 1;
if (path && splineIdxNext == (nodes.Length))
splineIdxNext = 0;
Debug.Log("Current splineIdx " + splineIdx);
//float currCarDistance = Vector3.Distance(chasingCar.transform.position, nodes[splineIdx].position);
float currCarDistance = SumSplineProgress(splineIdx);
float overallDistance = Vector3.Distance(nodes[splineIdx].position, nodes[splineIdxNext].position);
float currCarSplineProgress = currCarDistance / overallDistance;
float overallProgress = (currCarDistance) / (totalDistance);
progress = overallProgress;
}
else
{
progress += Time.deltaTime / duration;
}
Debug.Log("Chasing, current progress: " + progress);
position = spline.GetPoint(progress);
Finally, here's the functions I've tried to use to calculate the spline walker progress in the past:
int GetSplineIndex(float progress)
{
float curProgress = progress * (totalDistance);
Debug.Log("Current calculated progress " + curProgress);
return System.Convert.ToInt32(Mathf.Floor(curProgress));
}
float SumSplineProgress(int index)
{
float currTotalDistance = 0f;
for(int i = index; i > -1; i--)
{
currTotalDistance += distances[i];
}
return currTotalDistance;
}
I might just be making it harder on myself than I need to, but I'm just going to say, I'm legit stumped. I got close with having the spline waypoint jump ahead of the car MORE when there is more distance between the current start and end waypoint for the AI car, but that's still not what I'm trying to achieve.
Anyone have any particular suggestions here? Tips, nudges in the direction, and code would be fantastic. Thanks in advance!
UPDATE
Yes, I'm still working on this! There was some logic that I regarded as faulty in the previous spline calculation code - for instance, this:
float currCarSplineProgress = currCarDistance / overallDistance;
I've changed to this:
float currCarSplineProgress = (currCarDistance) / currSplineLength;
The idea of that part is to check the car's progress on the current curve it is traveling close to in the overall spline, and position the spline walker accordingly so that it jumps ahead to the next spline when needed. Here's the full updated code:
Vector3 position;
if (chasingCar && path)
{
float distFromCar = Vector3.Distance(transform.position, chasingCar.transform.position);
Debug.Log("Distance from car " + distFromCar);
if(distFromCar < 50)
{
//get current spline waypoint
//int splineIdx = GetSplineIndex(progress);
int splineIdx = chasingCar.GetComponent<CarEngine>().GetCurrentNodeTarget()-1;
//declare next spline waypoint
int splineIdxNext = splineIdx + 1;
if(splineIdx == -1)
{
splineIdx = nodes.Length - 2;
splineIdxNext = 0;
}
if (path && splineIdxNext == (nodes.Length))
splineIdxNext = 0;
Debug.Log("Current splineIdx " + splineIdx);
//float currCarDistance = GetConvertedDistance(chasingCar.transform.position, nodes[splineIdx].position);
float currCarDistance = Vector3.Distance(chasingCar.transform.position, nodes[splineIdx].position);
float restDistance = Vector3.Distance(chasingCar.transform.position, nodes[splineIdxNext].position);
//float currCarDistance = SumSplineProgress(splineIdx);
Debug.Log("currCarDistance " + currCarDistance);
//float currSplineLength = Vector3.Distance(nodes[splineIdx].position, nodes[splineIdxNext].position);
float currSplineLength = currCarDistance + restDistance;
float overallDistance = 0;
float nextOverallDist = 0f;
if(splineIdx != 0)
overallDistance = SumSplineProgress(splineIdx-1);
Debug.Log("overallDistance " + overallDistance);
float currCarSplineProgNext = 0f;
if (splineIdxNext != 1 && splineIdxNext != 0)
{
nextOverallDist = SumSplineProgress(splineIdxNext - 1);
currCarSplineProgNext = (currCarDistance) / nextOverallDist;
}
Debug.Log("currSplineLength " + currSplineLength);
float currCarSplineProgress = (currCarDistance) / currSplineLength;
float leading = 10f;
if (distFromCar < 20)
leading += 15f;
float overallProgress;
Debug.Log("currCarSplineProgress " + currCarSplineProgress);
if (currCarSplineProgress < .7f)
{
overallProgress = (currSplineLength + (currCarDistance * .3f)) / (totalDistance);
}
else
{
Debug.Log("Jumping to next waypoint...");
overallProgress = (nextOverallDist + (currCarDistance * .3f)) / (totalDistance);
}
Debug.Log("Overall progress " + overallProgress);
//if (overallProgress >= 1f)
// overallProgress = 0f;
progress = overallProgress;
}
else
{
progress += Time.deltaTime / duration;
}
Debug.Log("Chasing, current progress: " + progress);
position = spline.GetPoint(progress);
}
else
{
position = spline.GetPoint(progress);
}
transform.localPosition = position;
Yet, unexpected things STILL happen when the car makes enough progress - the spline walker will just suddenly jump to one of the previous sections!
Any insights?
You have the right idea for making the AI chase a target moving along the spline. I believe that's how most AI in racing games work.
To ensure the target is always ahead of the AI, set the target's position to the AI's position on the spline added by some value related to how fast the AI is moving.
I would suggest checking out the Unity Standard Assets package:
https://assetstore.unity.com/packages/essentials/asset-packs/standard-assets-for-unity-2017-3-32351
There is a car AI system in there that works by following a target that is moving along a spline.
Is there a build-in way how to get a time by value from Animation curve in Unity3d? (The opposite way of Evaluate)
I need to achieve this (instead of getting value from time):
float time = AnimationCurve.Evaluate(float value);
Generically speaking getting X value from Y value.
I know this is 3 years old, but I found via a Google search, and in case someone else lands here:
I simply create an inverse curve, which allows me to look up by time.
public AnimationCurve speedCurve;
private AnimationCurve inverseSpeedCurve;
private void Start()
{
//create inverse speedcurve
inverseSpeedCurve = new AnimationCurve();
for (int i = 0; i < speedCurve.length; i++)
{
Keyframe inverseKey = new Keyframe(speedCurve.keys[i].value, speedCurve.keys[i].time);
inverseSpeedCurve.AddKey(inverseKey);
}
}
Just a basic implementation maybe it will give an idea for you. Method loops through all time and if your value is near that value at that time it will yield. It's a coroutine but you can change it to use inside Update maybe?
public AnimationCurve curve;
public float valToTime = .5f;
public float treshold = .005f;
public float yourTime;
IEnumerator valueToTime(float determineTime)
{
float timeCounter = 0;
Keyframe[] k = curve.keys;
float endTime = k[k.Length-1].time;
Debug.Log("end "+endTime);
while(timeCounter < endTime)
{
float val = curve.Evaluate(timeCounter);
Debug.Log("val "+ val + " time "+timeCounter);
// have to find a better solution for treshold sometimes it misses(use Update?)!
if(Mathf.Abs(val - determineTime) < treshold)
{
//Your time would be this
yourTime = timeCounter;
yield break;
}
else
{
//If it's -1 than a problem occured, try changing treshold
yourTime = -1f;
}
timeCounter += Time.deltaTime;
yield return null;
}
}
Putting together the best elements of most of the solutions posted, I've come up with an approach that produces pretty high accuracy. It involves doing the work upfront and so, is also quite efficient.
Note: If the original curve possesses any maximum/minimum point (points on the curve with a gradient of zero) this method will still attempt to invert it but can only do so by introducing several discontinuities to the inverted curve. It is not ideal for such cases.
Evaluate the original curve at several "sample-points" using a "sample-delta" constant.
For each "value" evaluated, compute the tangent at that point as the "sample-delta" / "value-delta".
Create keyframes that use the "value" as the "time" and the "sample-point" as the "value", and set the "inTangent" and "outTangent" to the tangent obtained in Step 3.
Add the keyframe generated at every "sample-point" to a new AnimationCurve().
The new AnimationCurve() is therefore an inverted version of the original.
Smooth the tangents of the new AnimationCurve() (the inverted version) to remove discontinuities caused by sudden and rapid tangent changes. NB: Smoothing the tangents may make the inverted curve lose it's general definition if the original curve had at least one maximum/minimum point.
Image of Normal Curve vs Inverted Curve:
invertedCurve = new AnimationCurve();
float totalTime = normalCurve.keys[normalCurve.length - 1].time;
float sampleX = 0; //The "sample-point"
float deltaX = 0.01f; //The "sample-delta"
float lastY = normalCurve.Evaluate(sampleX);
while (sampleX < totalTime)
{
float y = normalCurve.Evaluate(sampleX); //The "value"
float deltaY = y - lastY; //The "value-delta"
float tangent = deltaX / deltaY;
Keyframe invertedKey = new Keyframe(y, sampleX, tangent, tangent);
invertedCurve.AddKey(invertedKey);
sampleX += deltaX;
lastY = y;
}
for(int i = 0; i < invertedCurve.length; i++)
{
invertedCurve.SmoothTangents(i, 0.1f);
}
I needed this very thing just now, so I came up with this. I found it quite accurate and fast (an accuracy value of 10 was enough, and even lower may have done). But it will only work on curves that have ONE definite time for each value (i.e. nothing like waves with multiple times having the same value).
Similar to the other answer, it iterates through possible times - but rather than in a linear fashion the step value starts as the entire time range and halves each time.
Hope it's useful for you.
// NB. Will only work for curves with one definite time for each value
public float GetCurveTimeForValue( AnimationCurve curveToCheck, float value, int accuracy ) {
float startTime = curveToCheck.keys [0].time;
float endTime = curveToCheck.keys [curveToCheck.length - 1].time;
float nearestTime = startTime;
float step = endTime - startTime;
for (int i = 0; i < accuracy; i++) {
float valueAtNearestTime = curveToCheck.Evaluate (nearestTime);
float distanceToValueAtNearestTime = Mathf.Abs (value - valueAtNearestTime);
float timeToCompare = nearestTime + step;
float valueAtTimeToCompare = curveToCheck.Evaluate (timeToCompare);
float distanceToValueAtTimeToCompare = Mathf.Abs (value - valueAtTimeToCompare);
if (distanceToValueAtTimeToCompare < distanceToValueAtNearestTime) {
nearestTime = timeToCompare;
valueAtNearestTime = valueAtTimeToCompare;
}
step = Mathf.Abs(step * 0.5f) * Mathf.Sign(value-valueAtNearestTime);
}
return nearestTime;
}
just stumbled upon this problem myself and didn't like the solutions mentioned here, so i wanted to share my own. It's rather an adaption to the answer which inverts the keyframes.
I improved it by also inverting the tangents and the weight of the points.
I'm sure there is an easier way, but i found this working nicely for reversing the animationcurve.
Edit: Forgot to mention, for me it only worked when the tangents are set to weighted, i don't know what weight calculation unity does when you set it to auto or similar, so weighted was predicatable and easy to inverse.
inverseCurve = new AnimationCurve();
for (int i = 0; i < initialCurve.length; i++)
{
float inWeight = (initialCurve.keys[i].inTangent * initialCurve.keys[i].inWeight) / 1;
float outWeight = (initialCurve.keys[i].outTangent * initialCurve.keys[i].outWeight) / 1;
Keyframe inverseKey = new Keyframe(initialCurve.keys[i].value, initialCurve.keys[i].time, 1/initialCurve.keys[i].inTangent, 1/initialCurve.keys[i].outTangent, inWeight, outWeight);
inverseCurve.AddKey(inverseKey);
}
Thought I'd share my own version, as suggested in other forums too I tried looping over Evaluate() instead of reversing the whole curve which I think is overkill and not always feasible.
This checks for a value approximation down to the indicated decimals, it also assumes that the curve has "normalized" time (if it wasn't the case this could be expanded by looking for the smallest and the biggest time keys.
/// <summary>
/// Inverse of Evaluate()
/// </summary>
/// <param name="curve">normalized AnimationCurve (time goes from 0 to 1)</param>
/// <param name="value">value to search</param>
/// <returns>time at which we have the closest value not exceeding it</returns>
public static float EvaluateTime(this AnimationCurve curve, float value, int decimals = 6) {
// Retrieve the closest decimal and then go down
float time = 0.1f;
float step = 0.1f;
float evaluate = curve.Evaluate(time);
while(decimals > 0) {
// Loop until we pass our value
while(evaluate < value) {
time += step;
evaluate = curve.Evaluate(time);
}
// Go one step back and increase precision of the step by one decimal
time -= step;
evaluate = curve.Evaluate(time);
step /= 10f;
decimals--;
}
return time;
}
I want to build an app that calculates accurate Distance travelled by iPhone (not long distance) using Gyro+Accelerometer. No need for GPS here.
How should I approach this problem?
Basic calculus behind this problem is in the expression
(and similar expressions for displacements in y and z) and basic geometry is the Pythagorean theorem
So, once you have your accelerometer signals passed through a low-pass filter and binned in time with sampling interval dt, you can find the displacement in x as (pardon my C...)
float dx=0.0f;
float vx=0.0f;
for (int i=1; i<n; i++)
{
vx+=(acceleration_x[i-1] + acceleration_x[i])/2.0f*dt;
dx+=vx*dt;
}
and similarly for dy and dz. Here
float acceleration_x[n];
contains x-acceleration values from start to end of measurement at times 0, dt, 2*dt, 3*dt, ... (n-1)*dt.
To find the total displacement, you just do
dl=sqrt(dx*dx + dy*dy + dz*dz);
Gyroscope is not necessary for this, but if you are measuring linear distances, you can use the gyroscope reading to control that rotation of the device was not too large. If rotation was too strong, make the user re-do the measurement.
You get position by integrating the linear acceleration twice but the error is horrible. It is useless in practice.
Here is an explanation why (Google Tech Talk) at 23:20. I highly recommend this video.
Similar questions:
track small movements of iphone with no GPS
What is the real world accuracy of phone accelerometers when used for positioning?
how to calculate phone's movement in the vertical direction from rest?
iOS: Movement Precision in 3D Space
How to use Accelerometer to measure distance for Android Application Development
Distance moved by Accelerometer
Update (24 Feb 2013): #Simon Yes, if you know more about the movement, for example a person walking and the sensor is on his foot, then you can do a lot more. These are called
domain specific assumptions.
They break miserably if the assumptions do not hold and can be quite cumbersome to implement. Nevertheless, if they work, you can do fun things. See the links in my answer Android accelerometer accuracy (Inertial navigation) at indoor positioning.
You should use the Core Motion interface like described in Simple iPhone motion detect. Especially all rotations can be tracked very accurately. If you plan to do something related to linear movements this is very hard stuff. Have a look at Getting displacement from accelerometer data with Core Motion.
I took a crack at this and gave up (late at night, didn't seem to be getting anywhere). This is for a Unity3d project.
If anyone wants to pick up where I left off, I would be happy to elaborate on what all this stuff does.
Basically after some of what turned out to be false positives, I thought I'd try and filter this using a low pass filter, then attempted to remove bounces by finding a trend, then (acc_x[i-1]+acc_x[i])/2.
It looks like the false positive is still coming from the tilt, which I attempted to remove..
If this code is useful or leads you someplace, please let me know!
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// rbi.noli#gmail.com
/// </summary>
public class AccelerometerInput : MonoBehaviour
{
Transform myTransform;
Gyroscope gyro;
GyroCam gyroCam;
void Awake()
{
gyroCam= FindObjectOfType<GyroCam> ();
myTransform = transform;
if (SystemInfo.supportsGyroscope) {
gyro = Input.gyro;
gyro.enabled = true;
}
}
bool shouldBeInitialized = false;
void Update ()
{
transform.Translate (GetAccelerometer ());// * Time.deltaTime * speed);
//GetComponent<Rigidbody> ().AddForce (GetAccelerometer ());
}
public float speed = 10.0F;
public Vector3 dir;
public float f;
Vector3 GetAccelerometer()
{
dir = Input.acceleration;
dir.x *= gyro.attitude.x;
dir.z *= gyro.attitude.z;
if (Mathf.Abs (dir.x) < .001f)
dir.x = 0;
dir.y = 0;
if (Mathf.Abs (dir.z) < .001f)
dir.z = 0;
RecordPointsForFilter (dir);
//print ("Direction : " + dir.ToString("F7"));
return TestPointsForVelocity();
}
Vector3[] points = new Vector3[20];
int index;
void RecordPointsForFilter(Vector3 recentPoint)
{
if (index >= 20)
index = 0;
points [index] = EvaluateTrend (recentPoint);;
index++;
}
//try to remove bounces
float xTrend = 0;
float zTrend = 0;
float lastTrendyX = 0;
float lastTrendyZ = 0;
Vector3 EvaluateTrend(Vector3 recentPoint)
{
//if the last few points were positive, and this point is negative, don't pass it along
//accumulate points into a trend
if (recentPoint.x > 0)
xTrend += .01f;
else
xTrend -= .1f;
if (recentPoint.z > 0)
zTrend += .1f;
else
zTrend -= .1f;
//if point matches trend, keep it
if (xTrend > 0) {
if (recentPoint.x > 0)
lastTrendyX = recentPoint.x;
} else // xTrend < 0
if (recentPoint.x < 0)
lastTrendyX = recentPoint.x;
if (zTrend > 0) {
if (recentPoint.z > 0)
lastTrendyZ = recentPoint.z;
} else // xTrend < 0
if (recentPoint.z < 0)
lastTrendyZ = recentPoint.z;
return new Vector3( lastTrendyX, 0, lastTrendyZ);
}
Vector3 TestPointsForVelocity()
{
float x = 0;
float z = 0;
float xAcc = 0;
float zAcc = 0;
int successfulHits = 0;
for(int i = 0; i < points.Length; i++)
{
if(points[i]!=null)
{
successfulHits ++;
xAcc += points[i].x;
zAcc += points[i].z;
}
}
x = xAcc / successfulHits;
z = zAcc / successfulHits;
return new Vector3 (x, 0, z);
}
}
(acc_x[i-1]+acc_x[i])/2 is a low pass filter, it is the mean value between two measures in time
also look at here : http://www.freescale.com/files/sensors/doc/app_note/AN3397.pdf
pag :3
Navisens.
https://navisens.com/#how-work
Here the claim - Navisens patent-pending technology processes accelerometer and gyroscope data in a unique way to locate your phone.
Tried out the demo application, which works mostly in mapping the movements with out Location Services or WiFi once the inital location & direction are set.
iOS SDK - https://github.com/navisens/iOS-SDK
Android SDK - https://github.com/navisens/Android-SDK
Note: This is not open source
Here is the answer. Somebody asked before.
There is an app called RangeFinder doing the same thing ( available in App Store ) .