how can i draw a growing line effect in mpandroidchart (Animation)? - java

how can i growing line android effect in MPAndroidChart, I want to continuously graph animation.
like this:
float phaseY = mAnimator.getPhaseY();
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
cubicPath.reset();
if (mXBounds.range >= 1) {
Entry prev = dataSet.getEntryForIndex(mXBounds.min);
Entry cur = prev;
// let the spline start
cubicPath.moveTo(cur.getX(), cur.getY() * phaseY);
for (int j = mXBounds.min + 1; j <= mXBounds.range + mXBounds.min; j++) {
prev = cur;
cur = dataSet.getEntryForIndex(j);
final float cpx = (prev.getX())
+ (cur.getX() - prev.getX()) / 2.0f;
cubicPath.cubicTo(
cpx, prev.getY() * phaseY,
cpx, cur.getY() * phaseY,
cur.getX(), cur.getY() * phaseY);
}
}
// if filled is enabled, close the path
if (dataSet.isDrawFilledEnabled()) {
cubicFillPath.reset();
cubicFillPath.addPath(cubicPath);
// create a new path, this is bad for performance
drawCubicFill(mBitmapCanvas, dataSet, cubicFillPath, trans, mXBounds);
}
mRenderPaint.setColor(dataSet.getColor());
mRenderPaint.setStyle(Paint.Style.STROKE);
trans.pathValueToPixel(cubicPath);
mBitmapCanvas.drawPath(cubicPath, mRenderPaint);
mRenderPaint.setPathEffect(null);

Take a look into Animations.
It might be enough to just use something like:
mChart.animateX(3000);
Furthermore you can choose one of many predefined animations called EasingOption to achieve a non-linear animation.

Try:
mLineChart.animateX(1750, Easing.EasingOption.Linear);

Related

Android chart with variable line stroke width

I am trying to create Android App with chart with variable line stroke width like this one in the link.
I have tried to realize it with MPAndroidChart. Override drawCubicBezier method in LineChartRenderer class and to set dynamically setStrokeWidth on Paint. But nothing happens. Probably my knowledge is not enough to do it.
Can you help me how to achieve a different line thickness or if there is another chart library that has such functionality?
This is how I set my new Renderer
chart2.setRenderer(new myLineChartRenderer(chart2, chart2.getAnimator(), chart2.getViewPortHandler()));
This is the class
public class myLineChartRenderer extends LineChartRenderer {
public myLineChartRenderer(LineChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
protected void drawCubicBezier(ILineDataSet dataSet) {
Path mPath = new Path();
ArrayList<Path> mPaths = new ArrayList<Path>();
mRenderPaint.setAntiAlias(true);
mRenderPaint.setDither(true);
mRenderPaint.setStyle(Paint.Style.STROKE);
mRenderPaint.setStrokeJoin(Paint.Join.ROUND);
mRenderPaint.setStrokeCap(Paint.Cap.ROUND);
//mRenderPaint.setStrokeWidth(STROKE_WIDTH);
float phaseY = mAnimator.getPhaseY();
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
float intensity = dataSet.getCubicIntensity();
cubicPath.reset();
if (mXBounds.range >= 1) {
float prevDx = 0f;
float prevDy = 0f;
float curDx = 0f;
float curDy = 0f;
// Take an extra point from the left, and an extra from the right.
// That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart.
// So in the starting `prev` and `cur`, go -2, -1
// And in the `lastIndex`, add +1
final int firstIndex = mXBounds.min + 1;
final int lastIndex = mXBounds.min + mXBounds.range;
Entry prevPrev;
Entry prev = dataSet.getEntryForIndex(Math.max(firstIndex - 2, 0));
Entry cur = dataSet.getEntryForIndex(Math.max(firstIndex - 1, 0));
Entry next = cur;
int nextIndex = -1;
if (cur == null) return;
// let the spline start
cubicPath.moveTo(cur.getX(), cur.getY() * phaseY);
mPath.moveTo(cur.getX(), cur.getY() * phaseY);
for (int j = mXBounds.min + 1; j <= mXBounds.range + mXBounds.min; j++) {
prevPrev = prev;
prev = cur;
cur = nextIndex == j ? next : dataSet.getEntryForIndex(j);
nextIndex = j + 1 < dataSet.getEntryCount() ? j + 1 : j;
next = dataSet.getEntryForIndex(nextIndex);
prevDx = (cur.getX() - prevPrev.getX()) * intensity;
prevDy = (cur.getY() - prevPrev.getY()) * intensity;
curDx = (next.getX() - prev.getX()) * intensity;
curDy = (next.getY() - prev.getY()) * intensity;
cubicPath.cubicTo(prev.getX() + prevDx, (prev.getY() + prevDy) * phaseY,
cur.getX() - curDx,
(cur.getY() - curDy) * phaseY, cur.getX(), cur.getY() * phaseY);
mPath.cubicTo(prev.getX() + prevDx, (prev.getY() + prevDy) * phaseY,
cur.getX() - curDx,
(cur.getY() - curDy) * phaseY, cur.getX(), cur.getY() * phaseY);
mPaths.add(mPath);
// Log.i("Curve", (prev.getX() + prevDx)+" | "+ ((prev.getY() + prevDy) * phaseY)+" | "+ (cur.getX() - curDx)+" | "+ ((cur.getY() - curDy) * phaseY)+" | "+ cur.getX()+" | "+ (cur.getY() * phaseY));
}
}
// if filled is enabled, close the path
if (dataSet.isDrawFilledEnabled()) {
cubicFillPath.reset();
cubicFillPath.addPath(cubicPath);
//drawCubicFill(mBitmapCanvas, dataSet, cubicFillPath, trans, mXBounds);
}
mRenderPaint.setColor(dataSet.getColor());
mRenderPaint.setStyle(Paint.Style.STROKE);
trans.pathValueToPixel(cubicPath);
//mBitmapCanvas.drawPath(cubicPath, mRenderPaint);
for(int i=0; i<mPaths.size();i++)
{
mRenderPaint.setStrokeWidth(i+30);
mBitmapCanvas.drawPath(mPaths.get(i), mRenderPaint);
}
mRenderPaint.setPathEffect(null);
}
}
I tried to divide the path into separate parts and change the thickness of the line while drawing them, but the result is totally wrong.

how to Smooth catmull rom spline curve when it is moving like amaging wire in libgdx

i have tried to move my curve but it is not moving well, when it changes its direction from left to right or right to left then the movement is quite awkward.
i want to move my curve like this video
video of curve movement what i actually want.
In this video when a it change its direction it is so graceful but in my case it change its direction and the curve gives a crazy shape at newly added point.
Experts please solve this problem.
here is my code
//create paths
private Bezier<Vector2> path1;
private CatmullRomSpline<Vector2> path2;
private ShapeRenderer sr;
int height,width;
Vector2 starting,ending,endingControl;
ArrayList<Vector2> listOfPoints;
Vector3 touchPos;
float timeDifference;
Boolean leftPos=false,rightPos=false;
Boolean isTouch=false,isTouchUp=false;
Vector2 mVector2;
private OrthographicCamera cam;
Vector2[] controlPoints;
#Override
public void create () {
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
ending=new Vector2(width/2,height/2);
endingControl=new Vector2(ending.x,ending.y+10);
starting=new Vector2(width/2,0);
controlPoints = new Vector2[]{starting,starting,ending,ending};
// set up the curves
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
listOfPoints=new ArrayList<Vector2>();
// setup ShapeRenderer
sr = new ShapeRenderer();
sr.setAutoShapeType(true);
sr.setColor(Color.BLACK);
cam=new OrthographicCamera();
cam.setToOrtho(false);
listOfPoints.add(new Vector2(width/2,0)); //starting
listOfPoints.add(new Vector2(width/2,0)); //starting
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
super.resize(width, height);
cam.update();
}
#Override
public void render () {
cam.update();
Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
sr.begin();
sr.set(ShapeType.Filled);
if(Gdx.input.isTouched())
{
if(!isTouch){
listOfPoints.add(new Vector2(ending.x+2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
//endingControl.x=ending.y;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=true;
ending.x+=3;
}
else {
if(isTouch){
listOfPoints.add(new Vector2(ending.x-2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=false;
ending.x-=3;
}
moveAndReduce();
for(int i = 0; i < 100; ++i){
float t = i /100f;
Vector2 st = new Vector2();
Vector2 end = new Vector2();
path2.valueAt(st,t);
path2.valueAt(end, t-0.01f);
sr.rectLine(st.x, st.y, end.x, end.y,3);
}
sr.end();
}
#Override
public void dispose () {
sr.dispose();
}
public void moveAndReduce()
{
for(Vector2 vector2:listOfPoints)
{
vector2.y-=3 ;
}
if(listOfPoints.size()>3 && listOfPoints.get(3).y<-1)
{
listOfPoints.remove(0);
listOfPoints.set(0, listOfPoints.get(1));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
}
Going by the video the curve does not look like it is constrained by control points but just a simple trace of and accelerating point.
You create an array of floats the length in pixels matching the length of the line in the x direction. For example if screen is 200 pixels wide the line can be 100 so the array is 100 in length. Set each float in the array to the start value half the screen height. I call the array line in this answer. You call it what you like.
The you assign a head index that is the index of the rightmost point. Each frame you move the head index up by one. If it is over the array length-1 you set it to zero (beginning of array)
Rendering the Path
When you draw the line you draw all the points from head + 1
Path p = new Path();
for(int i = 0; i < 100; ++i){
p.lineTo(i, line[(i + head + 1) % 100]); // add path points
}
// draw the path;
Moving up and down
To make it move you have a movement float move that is 0 for no movement or positive and negative values the move up or down.
When you want it to move increase the move amount by a fixed value.
// moving down
if(move < maxMove){ // set a max move amount eg 10
move += moveAmount; // moveAmount 0.2 just as an example
}
Same for moving up, but subtract
When there is no input you move the move amount back to zero by a fixed rate
// assume this is code run when no input
if(move != 0){
if(Math.abs(move) < moveAmount){ // if close to zero set to zero
move = 0;
}else{
move -= Math.sign(move) * moveAmount; // else move towards zero at
// fixed rate
}
}
Moving forward
The line does not move forward, just appears to do so as we move the head position up the array each frame.
Back to moving the line's head the following move the line head position up or down (but is not complete the last line is modified to create a smoother curve)
float pos = line[head]; // get the pos of line at head
head += 1; // move the head forward 1
head %= 100; // if past end of array move to 0
line[head] = pos + move; // set the new head position
A better curve
This will move the head of the line up or down depending on move. The curve we get is not that nice so to make it a little smoother you need to change the rate the move value changes the head position.
// an sCurve for any value of move the result is from -1 to 1
// the greater or smaller move the closer to 1 or -1 the value gets
// the value -1.2 controls the rate at which the value moves to 1 or -1
// the closer to -1 the value is the slower the value moves to 1 or -1
float res = (2 / (1 + Math.pow(move,-1.2))) -1;
This in effect changes the shape of the lines curve to a almost sine wave when moving up and down
// so instead of
//line[head] = pos + move; // set the new head position
line[head] = pos + ( (2 / (1 + Math.pow(move,-1.2))) -1 ) * maxSpeed;
// max speed is the max speed the line head can move up or down
// per frame in pixels.
Example to show the curve
Below is a Javascript implementation that does it as outlined above (is not intended as answer code). Use the keyboard Arrow Up and Arrow down to move the line
If you are using a tablet or phone then the following image is what you will see as way to late for me to add and test touch for the example
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } };
const keys = {
ArrowUp : false,
ArrowDown : false,
};
function keyEvents(e){
if(keys[e.code] !== undefined){
keys[e.code] = event.type === "keydown";
e.preventDefault();
}
}
addEventListener("keyup", keyEvents);
addEventListener("keydown", keyEvents);
focus();
var gameOver = 0;
var gameOverWait = 100;
var score = 0;
var nextWallIn = 500
var nextWallCount = nextWallIn;
var wallHole = 50;
const wallWidth = 5;
const walls = [];
function addWall(){
var y = (Math.random() * (H - wallHole * 2)) + wallHole *0.5;
walls.push({
x : W,
top : y,
bottom : y + wallHole,
point : 1, // score point
});
}
function updateWalls(){
nextWallCount += 1;
if(nextWallCount >= nextWallIn){
addWall();
nextWallCount = 0;
nextWallIn -= 1;
wallHole -= 1;
}
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
w.x -= 1;
if(w.x < -wallWidth){
walls.splice(i--,1);
}
if(w.x >= line.length- + wallWidth && w.x < line.length){
var pos = line[head];
if(pos < w.top || pos > w.bottom){
gameOver = gameOverWait;
}
}
if(w.point > 0 && w.x <= line.length){
score += w.point;
w.point = 0;
}
}
}
function drawWalls(){
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
ctx.fillStyle = "red";
ctx.fillRect(w.x,0,wallWidth,w.top);
ctx.fillRect(w.x,w.bottom,wallWidth,H-w.bottom);
}
}
const sCurve = (x,p) => (2 / (1 + Math.pow(p,-x))) -1;
const ctx = canvas.getContext("2d");
var W,H; // canvas width and height
const line = [];
var move = 0;
var curvePower = 1.2;
var curveSpeed = 0.2;
var maxSpeed = 10;
var headMoveMultiply = 2;
var head;
function init(){
line.length = 0;
doFor(W / 2,i => line[i] = H / 2);
head = line.length - 1;
move = 0;
walls.length = 0;
score = 0;
nextWallIn = 500
nextWallCount = nextWallIn;
wallHole = 50;
ctx.font = "30px arial black";
}
function stepLine(){
var pos = line[head];
head += 1;
head %= line.length;
line[head] = pos + sCurve(move,curvePower)*headMoveMultiply ;
}
function drawLine(){
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
for(var i = 0; i <line.length; i++){
ctx.lineTo(i,line[(i + head + 1) % line.length]);
}
ctx.stroke();
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
init();
}
if(gameOver === 1){
gameOver = 0;
init();
}
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,W,H);
if(keys.ArrowUp){
if(move > - maxSpeed){
move -= curveSpeed;
}
}else if(keys.ArrowDown){
if(move < maxSpeed){
move += curveSpeed;
}
}else{
move -= Math.sign(move)*curveSpeed;
if(Math.abs(move) < curveSpeed){
move = 0;
}
}
if(gameOver === 0){
stepLine();
updateWalls();
}
drawLine();
drawWalls();
ctx.fillStyle = "Black";
ctx.textAlign = "left";
ctx.fillText("Score : " + score, 10,30);
if(gameOver > 0){
ctx.textAlign = "center";
ctx.fillText("Crashed !!", W / 2,H * 0.4);
gameOver -= 1;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index : -10;
}
<br><br><br>Up down arrow keys to move line.
<canvas id=canvas></canvas>

ANDROID: Load view for activity asynchronously

I have a custom view class that I want to set for my activity. However it's initiation is very long (Most of the code, such as loading bitmaps is placed in constructor). Therefore, I want to show the user a preloader before setting the View.
I've tried setting the loading screen first and launching the view on a different thread using asynctask and handlers, though without any luck. The first gives me can't create handler inside thread that has not called looper.prepare() and the other one just executes code line by line.
Maybe I'm doing something wrong, any ideas how to divide the loading process, e.g.:
1. set loading screen.
2. load view class on the background (instantiate).
3. set the view class in place of the loading screen.
Thanks
UPDATE
public MainMenuView(Context context) {
super(context);
System.gc();
this.context = context;
sh = new SharedHelper(context);
am = context.getAssets();
sp = new SoundHelper(context,
SoundHelper.CLICK,
SoundHelper.MAIN_MELODY
);
screenWH = Helper.getScreenDimensions(context);
bg = new Background(screenWH, am);
scaleAmount = bg.scaleAmount();
scaleAmount = scaleAmount * 0.9f;
strings = Helper.getStrings((int)sh.getOption(Option.LANGUAGE), new DBHelper(context));
mainTitle = Helper.scaleBMP(Helper.assetImage(am, "Main/title.png"), scaleAmount);
mainTitleM.postTranslate(bg.getMargins()[0], -10);
/**
* Load cloud-like buttons
* */
float top = 0.03f * screenWH[1];
float left = bg.getMargins()[0] + mainTitle.getWidth();
MainButton repeat = new MainButton("Main/Buttons/repeat_on.png", "Main/Buttons/repeat.png", am, scaleAmount);
repeat.setPosition(left, top);
repeat.setType(Helper.REPEAT);
btns.add(repeat);
left += 0.3f * repeat.getBMP().getWidth();
top += repeat.getBMP().getHeight();
MainButton test = new MainButton("Main/Buttons/test_on.png", "Main/Buttons/test.png", am, scaleAmount);
test.setPosition(left, top);
test.setType(Helper.TEST);
btns.add(test);
left -= 0.7f * test.getBMP().getWidth();
top += 0.5f * test.getBMP().getHeight();
MainButton learn = new MainButton("Main/Buttons/learn_on.png", "Main/Buttons/learn.png", am, scaleAmount);
learn.setPosition(left, top);
learn.setType(Helper.LEARN);
btns.add(learn);
/**
* Main control buttons
* */
left = 5;
top = bg.getTopMargin();
buttonClass parent = new buttonClass("Buttons/parents control.png", am, scaleAmount, Helper.PARENTS);
parent.setPosition(left, top);
btns.add(parent);
top += parent.getBMP().getHeight() + 2;
buttonClass facebook = new buttonClass("Buttons/facebook.png", am, scaleAmount, Helper.FACEBOOK);
facebook.setPosition(left, top);
btns.add(facebook);
top += facebook.getBMP().getHeight() + 2;
buttonClass share = new buttonClass("Buttons/share.png", am, scaleAmount, Helper.SHARE);
share.setPosition(left, top);
btns.add(share);
top += share.getBMP().getHeight() + 2;
buttonClass pay = new buttonClass("Buttons/pay.png", am, scaleAmount, Helper.PAY);
pay.setPosition(left, top);
btns.add(pay);
/**
* Sprites
* */
float widthsix = 0;
float widthasix = 0;
float highest = 0;
for(int i=1; i<=11; i++){
mainSprite spr = new mainSprite(i, context, scaleAmount);
if(i <= 6){
widthsix += spr.getBMP().getWidth();
}
else{
widthasix += spr.getBMP().getWidth();
}
if(i == 9){
highest = spr.getBMP().getHeight();
}
sprites.add(spr);
}
float[] margins = bg.getMargins();
left = margins[0] + (margins[2] - margins[0] - widthsix) / 2;
float leftA = margins[0] + (margins[2] - margins[0] - widthasix) / 2;
top = screenWH[1] - highest;
float angle = 0;
float offset = 0.05f * screenWH[1];
float angle_step = 30.0f;
int i = 1;
float topA = 0;
for(mainSprite s : sprites){
float top_mod = top - s.getBMP().getHeight() + (float)Math.sin(Math.toRadians(angle)) * offset - offset;
s.setPosition(left, top_mod);
left += s.getBMP().getWidth();
angle += angle_step;
if(i == 3){
topA = screenWH[1];
}
if(i == 6){
top = topA;
left = leftA;
angle_step = 36f;
angle = 0;
}
i++;
}
sp.playSound(SoundHelper.MAIN_MELODY);
addDialog();
/**
* start animation
* */
currentSpr = 8;
sprites.get(currentSpr).start();
handle.postDelayed(runAnimation, 5000);
}
public void loadMain(){
screenWH = Helper.getScreenDimensions(context);
bg = new Background(screenWH, am);
scaleAmount = bg.scaleAmount();
scaleAmount = scaleAmount * 0.9f;
strings = Helper.getStrings((int)sh.getOption(Option.LANGUAGE), new DBHelper(context));
mainTitle = Helper.scaleBMP(Helper.assetImage(am, "Main/title.png"), scaleAmount);
mainTitleM.postTranslate(bg.getMargins()[0], -10);
/**
* Load cloud-like buttons
* */
float top = 0.03f * screenWH[1];
float left = bg.getMargins()[0] + mainTitle.getWidth();
MainButton repeat = new MainButton("Main/Buttons/repeat_on.png", "Main/Buttons/repeat.png", am, scaleAmount);
repeat.setPosition(left, top);
repeat.setType(Helper.REPEAT);
btns.add(repeat);
left += 0.3f * repeat.getBMP().getWidth();
top += repeat.getBMP().getHeight();
MainButton test = new MainButton("Main/Buttons/test_on.png", "Main/Buttons/test.png", am, scaleAmount);
test.setPosition(left, top);
test.setType(Helper.TEST);
btns.add(test);
left -= 0.7f * test.getBMP().getWidth();
top += 0.5f * test.getBMP().getHeight();
MainButton learn = new MainButton("Main/Buttons/learn_on.png", "Main/Buttons/learn.png", am, scaleAmount);
learn.setPosition(left, top);
learn.setType(Helper.LEARN);
btns.add(learn);
/**
* Main control buttons
* */
left = 5;
top = bg.getTopMargin();
buttonClass parent = new buttonClass("Buttons/parents control.png", am, scaleAmount, Helper.PARENTS);
parent.setPosition(left, top);
btns.add(parent);
top += parent.getBMP().getHeight() + 2;
buttonClass facebook = new buttonClass("Buttons/facebook.png", am, scaleAmount, Helper.FACEBOOK);
facebook.setPosition(left, top);
btns.add(facebook);
top += facebook.getBMP().getHeight() + 2;
buttonClass share = new buttonClass("Buttons/share.png", am, scaleAmount, Helper.SHARE);
share.setPosition(left, top);
btns.add(share);
top += share.getBMP().getHeight() + 2;
buttonClass pay = new buttonClass("Buttons/pay.png", am, scaleAmount, Helper.PAY);
pay.setPosition(left, top);
btns.add(pay);
/**
* Sprites
* */
float widthsix = 0;
float widthasix = 0;
float highest = 0;
for(int i=1; i<=11; i++){
mainSprite spr = new mainSprite(i, context, scaleAmount);
if(i <= 6){
widthsix += spr.getBMP().getWidth();
}
else{
widthasix += spr.getBMP().getWidth();
}
if(i == 9){
highest = spr.getBMP().getHeight();
}
sprites.add(spr);
}
float[] margins = bg.getMargins();
left = margins[0] + (margins[2] - margins[0] - widthsix) / 2;
float leftA = margins[0] + (margins[2] - margins[0] - widthasix) / 2;
top = screenWH[1] - highest;
float angle = 0;
float offset = 0.05f * screenWH[1];
float angle_step = 30.0f;
int i = 1;
float topA = 0;
for(mainSprite s : sprites){
float top_mod = top - s.getBMP().getHeight() + (float)Math.sin(Math.toRadians(angle)) * offset - offset;
s.setPosition(left, top_mod);
left += s.getBMP().getWidth();
angle += angle_step;
if(i == 3){
topA = screenWH[1];
}
if(i == 6){
top = topA;
left = leftA;
angle_step = 36f;
angle = 0;
}
i++;
}
sp.playSound(SoundHelper.MAIN_MELODY);
addDialog();
/**
* start animation
* */
currentSpr = 8;
sprites.get(currentSpr).start();
//handle.postDelayed(runAnimation, 5000);
}
I'm loading the view in the onCreate method of Activity
new Handler().post(new Runnable(){
#Override
public void run(){
setContentView(new CustomView(context));
}
});
Move the code that loads all the bitmaps and stuff inside the AsyncTask instead of doing it in the constructor. Do this from the onCreate activity method.
You can then use the onProgressUpdate callback of the AsyncTask to inform of progress (e.g. a loading progress bar). For example, you can use ProgresBar.setMax to set it to the number of items to load, pass that to the progress callback as you load items via publishProgress, then call ProgressBar.setProgress with that value.
As you load the items, you can add these to an Assets class. This can be passed back via the onPostExecute callback method which you can then assign to a member variable in your activity.
You can then either have the layout of the activity look like:
LinearLayout
RelativeLayout [id=load_screen, background=loading_bg]
ProgressBar [id=loading_progress]
RelativeLayout [id=main, visibility=hidden]
...
then call findViewById(R.id.load_screen).setVisible(View.GONE) to hide the load screen and findViewById(R.id.load_screen).setVisible(View.VISIBLE)
Alternatively, you can load your replacement view (via the LayoutInflater.inflate call) and then call setContentView on it. When the inflate call returns, the view is fully loaded, so you can do all the handler setup here.
The layout loading or visibility switch will also happen in the onPostExecute method of the AsyncTask.

collision detection doesn't push back

Alright, so I'm working on collision detection for a 3d game, this is what I got so far:
public void mapCol(Spatial map, Node model2){
Mesh m = (Mesh) ((Node) map).getChild("obj_mesh0");
int c = 0;
m.updateWorldBound(true);
boolean col = false;
c = m.getMeshData().getPrimitiveCount(0);
// System.out.println(c);
Vector3[][] v3 = new Vector3[c][3];
for(int s = 0; s < c; s++){
v3[s] = null;
v3[s] = m.getMeshData().getPrimitive(s, 0, v3[s]);
Vector3 min = new Vector3((float)Math.min((float) Math.min(v3[s][0].getXf(), v3[s][1].getXf()), v3[s][2].getXf()),
(float)Math.min((float)Math.min(v3[s][0].getYf(), v3[s][1].getYf()), v3[s][2].getYf()),
(float)Math.min((float)Math.min(v3[s][0].getZf(), v3[s][1].getZf()), v3[s][2].getZf()));
Vector3 max = new Vector3((float) Math.max((float)Math.max(v3[s][0].getXf(), v3[s][1].getXf()), v3[s][2].getXf()),
(float)Math.max((float)Math.max(v3[s][0].getYf(), v3[s][1].getYf()), v3[s][2].getYf()),
(float)Math.max((float)Math.max(v3[s][0].getZf(), v3[s][1].getZf()), v3[s][2].getZf()));
Vector3 v2 = new Vector3();
v2 = max.add(min, v2);
v2.divideLocal(2);
if(max.getXf() > model2.getTranslation().getXf() - sp1.getRadius()&&
min.getXf() < model2.getTranslation().getXf() + sp1.getRadius() &&
max.getZf() > model2.getTranslation().getZf() - sp1.getRadius() &&
min.getZf() < model2.getTranslation().getZf() + sp1.getRadius() &&
max.getYf() > model2.getTranslation().getYf() + sp1.getRadius()&&
!col){
float cosine = (float) v2.dot(v2);
float angle = (float) Math.toDegrees(Math.acos( cosine ));
float pangle = (float) Math.toDegrees(Math.atan2((min.getX() + ((max.getX() - min.getX())/2)) - model2.getTranslation().getX(), (min.getZ() + ((max.getZ() - min.getZ())/2) - model2.getTranslation().getZ())));
if(min.getY() < max.getY()){
System.out.println("pangle:" + pangle + " angle:" + angle);
model2.setTranslation(
(min.getX() + ((max.getX() - min.getX())/2)) - (Math.sin(Math.toRadians(pangle)) * (sp1.getRadius())),
model2.getTranslation().getYf(),
(min.getZ() + ((max.getZ() - min.getZ())/2)) - (-Math.cos(Math.toRadians(pangle)) * (sp1.getRadius()))
);
col = true;
}
}
}
}
Now the part to really look at is right here:
model2.setTranslation(
(min.getX() + ((max.getX() - min.getX())/2)) - (Math.sin(Math.toRadians(pangle)) * (sp1.getRadius())),
model2.getTranslation().getYf(),
(min.getZ() + ((max.getZ() - min.getZ())/2)) - (-Math.cos(Math.toRadians(pangle)) * (sp1.getRadius()))
);
Any idea why it wouldn't set model2 modle2's radius away from the wall? (making it stop at the way and able to go no further)
float cosine = v2.dot(v2)
is intentional ?
Because it just gives you length of v2, squared.
Probably that should be
float cosine = velocity.dot(normalVector)/(velocity.length()*normalVector.length())
, if you wanted cosine of angle between them, but I don't fully understand your code, so I don't know.

Java image analysis - counting vertical lines

I need a little help on an image analysis algorithm in Java. I basically have images like this:
So, as you might guessed, I need to count the lines.
What approach do you think would be best?
Thanks,
Smaug
A simple segmentation algorithm can help you out. Heres how the algorithm works:
scan pixels from left to right and
record the position of the first
black (whatever the color of your
line is) pixel.
carry on this process
unless you find one whole scan when
you don't find the black pixel.
Record this position as well.
We are
just interested in the Y positions
here. Now using this Y position
segment the image horizontally.
Now
we are going to do the same process
but this time we are going to scan
from top to bottom (one column at a
time) in the segment we just created.
This time we are interested in X
positions.
So in the end we get every
lines extents or you can say a
bounding box for every line.
The
total count of these bounding boxes
is the number of lines.
You can do many optimizations in the algorithm according to your needs.
package ac.essex.ooechs.imaging.commons.edge.hough;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.util.Vector;
import java.io.File;
/**
* <p/>
* Java Implementation of the Hough Transform.<br />
* Used for finding straight lines in an image.<br />
* by Olly Oechsle
* </p>
* <p/>
* Note: This class is based on original code from:<br />
* http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm
* </p>
* <p/>
* If you represent a line as:<br />
* x cos(theta) + y sin (theta) = r
* </p>
* <p/>
* ... and you know values of x and y, you can calculate all the values of r by going through
* all the possible values of theta. If you plot the values of r on a graph for every value of
* theta you get a sinusoidal curve. This is the Hough transformation.
* </p>
* <p/>
* The hough tranform works by looking at a number of such x,y coordinates, which are usually
* found by some kind of edge detection. Each of these coordinates is transformed into
* an r, theta curve. This curve is discretised so we actually only look at a certain discrete
* number of theta values. "Accumulator" cells in a hough array along this curve are incremented
* for X and Y coordinate.
* </p>
* <p/>
* The accumulator space is plotted rectangularly with theta on one axis and r on the other.
* Each point in the array represents an (r, theta) value which can be used to represent a line
* using the formula above.
* </p>
* <p/>
* Once all the points have been added should be full of curves. The algorithm then searches for
* local peaks in the array. The higher the peak the more values of x and y crossed along that curve,
* so high peaks give good indications of a line.
* </p>
*
* #author Olly Oechsle, University of Essex
*/
public class HoughTransform extends Thread {
public static void main(String[] args) throws Exception {
String filename = "/home/ooechs/Desktop/vase.png";
// load the file using Java's imageIO library
BufferedImage image = javax.imageio.ImageIO.read(new File(filename));
// create a hough transform object with the right dimensions
HoughTransform h = new HoughTransform(image.getWidth(), image.getHeight());
// add the points from the image (or call the addPoint method separately if your points are not in an image
h.addPoints(image);
// get the lines out
Vector<HoughLine> lines = h.getLines(30);
// draw the lines back onto the image
for (int j = 0; j < lines.size(); j++) {
HoughLine line = lines.elementAt(j);
line.draw(image, Color.RED.getRGB());
}
}
// The size of the neighbourhood in which to search for other local maxima
final int neighbourhoodSize = 4;
// How many discrete values of theta shall we check?
final int maxTheta = 180;
// Using maxTheta, work out the step
final double thetaStep = Math.PI / maxTheta;
// the width and height of the image
protected int width, height;
// the hough array
protected int[][] houghArray;
// the coordinates of the centre of the image
protected float centerX, centerY;
// the height of the hough array
protected int houghHeight;
// double the hough height (allows for negative numbers)
protected int doubleHeight;
// the number of points that have been added
protected int numPoints;
// cache of values of sin and cos for different theta values. Has a significant performance improvement.
private double[] sinCache;
private double[] cosCache;
/**
* Initialises the hough transform. The dimensions of the input image are needed
* in order to initialise the hough array.
*
* #param width The width of the input image
* #param height The height of the input image
*/
public HoughTransform(int width, int height) {
this.width = width;
this.height = height;
initialise();
}
/**
* Initialises the hough array. Called by the constructor so you don't need to call it
* yourself, however you can use it to reset the transform if you want to plug in another
* image (although that image must have the same width and height)
*/
public void initialise() {
// Calculate the maximum height the hough array needs to have
houghHeight = (int) (Math.sqrt(2) * Math.max(height, width)) / 2;
// Double the height of the hough array to cope with negative r values
doubleHeight = 2 * houghHeight;
// Create the hough array
houghArray = new int[maxTheta][doubleHeight];
// Find edge points and vote in array
centerX = width / 2;
centerY = height / 2;
// Count how many points there are
numPoints = 0;
// cache the values of sin and cos for faster processing
sinCache = new double[maxTheta];
cosCache = sinCache.clone();
for (int t = 0; t < maxTheta; t++) {
double realTheta = t * thetaStep;
sinCache[t] = Math.sin(realTheta);
cosCache[t] = Math.cos(realTheta);
}
}
/**
* Adds points from an image. The image is assumed to be greyscale black and white, so all pixels that are
* not black are counted as edges. The image should have the same dimensions as the one passed to the constructor.
*/
public void addPoints(BufferedImage image) {
// Now find edge points and update the hough array
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
// Find non-black pixels
if ((image.getRGB(x, y) & 0x000000ff) != 0) {
addPoint(x, y);
}
}
}
}
/**
* Adds a single point to the hough transform. You can use this method directly
* if your data isn't represented as a buffered image.
*/
public void addPoint(int x, int y) {
// Go through each value of theta
for (int t = 0; t < maxTheta; t++) {
//Work out the r values for each theta step
int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
// this copes with negative values of r
r += houghHeight;
if (r < 0 || r >= doubleHeight) continue;
// Increment the hough array
houghArray[t][r]++;
}
numPoints++;
}
/**
* Once points have been added in some way this method extracts the lines and returns them as a Vector
* of HoughLine objects, which can be used to draw on the
*
* #param percentageThreshold The percentage threshold above which lines are determined from the hough array
*/
public Vector<HoughLine> getLines(int threshold) {
// Initialise the vector of lines that we'll return
Vector<HoughLine> lines = new Vector<HoughLine>(20);
// Only proceed if the hough array is not empty
if (numPoints == 0) return lines;
// Search for local peaks above threshold to draw
for (int t = 0; t < maxTheta; t++) {
loop:
for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) {
// Only consider points above threshold
if (houghArray[t][r] > threshold) {
int peak = houghArray[t][r];
// Check that this peak is indeed the local maxima
for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) {
for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) {
int dt = t + dx;
int dr = r + dy;
if (dt < 0) dt = dt + maxTheta;
else if (dt >= maxTheta) dt = dt - maxTheta;
if (houghArray[dt][dr] > peak) {
// found a bigger point nearby, skip
continue loop;
}
}
}
// calculate the true value of theta
double theta = t * thetaStep;
// add the line to the vector
lines.add(new HoughLine(theta, r));
}
}
}
return lines;
}
/**
* Gets the highest value in the hough array
*/
public int getHighestValue() {
int max = 0;
for (int t = 0; t < maxTheta; t++) {
for (int r = 0; r < doubleHeight; r++) {
if (houghArray[t][r] > max) {
max = houghArray[t][r];
}
}
}
return max;
}
/**
* Gets the hough array as an image, in case you want to have a look at it.
*/
public BufferedImage getHoughArrayImage() {
int max = getHighestValue();
BufferedImage image = new BufferedImage(maxTheta, doubleHeight, BufferedImage.TYPE_INT_ARGB);
for (int t = 0; t < maxTheta; t++) {
for (int r = 0; r < doubleHeight; r++) {
double value = 255 * ((double) houghArray[t][r]) / max;
int v = 255 - (int) value;
int c = new Color(v, v, v).getRGB();
image.setRGB(t, r, c);
}
}
return image;
}
}
Source: http://vase.essex.ac.uk/software/HoughTransform/HoughTransform.java.html
I've implemented a simple solution (must be improved) using Marvin Framework that finds the vertical lines start and end points and prints the total number of lines found.
Approach:
Binarize the image using a given threshold.
For each pixel, if it is black (solid), try to find a vertical line
Save the x,y, of the start and end points
The line has a minimum lenght? It is an acceptable line!
Print the start point in red and the end point in green.
The output image is shown below:
The programs output:
Vertical line fount at: (74,9,70,33)
Vertical line fount at: (113,9,109,31)
Vertical line fount at: (80,10,76,32)
Vertical line fount at: (137,11,133,33)
Vertical line fount at: (163,11,159,33)
Vertical line fount at: (184,11,180,33)
Vertical line fount at: (203,11,199,33)
Vertical line fount at: (228,11,224,33)
Vertical line fount at: (248,11,244,33)
Vertical line fount at: (52,12,50,33)
Vertical line fount at: (145,13,141,35)
Vertical line fount at: (173,13,169,35)
Vertical line fount at: (211,13,207,35)
Vertical line fount at: (94,14,90,36)
Vertical line fount at: (238,14,236,35)
Vertical line fount at: (130,16,128,37)
Vertical line fount at: (195,16,193,37)
Vertical lines total: 17
Finally, the source code:
import java.awt.Color;
import java.awt.Point;
import marvin.image.MarvinImage;
import marvin.io.MarvinImageIO;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinPluginLoader;
public class VerticalLineCounter {
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
public VerticalLineCounter(){
// Binarize
MarvinImage image = MarvinImageIO.loadImage("./res/lines.jpg");
MarvinImage binImage = image.clone();
threshold.setAttribute("threshold", 127);
threshold.process(image, binImage);
// Find lines and save an output image
MarvinImage imageOut = findVerticalLines(binImage, image);
MarvinImageIO.saveImage(imageOut, "./res/lines_out.png");
}
private MarvinImage findVerticalLines(MarvinImage binImage, MarvinImage originalImage){
MarvinImage imageOut = originalImage.clone();
boolean[][] processedPixels = new boolean[binImage.getWidth()][binImage.getHeight()];
int color;
Point endPoint;
int totalLines=0;
for(int y=0; y<binImage.getHeight(); y++){
for(int x=0; x<binImage.getWidth(); x++){
if(!processedPixels[x][y]){
color = binImage.getIntColor(x, y);
// Black?
if(color == 0xFF000000){
endPoint = getEndOfLine(x,y,binImage,processedPixels);
// Line lenght threshold
if(endPoint.x - x > 5 || endPoint.y - y > 5){
imageOut.fillRect(x-2, y-2, 5, 5, Color.red);
imageOut.fillRect(endPoint.x-2, endPoint.y-2, 5, 5, Color.green);
totalLines++;
System.out.println("Vertical line fount at: ("+x+","+y+","+endPoint.x+","+endPoint.y+")");
}
}
}
processedPixels[x][y] = true;
}
}
System.out.println("Vertical lines total: "+totalLines);
return imageOut;
}
private Point getEndOfLine(int x, int y, MarvinImage image, boolean[][] processedPixels){
int xC=x;
int cY=y;
while(true){
processedPixels[xC][cY] = true;
processedPixels[xC-1][cY] = true;
processedPixels[xC-2][cY] = true;
processedPixels[xC-3][cY] = true;
processedPixels[xC+1][cY] = true;
processedPixels[xC+2][cY] = true;
processedPixels[xC+3][cY] = true;
if(getSafeIntColor(xC,cY,image) < 0xFF000000){
// nothing
}
else if(getSafeIntColor(xC-1,cY,image) == 0xFF000000){
xC = xC-2;
}
else if(getSafeIntColor(xC-2,cY,image) == 0xFF000000){
xC = xC-3;
}
else if(getSafeIntColor(xC+1,cY,image) == 0xFF000000){
xC = xC+2;
}
else if(getSafeIntColor(xC+2,cY,image) == 0xFF000000){
xC = xC+3;
}
else{
return new Point(xC, cY);
}
cY++;
}
}
private int getSafeIntColor(int x, int y, MarvinImage image){
if(x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()){
return image.getIntColor(x, y);
}
return -1;
}
public static void main(String args[]){
new VerticalLineCounter();
System.exit(0);
}
}
It depends on how much they look like that.
Bring the image to 1-bit (black and white) in a way that preserves the lines and brings the background to pure white
Perhaps do simple cleanup like speck removal (remove any small black components).
Then,
Find a black pixel
Use flood-fill algorithms to find its extent
See if the shape meets the criteria for being a line (lineCount++ if so)
remove it
Repeat this until there are no black pixels
A lot depends on how good you do #3, some ideas
Use Hough just on this section to check that you have one line, and that it is vertical(ish)
(after #1) rotate it to the vertical and check its width/height ratio

Categories

Resources