I have array of 2d points and I need to create a Path that passes through all of the points. I think I should use Path.cubicTo() method which creates bezier curve between two points using specified control points. The problem is that I don't know control points of my curve. How do I calculate them?
Maybe there's a better way of doing this? Maybe there's some sort of library that could help me?
After I read this articles it became quite simple.
This is how you do it on android. After you run this code your path p will go through all points from knotsArr array.
Point[] knotsArr = {new Point(0, 0),
new Point(5, 5),
new Point(10, 0),
new Point(15, 5)};
Point[][] controlPoints = BezierSplineUtil.getCurveControlPoints(knotsArr);
Point[] firstCP = controlPoints[0];
Point[] secondCP = controlPoints[1];
Path p = new Path();
p.moveTo(knots.get(0).x, knots.get(0).y);
for (int i = 0; i < firstCP.length; i++) {
p.cubicTo(firstCP[i].x, firstCP[i].y,
secondCP[i].x, secondCP[i].y,
knots.get(i + 1).x, knots.get(i + 1).y);
}
BezierSplineUtil.java
public class BezierSplineUtil {
public static class Point {
public final float x;
public final float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
}
/**
* Get open-ended bezier spline control points.
*
* #param knots bezier spline points.
* #return [2 x knots.length - 1] matrix. First row of the matrix = first
* control points. Second row of the matrix = second control points.
* #throws IllegalArgumentException if less than two knots are passed.
*/
public static Point[][] getCurveControlPoints(Point[] knots) {
if (knots == null || knots.length < 2) {
throw new IllegalArgumentException("At least two knot points are required");
}
final int n = knots.length - 1;
final Point[] firstControlPoints = new Point[n];
final Point[] secondControlPoints = new Point[n];
// Special case: bezier curve should be a straight line
if (n == 1) {
// 3P1 = 2P0 + P3
float x = (2 * knots[0].x + knots[1].x) / 3;
float y = (2 * knots[0].y + knots[1].y) / 3;
firstControlPoints[0] = new Point(x, y);
// P2 = 2P1 - P0
x = 2 * firstControlPoints[0].x - knots[0].x;
y = 2 * firstControlPoints[0].y - knots[0].y;
secondControlPoints[0] = new Point(x, y);
return new Point[][] { firstControlPoints, secondControlPoints };
}
// Calculate first bezier control points
// Right hand side vector
float[] rhs = new float[n];
// Set right hand side X values
for (int i = 1; i < n - 1; i++) {
rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
}
rhs[0] = knots[0].x + 2 * knots[1].x;
rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2f;
// Get first control points X-values
float[] x = getFirstControlPoints(rhs);
// Set right hand side Y values
for (int i = 1; i < n - 1; i++) {
rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
}
rhs[0] = knots[0].y + 2 * knots[1].y;
rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2f;
// Get first control points Y-values
float[] y = getFirstControlPoints(rhs);
for (int i = 0; i < n; i++) {
// First control point
firstControlPoints[i] = new Point(x[i], y[i]);
// Second control point
if (i < n - 1) {
float xx = 2 * knots[i + 1].x - x[i + 1];
float yy = 2 * knots[i + 1].y - y[i + 1];
secondControlPoints[i] = new Point(xx, yy);
} else {
float xx = (knots[n].x + x[n - 1]) / 2;
float yy = (knots[n].y + y[n - 1]) / 2;
secondControlPoints[i] = new Point(xx, yy);
}
}
return new Point[][] { firstControlPoints, secondControlPoints };
}
/**
* Solves a tridiagonal system for one of coordinates (x or y) of first
* bezier control points.
*
* #param rhs right hand side vector.
* #return Solution vector.
*/
private static float[] getFirstControlPoints(float[] rhs) {
int n = rhs.length;
float[] x = new float[n]; // Solution vector
float[] tmp = new float[n]; // Temp workspace
float b = 2.0f;
x[0] = rhs[0] / b;
// Decomposition and forward substitution
for (int i = 1; i < n; i++) {
tmp[i] = 1 / b;
b = (i < n - 1 ? 4.0f : 3.5f) - tmp[i];
x[i] = (rhs[i] - x[i - 1]) / b;
}
// Backsubstitution
for (int i = 1; i < n; i++) {
x[n - i - 1] -= tmp[n - i] * x[n - i];
}
return x;
}
}
Related
I am developing an Augmented reality application with Rajawali lib. My problem is as below.
I want to draw a surface onto the camera view, when i tried the latest ver of rajawali, it didn't work. I spent many days and find out that the latest did not support draw over camera any more. And the rajawali v0.9 work fine. So the following question is apply for v0.9.
When i tried to register SensorEventListener, and in onSensorChanged() I got 3 values which represent 3 dimension of android device, but it was very noise and unstable. I have tried implement low-pass filter but it still noise.
Finally i found this question, but on v0.9, the
getCamera().setOrientation(quaternion)
did not work. I dont know why.
Now i don't know what to do next :(
Here is my codes, it works very well in my project, and I hope it to help you.
// The code snippet of my renderer class
#Override
public void onRender(final long elapsedTime, final double deltaTime) {
mHeadTracker.getLastHeadView(mHeadTransform.getHeadView(), 0);
android.opengl.Matrix.invertM(mHeadTransform.getHeadView(), 0, mHeadTransform.getHeadView(), 0);
Quaternion q = mHeadTransform.getQuaternion(mHeadTransform.getHeadView(), 0);
getCurrentCamera().setOrientation(q);
super.onRender(elapsedTime, deltaTime);
}
// The code snippet of HeadTransform class
private static Quaternion sQuaternion = new Quaternion();
public Quaternion getQuaternion(float[] quaternion, int offset) {
if (offset + 4 > quaternion.length) {
throw new IllegalArgumentException(
"Not enough space to write the result");
}
float[] m = this.mHeadView;
float t = m[0] + m[5] + m[10];
float x;
float y;
float z;
float w;
float s;
if (t >= 0.0F) {
s = (float) Math.sqrt(t + 1.0F);
w = 0.5F * s;
s = 0.5F / s;
x = (m[9] - m[6]) * s;
y = (m[2] - m[8]) * s;
z = (m[4] - m[1]) * s;
} else {
if ((m[0] > m[5]) && (m[0] > m[10])) {
s = (float) Math.sqrt(1.0F + m[0] - m[5] - m[10]);
x = s * 0.5F;
s = 0.5F / s;
y = (m[4] + m[1]) * s;
z = (m[2] + m[8]) * s;
w = (m[9] - m[6]) * s;
} else {
if (m[5] > m[10]) {
s = (float) Math.sqrt(1.0F + m[5] - m[0] - m[10]);
y = s * 0.5F;
s = 0.5F / s;
x = (m[4] + m[1]) * s;
z = (m[9] + m[6]) * s;
w = (m[2] - m[8]) * s;
} else {
s = (float) Math.sqrt(1.0F + m[10] - m[0] - m[5]);
z = s * 0.5F;
s = 0.5F / s;
x = (m[2] + m[8]) * s;
y = (m[9] + m[6]) * s;
w = (m[4] - m[1]) * s;
}
}
}
quaternion[(offset + 0)] = x;
quaternion[(offset + 1)] = y;
quaternion[(offset + 2)] = z;
quaternion[(offset + 3)] = w;
Log.d("facevr", x + "," + y + "," + z + "," + w);
return sQuaternion.setAll(w, x, y, z);
}
I'm trying to animate some drawables in Android, I've set a path using PathEvaluator that animates along some curves along a full path.
When I set a duration (e.g. 6 seconds) it splits the duration to the number of curves I've set regardless of their length which causes the animation to be to slow on some segments and too fast on others.
On iOS this can be fixed using
animation.calculationMode = kCAAnimationCubicPaced;
animation.timingFunction = ...;
Which lets iOS to smooth your entire path into mid-points and span the duration according to each segment length.
Is there any way to get the same result in Android?
(besides breaking the path into discrete segments and assigning each segment its own duration manually which is really ugly and unmaintainable).
I don't think that anything can be done with ObjectAnimator because there seems to be no function that can be called to assert the relative duration of a certain fragment of the animation.
I did develop something similar to what you need a while back, but it works slightly differently - it inherits from Animation.
I've modified everything to work with your curving needs, and with the PathPoint class.
Here's an overview:
I supply the list of points to the animation in the constructor.
I calculate the length between all the points using a simple distance calculator. I then sum it all up to get the overall length of the path, and store the segment lengths in a map for future use (this is to improve efficiency during runtime).
When animating, I use the current interpolation time to figure out which 2 points I'm animating between, considering the ratio of time & the ratio of distance traveled.
I calculate the time it should take to animate between these 2 points according to the relative distance between them, compared to the overall distance.
I then interpolate separately between these 2 points using the calculation in the PathAnimator class.
Here's the code:
CurveAnimation.java:
public class CurveAnimation extends Animation
{
private static final float BEZIER_LENGTH_ACCURACY = 0.001f; // Must be divisible by one. Make smaller to improve accuracy, but will increase runtime at start of animation.
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>(); // map between the end point and the length of the path to it.
public CurveAnimation(List<PathPoint> pathPoints)
{
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2)
{
Log.e("CurveAnimation", "There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
PathPoint[] startEndPart = getStartEndForTime(interpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (interpolatedTime - startTime) / (endTime - startTime);
float x, y;
float[] xy;
if (endPoint.mOperation == PathPoint.CURVE)
{
xy = getBezierXY(startPoint, endPoint, progress);
x = xy[0];
y = xy[1];
}
else if (endPoint.mOperation == PathPoint.LINE)
{
x = startPoint.mX + progress * (endPoint.mX - startPoint.mX);
y = startPoint.mY + progress * (endPoint.mY - startPoint.mY);
}
else
{
x = endPoint.mX;
y = endPoint.mY;
}
t.getMatrix().setTranslate(x, y);
super.applyTransformation(interpolatedTime, t);
}
private PathPoint[] getStartEndForTime(float time)
{
double length = 0;
if (time == 1)
{
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2), mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time)
{
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartTimeOfPoint(PathPoint point)
{
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1)
{
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength()
{
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index)
{
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end)
{
if (mSegmentLengths.containsKey(end))
{
return mSegmentLengths.get(end);
}
else if (end.mOperation == PathPoint.LINE)
{
return calculateLength(start.mX, end.mX, start.mY, end.mY);
}
else if (end.mOperation == PathPoint.CURVE)
{
return calculateBezeirLength(start, end);
}
else
{
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1)
{
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end)
{
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY)
{
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress)
{
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX +
3 * oneMinusT * oneMinusT * progress * end.mControl0X +
3 * oneMinusT * progress * progress * end.mControl1X +
progress * progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY +
3 * oneMinusT * oneMinusT * progress * end.mControl0Y +
3 * oneMinusT * progress * progress * end.mControl1Y +
progress * progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
Here's a sample that shows how to activate the animation:
private void animate()
{
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0);
path.lineTo(0, 300);
path.curveTo(100, 0, 300, 900, 400, 500);
CurveAnimation animation = new CurveAnimation(path.mPoints);
animation.setDuration(5000);
animation.setInterpolator(new LinearInterpolator());
btn.startAnimation(animation);
}
Now, keep in mind that I'm currently calculating the length of the curve according to an approximation. This will obviously cause some mild inaccuracies in the speed. If you feel it's not accurate enough, feel free to modify the code. Also, if you want to increase the length accuracy of the curve, try decreasing the value of BEZIER_LENGTH_ACCURACY. It must be dividable by 1, so accepted values can be 0.001, 0.000025, etc.
While you might notice some mild fluctuations in speed when using curves, I'm sure it's much better than simply dividing the time equally between all paths.
I hope this helps :)
I tried using Gil's answer, but it didn't fit how I was animating.
Gil wrote an Animation class which is used to animate Views.
I was using ObjectAnimator.ofObject() to animate custom classes using ValueProperties which can't be used with custom Animation.
So this is what I did:
I extend PathEvaluator and override its evaluate method.
I use Gil's logic to calculate path total length, and segmented lengths
Since PathEvaluator.evaluate is called for each PathPoint with t values
0..1, I needed to normalize the interpolated time given to me, so it'll be incremental and won't zero out for each segment.
I ignore the start/end PathPoints given to me so the current position can be
before start or after end along the path depending on the segment's duration.
I pass the current progress calculated to my super
(PathEvaluator) to calc the actual position.
This is the code:
public class NormalizedEvaluator extends PathEvaluator {
private static final float BEZIER_LENGTH_ACCURACY = 0.001f;
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>();
public NormalizedEvaluator(List<PathPoint> pathPoints) {
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2) {
Log.e("CurveAnimation",
"There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
public PathPoint evaluate(float interpolatedTime, PathPoint ignoredStartPoint,
PathPoint ignoredEndPoint) {
float index = getStartIndexOfPoint(ignoredStartPoint);
float normalizedInterpolatedTime = (interpolatedTime + index) / (mPathPoints.size() - 1);
PathPoint[] startEndPart = getStartEndForTime(normalizedInterpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (normalizedInterpolatedTime - startTime) / (endTime - startTime);
return super.evaluate(progress, startPoint, endPoint);
}
private PathPoint[] getStartEndForTime(float time) {
double length = 0;
if (time == 1) {
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2),
mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++) {
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time) {
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartIndexOfPoint(PathPoint point) {
for (int ii = 0; ii < mPathPoints.size(); ii++) {
PathPoint current = mPathPoints.get(ii);
if (current == point) {
return ii;
}
}
return -1;
}
private float getStartTimeOfPoint(PathPoint point) {
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1) {
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength() {
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++) {
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index) {
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end) {
if (mSegmentLengths.containsKey(end)) {
return mSegmentLengths.get(end);
} else if (end.mOperation == PathPoint.LINE) {
return calculateLength(start.mX, end.mX, start.mY, end.mY);
} else if (end.mOperation == PathPoint.CURVE) {
return calculateBezeirLength(start, end);
} else {
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1) {
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end) {
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY) {
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress) {
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX + 3 * oneMinusT * oneMinusT * progress
* end.mControl0X + 3 * oneMinusT * progress * progress * end.mControl1X + progress
* progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY + 3 * oneMinusT * oneMinusT * progress
* end.mControl0Y + 3 * oneMinusT * progress * progress * end.mControl1Y + progress
* progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
This is the usage:
NormalizedEvaluator evaluator = new NormalizedEvaluator((List<PathPoint>) path.getPoints());
ObjectAnimator anim = ObjectAnimator.ofObject(object, "position", evaluator, path.getPoints().toArray());
UPDATE: I just realized that I might have reinvented the wheel, please look at Specifying Keyframes.
It is shocking to see that nothing is available of this kind. Anyways if you don't want to calculate path length at run time then I was able to add functionality of assigning weights to paths. Idea is to assign a weight to your path and run the animation if it feels OK then well and good otherwise just decrease or increase weight assigned to each Path.
Following code is modified code from official Android sample that you pointed in your question:
// Set up the path we're animating along
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0).setWeight(0);
path.lineTo(0, 300).setWeight(30);// assign arbitrary weight
path.curveTo(100, 0, 300, 900, 400, 500).setWeight(70);// assign arbitrary weight
final PathPoint[] points = path.getPoints().toArray(new PathPoint[] {});
mFirstKeyframe = points[0];
final int numFrames = points.length;
final PathEvaluator pathEvaluator = new PathEvaluator();
final ValueAnimator anim = ValueAnimator.ofInt(0, 1);// dummy values
anim.setDuration(1000);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
// Special-case optimization for the common case of only two
// keyframes
if (numFrames == 2) {
PathPoint nextPoint = pathEvaluator.evaluate(fraction,
points[0], points[1]);
setButtonLoc(nextPoint);
} else {
PathPoint prevKeyframe = mFirstKeyframe;
for (int i = 1; i < numFrames; ++i) {
PathPoint nextKeyframe = points[i];
if (fraction < nextKeyframe.getFraction()) {
final float prevFraction = prevKeyframe
.getFraction();
float intervalFraction = (fraction - prevFraction)
/ (nextKeyframe.getFraction() - prevFraction);
PathPoint nextPoint = pathEvaluator.evaluate(
intervalFraction, prevKeyframe,
nextKeyframe);
setButtonLoc(nextPoint);
break;
}
prevKeyframe = nextKeyframe;
}
}
}
});
And that's it !!!.
Of course I modified other classes as well but nothing big was added. E.g. in PathPoint I added this:
float mWeight;
float mFraction;
public void setWeight(float weight) {
mWeight = weight;
}
public float getWeight() {
return mWeight;
}
public void setFraction(float fraction) {
mFraction = fraction;
}
public float getFraction() {
return mFraction;
}
In AnimatorPath I modified getPoints() method like this:
public Collection<PathPoint> getPoints() {
// calculate fractions
float totalWeight = 0.0F;
for (PathPoint p : mPoints) {
totalWeight += p.getWeight();
}
float lastWeight = 0F;
for (PathPoint p : mPoints) {
p.setFraction(lastWeight = lastWeight + p.getWeight() / totalWeight);
}
return mPoints;
}
And thats pretty much it. Oh and for better readability I added Builder Pattern in AnimatorPath, so all 3 methods were changed like this:
public PathPoint moveTo(float x, float y) {// same for lineTo and curveTo method
PathPoint p = PathPoint.moveTo(x, y);
mPoints.add(p);
return p;
}
NOTE: To handle Interpolators that can give fraction less then 0 or greater than 1 (e.g. AnticipateOvershootInterpolator) look at com.nineoldandroids.animation.KeyframeSet.getValue(float fraction) method and implement the logic in onAnimationUpdate(ValueAnimator animation).
I have question to mesh drawed by triangle_strip and line_strip with AndEngine.
I'm generating 2d terrain using triangle_strip , triangles are created this way
1 3
|\ |\
| \| \
0 2 4
final int pSegmentsCount = 51;
final int pVertexCount = ( Mesh.VERTEX_SIZE * (pSegmentsCount) );
final float pColor = new Color(0f,0f,0f).getABGRPackedFloat();
final float pSegmentWidth = ( CAMERA_WIDTH/pSegmentsCount );
float[] pBufferData = new float[pVertexCount];
mHeightOffsetCurrent = new float[pVertexCount];
float x = 0f,y = 0f;
for (int i = 0;i<(pSegmentsCount);i++){
if (i%2!=0) { //every 3rd point goes up 2-4-6 , 0-1-3-5 stays down at 0 , x increments every third point
y = 200;
} else {
x = pSegmentWidth*i; //step right by segment
y = 0;
}
mHeightOffsetCurrent[i] = 0; //init default offset
pBufferData[(i * Mesh.VERTEX_SIZE) + Mesh.VERTEX_INDEX_X] = x;
pBufferData[(i * Mesh.VERTEX_SIZE) + Mesh.VERTEX_INDEX_Y] = y;
pBufferData[(i * Mesh.VERTEX_SIZE) + Mesh.COLOR_INDEX] = pColor;
}
pBufferData[((pSegmentsCount-1) * Mesh.VERTEX_SIZE) + Mesh.VERTEX_INDEX_Y] = -300;
final VertexBufferObjectManager VBOM = getVertexBufferObjectManager();
final HighPerformanceMeshVertexBufferObject pMeshVBO = new HighPerformanceMeshVertexBufferObject(VBOM, pBufferData, pVertexCount, DrawType.DYNAMIC, true, Mesh.VERTEXBUFFEROBJECTATTRIBUTES_DEFAULT);
pMesh = new Mesh(0, 0,pVertexCount,DrawMode.LINE_STRIP,pMeshVBO);
i'm changing last vertex y position to better illustrate it
here is what i get ,
line_strip
triangle_strip
when i set Y of the last point to 0 i still see line with gradient to starting point.
What i'm doing wrong ? Why the last element is connected with the first one and how to fix this?
Arrays are drawed by glDrawArrays.
public static final int VERTEX_INDEX_X = 0;
public static final int VERTEX_INDEX_Y = Mesh.VERTEX_INDEX_X + 1;
public static final int COLOR_INDEX = Mesh.VERTEX_INDEX_Y + 1;
public static final int VERTEX_SIZE = 2 + 1;
I'm new in Android development. I'm looking for any method that applies pitch shifting to output sound (in real-time). But I couldn't find any point to start.
I've found this topic but I still don't know how can I apply this.
Any suggestions?
In general, the algorithm is called a phase vocoder -- searching for that on the Internets should get you started.
There are a few open source phase vocoders out there, you should be able to use those for reference too.
You can do phase vocoder in real-time -- the main component used is the FFT, so you'll need a fast FFT. The Android libraries can do this for you, see this documentation: http://developer.android.com/reference/android/media/audiofx/Visualizer.html
As it happens, I'm about to release an open source FFT for ARM that is faster than Apple's vDSP library (which was hitherto the fastest). I'll post back in a few days when I've uploaded it to github.com.
Good luck.
There is no built-in pitch shifting algorithm in the Android SDK. You have to code your own. Pitch shifting is a real hardcore DSP algorithm; good sounding algorithms are results of many months or rather years of development...
I personally do not know any Java implementation so I suggest you to adopt some of the free C++ PS algorithms, the best one - which I use in my audio applications, is SoundTouch:
http://www.surina.net/soundtouch/
I played with its code a little and it seems it would not be too much complicated to rewrite it in Java.
HOME URL: http://www.dspdimension.com
public class AudioPitch{
//region Private Static Memebers
private static int MAX_FRAME_LENGTH = 8192;
private static double M_PI = 3.14159265358979323846;
private static float[] gInFIFO = new float[MAX_FRAME_LENGTH];
private static float[] gOutFIFO = new float[MAX_FRAME_LENGTH];
private static float[] gFFTworksp = new float[2 * MAX_FRAME_LENGTH];
private static float[] gLastPhase = new float[MAX_FRAME_LENGTH / 2 + 1];
private static float[] gSumPhase = new float[MAX_FRAME_LENGTH / 2 + 1];
private static float[] gOutputAccum = new float[2 * MAX_FRAME_LENGTH];
private static float[] gAnaFreq = new float[MAX_FRAME_LENGTH];
private static float[] gAnaMagn = new float[MAX_FRAME_LENGTH];
private static float[] gSynFreq = new float[MAX_FRAME_LENGTH];
private static float[] gSynMagn = new float[MAX_FRAME_LENGTH];
private static long gRover;
//endregion
public static void PitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize/*(long)2048*/, long osamp/*(long)10*/, float sampleRate, float[] indata)
{
double magn, phase, tmp, window, real, imag;
double freqPerBin, expct;
long i, k, qpd, index, inFifoLatency, stepSize, fftFrameSize2;
float[] outdata = indata;
/* set up some handy variables */
fftFrameSize2 = fftFrameSize / 2;
stepSize = fftFrameSize / osamp;
freqPerBin = sampleRate / (double)fftFrameSize;
expct = 2.0 * M_PI * (double)stepSize / (double)fftFrameSize;
inFifoLatency = fftFrameSize - stepSize;
if (gRover == 0) gRover = inFifoLatency;
/* main processing loop */
for (i = 0; i < numSampsToProcess; i++)
{
/* As long as we have not yet collected enough data just read in */
gInFIFO[(int) gRover] = indata[(int) i];
outdata[(int) i] = gOutFIFO[(int) (gRover - inFifoLatency)];
gRover++;
/* now we have enough data for processing */
if (gRover >= fftFrameSize)
{
gRover = inFifoLatency;
/* do windowing and re,im interleave */
for (k = 0; k < fftFrameSize; k++)
{
window = -.5 * Math.cos(2.0 * M_PI * (double)k / (double)fftFrameSize) + .5;
gFFTworksp[(int) (2 * k)] = (float)(gInFIFO[(int) k] * window);
gFFTworksp[(int) (2 * k + 1)] = 0.0F;
}
/* ***************** ANALYSIS ******************* */
/* do transform */
ShortTimeFourierTransform(gFFTworksp, fftFrameSize, -1);
/* this is the analysis step */
for (k = 0; k <= fftFrameSize2; k++)
{
/* de-interlace FFT buffer */
real = gFFTworksp[(int) (2 * k)];
imag = gFFTworksp[(int) (2 * k + 1)];
/* compute magnitude and phase */
magn = 2.0 * Math.sqrt(real * real + imag * imag);
phase = smbAtan2(imag, real);
/* compute phase difference */
tmp = phase - gLastPhase[(int) k];
gLastPhase[(int) k] = (float)phase;
/* subtract expected phase difference */
tmp -= (double)k * expct;
/* map delta phase into +/- Pi interval */
qpd = (long)(tmp / M_PI);
if (qpd >= 0) qpd += qpd & 1;
else qpd -= qpd & 1;
tmp -= M_PI * (double)qpd;
/* get deviation from bin frequency from the +/- Pi interval */
tmp = osamp * tmp / (2.0 * M_PI);
/* compute the k-th partials' true frequency */
tmp = (double)k * freqPerBin + tmp * freqPerBin;
/* store magnitude and true frequency in analysis arrays */
gAnaMagn[(int) k] = (float)magn;
gAnaFreq[(int) k] = (float)tmp;
}
/* ***************** PROCESSING ******************* */
/* this does the actual pitch shifting */
for (int zero = 0; zero < fftFrameSize; zero++)
{
gSynMagn[zero] = 0;
gSynFreq[zero] = 0;
}
for (k = 0; k <= fftFrameSize2; k++)
{
index = (long)(k * pitchShift);
if (index <= fftFrameSize2)
{
gSynMagn[(int) index] += gAnaMagn[(int) k];
gSynFreq[(int) index] = gAnaFreq[(int) k] * pitchShift;
}
}
/* ***************** SYNTHESIS ******************* */
/* this is the synthesis step */
for (k = 0; k <= fftFrameSize2; k++)
{
/* get magnitude and true frequency from synthesis arrays */
magn = gSynMagn[(int) k];
tmp = gSynFreq[(int) k];
/* subtract bin mid frequency */
tmp -= (double)k * freqPerBin;
/* get bin deviation from freq deviation */
tmp /= freqPerBin;
/* take osamp into account */
tmp = 2.0 * M_PI * tmp / osamp;
/* add the overlap phase advance back in */
tmp += (double)k * expct;
/* accumulate delta phase to get bin phase */
gSumPhase[(int) k] += (float)tmp;
phase = gSumPhase[(int) k];
/* get real and imag part and re-interleave */
gFFTworksp[(int) (2 * k)] = (float)(magn * Math.cos(phase));
gFFTworksp[(int) (2 * k + 1)] = (float)(magn * Math.sin(phase));
}
/* zero negative frequencies */
for (k = fftFrameSize + 2; k < 2 * fftFrameSize; k++) gFFTworksp[(int) k] = 0.0F;
/* do inverse transform */
ShortTimeFourierTransform(gFFTworksp, fftFrameSize, 1);
/* do windowing and add to output accumulator */
for (k = 0; k < fftFrameSize; k++)
{
window = -.5 * Math.cos(2.0 * M_PI * (double)k / (double)fftFrameSize) + .5;
gOutputAccum[(int) k] += (float)(2.0 * window * gFFTworksp[(int) (2 * k)] / (fftFrameSize2 * osamp));
}
for (k = 0; k < stepSize; k++) gOutFIFO[(int) k] = gOutputAccum[(int) k];
/* shift accumulator */
//memmove(gOutputAccum, gOutputAccum + stepSize, fftFrameSize * sizeof(float));
for (k = 0; k < fftFrameSize; k++)
{
gOutputAccum[(int) k] = gOutputAccum[(int) (k + stepSize)];
}
/* move input FIFO */
for (k = 0; k < inFifoLatency; k++) gInFIFO[(int) k] = gInFIFO[(int) (k + stepSize)];
}
}
}
//endregion
//region Private Static Methods
public static void ShortTimeFourierTransform(float[] fftBuffer, long fftFrameSize, long sign)
{
float wr, wi, arg, temp;
float tr, ti, ur, ui;
long i, bitm, j, le, le2, k;
for (i = 2; i < 2 * fftFrameSize - 2; i += 2)
{
for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1)
{
if ((i & bitm) != 0) j++;
j <<= 1;
}
if (i < j)
{
temp = fftBuffer[(int) i];
fftBuffer[(int) i] = fftBuffer[(int) j];
fftBuffer[(int) j] = temp;
temp = fftBuffer[(int) (i + 1)];
fftBuffer[(int) (i + 1)] = fftBuffer[(int) (j + 1)];
fftBuffer[(int) (j + 1)] = temp;
}
}
long max = (long)(Math.log(fftFrameSize) / Math.log(2.0) + .5);
for (k = 0, le = 2; k < max; k++)
{
le <<= 1;
le2 = le >> 1;
ur = 1.0F;
ui = 0.0F;
arg = (float)M_PI / (le2 >> 1);
wr = (float)Math.cos(arg);
wi = (float)(sign * Math.sin(arg));
for (j = 0; j < le2; j += 2)
{
for (i = j; i < 2 * fftFrameSize; i += le)
{
tr = fftBuffer[(int) (i + le2)] * ur - fftBuffer[(int) (i + le2 + 1)] * ui;
ti = fftBuffer[(int) (i + le2)] * ui + fftBuffer[(int) (i + le2 + 1)] * ur;
fftBuffer[(int) (i + le2)] = fftBuffer[(int) i] - tr;
fftBuffer[(int) (i + le2 + 1)] = fftBuffer[(int) (i + 1)] - ti;
fftBuffer[(int) i] += tr;
fftBuffer[(int) (i + 1)] += ti;
}
tr = ur * wr - ui * wi;
ui = ur * wi + ui * wr;
ur = tr;
}
}
}
//endregion
private static double smbAtan2(double x, double y)
{
double signx;
if (x > 0.) signx = 1.;
else signx = -1.;
if (x == 0.) return 0.;
if (y == 0.) return signx * M_PI / 2.;
return Math.atan2(x, y);
}
}
this code working too but very consumption cpu usage.
pitchShift between 0.5 -2.0
call this class as below:
int maxValueOFShort = 32768;
short [] buffer = new short[800];
float[] inData = new float[buffer.length];
while (audiorackIsRun)
{
int m = recorder.read(buffer, 0, buffer.length);
for(int n=0; n<buffer.length;n++)
inData[n] = buffer[n]/(float)maxValueOFShort;
AudioPitch.PitchShift(1, buffer.length, 4096, 4, 44100, inData);
for(int n=0; n<buffer.length;n++)
buffer[n] = (short)(inData[n]*maxValueOFShort);
player.write(buffer, 0, buffer.length);
}
I found this code to generate a sphere in Opengl es. I am unable to understand the logic, could someone please give me some insights on this.
private void generateData() {
slicesBuffers = new FloatBuffer[slices];
normalsBuffers = new FloatBuffer[slices];
texCoordsBuffers = new FloatBuffer[slices];
for (int i = 0; i < slices; i++) {
float[] vertexCoords = new float[7 * (stacks + 1)];
float[] normalCoords = new float[4* (stacks + 1)];
float[] textureCoords = new float[10 * (stacks + 1)];
double alpha0 = i * (2 * Math.PI) / slices;
double alpha1 = (i + 1) * (2 * Math.PI) / slices;
float cosAlpha0 = (float) Math.cos(alpha0);
float sinAlpha0 = (float) Math.sin(alpha0);
float cosAlpha1 = (float) Math.cos(alpha1);
float sinAlpha1 = (float) Math.sin(alpha1);
for (int j = 0; j <= stacks; j++) {
double beta = j * Math.PI / stacks - Math.PI / 2;
float cosBeta = (float) Math.cos(beta);
float sinBeta = (float) Math.sin(beta);
Utils.setXYZ(vertexCoords, 6 * j,
radius * cosBeta * cosAlpha1,
radius * sinBeta,
radius * cosBeta * sinAlpha1);
Utils.setXYZ(vertexCoords, 6 * j + 3,
radius * cosBeta * cosAlpha0,
radius * sinBeta,
radius * cosBeta * sinAlpha0);
Utils.setXYZ(normalCoords, 6 * j,
cosBeta * cosAlpha1,
sinBeta,
cosBeta * sinAlpha1);
Utils.setXYZ(normalCoords, 6 * j + 3,
cosBeta * cosAlpha0,
sinBeta,
cosBeta * sinAlpha0);
Utils.setXY(textureCoords, 4 * j,
((float) (i + 1)) / slices,
((float) j) / stacks);
Utils.setXY(textureCoords, 4 * j + 2,
((float) i) / slices,
((float) j) / stacks);
}
slicesBuffers[i] = FloatBuffer.wrap(vertexCoords);
normalsBuffers[i] = FloatBuffer.wrap(normalCoords);
texCoordsBuffers[i] = FloatBuffer.wrap(textureCoords);
}
}
Thankyou
For the theory of sphere generation see:
en.wikipedia.org/wiki/Sphere (Vertex)
en.wikipedia.org/wiki/UV_mapping (Texture coordinate)
http://groups.google.com/group/android-developers/browse_thread/thread/0030261b82ed71e5/338fc1dcbfe6945f?lnk=raot(Normal surface)
Your code is right, it have a little bit issues, I have make some corrections:
public Sphere(int slices,int stacks, float radius, float H,float K,float Z, Bitmap image,Bitmap first,Bitmap second){
FloatBuffer[] slicesBuffers = new FloatBuffer[slices];
FloatBuffer[] normalsBuffers = new FloatBuffer[slices];
FloatBuffer[] texCoordsBuffers = new FloatBuffer[slices];
float[] total_vertexBuff;
float[] total_normalsBuff;
float[] total_textCoordsBuff;
int vertex_counter = 0;
int normals_counter = 0;
int texCoords_counter = 0;
int position_dst;
float tmp[];
for (int i = 0; i < slices; i++) {
float[] vertexCoords = new float[ 2 * 3 * (stacks + 1)];
float[] normalCoords = new float[ 2 * 3 *(stacks + 1)];
float[] textureCoords = new float[ 4 * (stacks + 1) ];
double alpha0 = i * (2 * Math.PI) / slices;
double alpha1 = (i + 1) * (2 * Math.PI) / slices;
float cosAlpha0 = (float) Math.cos(alpha0);
float sinAlpha0 = (float) Math.sin(alpha0);
float cosAlpha1 = (float) Math.cos(alpha1);
float sinAlpha1 = (float) Math.sin(alpha1);
for (int j = 0; j <= stacks; j++) {
double beta = j * Math.PI / stacks - Math.PI / 2;
float cosBeta = (float) Math.cos(beta);
float sinBeta = (float) Math.sin(beta);
setXYZ(vertexCoords, 6 * j, radius * cosBeta * cosAlpha1, radius * sinBeta, radius * cosBeta * sinAlpha1 );
setXYZ(vertexCoords, 6 * j + 3,radius * cosBeta * cosAlpha0,radius * sinBeta,radius * cosBeta * sinAlpha0);
vertex_counter += 2;
Log.d(TAG, "j:"+j);
setXYZ(normalCoords, 6 * j,cosBeta * cosAlpha1,sinBeta,cosBeta * sinAlpha1);
setXYZ(normalCoords, 6 * j + 3,cosBeta * cosAlpha0,sinBeta,cosBeta * sinAlpha0);
normals_counter += 2;
setXY(textureCoords, 4 * j,((float) (i + 1)) / slices,((float) j) / stacks);
setXY(textureCoords, 4 * j + 2,((float) i) / slices,((float) j) / stacks);
texCoords_counter += 2;
}
slicesBuffers[i] = FloatBuffer.wrap(vertexCoords);
normalsBuffers[i] = FloatBuffer.wrap(normalCoords);
texCoordsBuffers[i] = FloatBuffer.wrap(textureCoords);
}
total_vertexBuff = new float[vertex_counter * 3];
total_normalsBuff = new float[normals_counter * 3];
total_textCoordsBuff = new float[texCoords_counter * 2];
position_dst = 0;
// ricopio vertici
for (int i = 0; i < slicesBuffers.length; i++) {
for(int j = 0; j < slicesBuffers[i].capacity();j++,position_dst++)
total_vertexBuff[position_dst] = slicesBuffers[i].get(j);
}
position_dst = 0;
// ricopio normali
for (int i = 0; i < normalsBuffers.length; i++) {
for(int j = 0; j < normalsBuffers[i].capacity();j++,position_dst++)
total_normalsBuff[position_dst] = normalsBuffers[i].get(j);
}
position_dst = 0;
// ricopio coordinate texture
for (int i = 0; i < texCoordsBuffers.length; i++) {
for(int j = 0; j < texCoordsBuffers[i].capacity();j++,position_dst++)
total_textCoordsBuff[position_dst] = texCoordsBuffers[i].get(j);
}
this.image = image;
this.half_first = first;
this.half_second = second;
this.vertexBuffer = FloatBuffer.wrap(total_vertexBuff);
this.normalsBuffer = FloatBuffer.wrap(total_normalsBuff);
this.texCoordsBuffer = FloatBuffer.wrap(total_textCoordsBuff);
Log.d(TAG, "vertex_counter:"+vertex_counter);
Log.d(TAG, "texCoords_counter:"+texCoords_counter);
Log.d(TAG, "vertexBuffer:"+this.vertexBuffer.capacity());
Log.d(TAG, "texCoordsBuffer:"+this.texCoordsBuffer.capacity());
this.textures_ids = IntBuffer.allocate(2);
this.totalVertexCount = vertex_counter;
this.setPlaneBuffer();
return;
}
I really hope I help you.
Bye
pedr0