new plotter
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import arity.calculator.Graph2dView;
|
||||
import arity.calculator.Graph3dView;
|
||||
import arity.calculator.GraphView;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import org.javia.arity.Complex;
|
||||
import org.javia.arity.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorPreferences;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -39,6 +39,12 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment
|
||||
|
||||
@Override
|
||||
protected void createGraphicalView(@NotNull View root, @NotNull PreparedInput preparedInput) {
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
|
||||
|
||||
final GraphLineColor realLineColor = CalculatorPreferences.Graph.lineColorReal.getPreference(preferences);
|
||||
final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences);
|
||||
|
||||
// remove old
|
||||
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
|
||||
|
||||
@@ -53,45 +59,28 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment
|
||||
|
||||
final int arity = yVariable == null ? 1 : 2;
|
||||
|
||||
final List<Function> functions = new ArrayList<Function>();
|
||||
functions.add(new Function() {
|
||||
@Override
|
||||
public int arity() {
|
||||
return arity;
|
||||
}
|
||||
final List<FunctionPlotDef> functions = new ArrayList<FunctionPlotDef>();
|
||||
|
||||
@Override
|
||||
public double eval(double x) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x).realPart();
|
||||
}
|
||||
functions.add(FunctionPlotDef.newInstance(new RealArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(realLineColor.getColor(), FunctionLineStyle.solid, 3f)));
|
||||
|
||||
@Override
|
||||
public double eval(double x, double y) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x, yVariable, y).realPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Complex eval(Complex x) {
|
||||
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re);
|
||||
return new Complex(result.realPart(), result.imaginaryPart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Complex eval(Complex x, Complex y) {
|
||||
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re, yVariable, y.re);
|
||||
return new Complex(result.realPart(), result.imaginaryPart());
|
||||
}
|
||||
});
|
||||
|
||||
if (functions.size() == 1) {
|
||||
final Function f = functions.get(0);
|
||||
graphView = f.arity() == 1 ? new Graph2dView(getActivity()) : new Graph3dView(getActivity());
|
||||
graphView.setFunction(f);
|
||||
} else {
|
||||
graphView = new Graph2dView(this.getActivity());
|
||||
((Graph2dView) graphView).setFunctions(functions);
|
||||
if (arity == 1) {
|
||||
functions.add(FunctionPlotDef.newInstance(new ImaginaryArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(imagLineColor.getColor(), FunctionLineStyle.solid, 3f)));
|
||||
}
|
||||
|
||||
switch (arity) {
|
||||
case 1:
|
||||
graphView = new Graph2dView(getActivity());
|
||||
break;
|
||||
case 2:
|
||||
graphView = new Graph3dView(getActivity());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported arity: " + arity);
|
||||
}
|
||||
|
||||
graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor()));
|
||||
graphView.setFunctionPlotDefs(functions);
|
||||
|
||||
graphContainer.addView((View) graphView);
|
||||
} else {
|
||||
onError();
|
||||
@@ -128,4 +117,79 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment
|
||||
this.graphView.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* STATIC
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
private static abstract class AbstractArityFunction extends Function {
|
||||
|
||||
protected final int arity;
|
||||
|
||||
@NotNull
|
||||
protected final Generic expression;
|
||||
|
||||
@NotNull
|
||||
protected final Constant xVariable;
|
||||
|
||||
@Nullable
|
||||
protected final Constant yVariable;
|
||||
|
||||
public AbstractArityFunction(int arity, @NotNull Generic expression, @NotNull Constant xVariable, @Nullable Constant yVariable) {
|
||||
this.arity = arity;
|
||||
this.expression = expression;
|
||||
this.xVariable = xVariable;
|
||||
this.yVariable = yVariable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int arity() {
|
||||
return arity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RealArityFunction extends AbstractArityFunction {
|
||||
|
||||
private RealArityFunction(int arity,
|
||||
@NotNull Generic expression,
|
||||
@NotNull Constant xVariable,
|
||||
@Nullable Constant yVariable) {
|
||||
super(arity, expression, xVariable, yVariable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x).realPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x, double y) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x, yVariable, y).realPart();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImaginaryArityFunction extends AbstractArityFunction {
|
||||
|
||||
private ImaginaryArityFunction(int arity,
|
||||
@NotNull Generic expression,
|
||||
@NotNull Constant xVariable,
|
||||
@Nullable Constant yVariable) {
|
||||
super(arity, expression, xVariable, yVariable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x).imaginaryPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double eval(double x, double y) {
|
||||
return PlotUtils.calculatorExpression(expression, xVariable, x, yVariable, y).imaginaryPart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
android-app/src/main/java/org/solovyev/android/calculator/plot/FPS.java
Executable file
22
android-app/src/main/java/org/solovyev/android/calculator/plot/FPS.java
Executable file
@@ -0,0 +1,22 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
class FPS {
|
||||
private int drawCnt;
|
||||
private long lastTime;
|
||||
private int fps;
|
||||
|
||||
boolean incFrame() {
|
||||
if (--drawCnt > 0) {
|
||||
return false;
|
||||
}
|
||||
drawCnt = 100;
|
||||
long now = System.currentTimeMillis();
|
||||
fps = Math.round(100000f / (now - lastTime));
|
||||
lastTime = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return fps;
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/13
|
||||
* Time: 7:41 PM
|
||||
*/
|
||||
public class FunctionLineDef {
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* CONSTANTS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
private static final Float DEFAULT_LINE_WIDTH = -1f;
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* FIELDS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
private int lineColor = Color.WHITE;
|
||||
|
||||
@NotNull
|
||||
private FunctionLineStyle lineStyle = FunctionLineStyle.solid;
|
||||
|
||||
private float lineWidth = -DEFAULT_LINE_WIDTH;
|
||||
|
||||
private FunctionLineDef() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle) {
|
||||
final FunctionLineDef result = new FunctionLineDef();
|
||||
result.lineColor = lineColor;
|
||||
result.lineStyle = lineStyle;
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle, float lineWidth) {
|
||||
final FunctionLineDef result = new FunctionLineDef();
|
||||
result.lineColor = lineColor;
|
||||
result.lineStyle = lineStyle;
|
||||
result.lineWidth = lineWidth;
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionLineDef newDefaultInstance() {
|
||||
return new FunctionLineDef();
|
||||
}
|
||||
|
||||
|
||||
public int getLineColor() {
|
||||
return lineColor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionLineStyle getLineStyle() {
|
||||
return lineStyle;
|
||||
}
|
||||
|
||||
public float getLineWidth() {
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
public void applyToPaint(@NotNull Paint paint) {
|
||||
paint.setColor(lineColor);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
if ( lineWidth == DEFAULT_LINE_WIDTH ) {
|
||||
paint.setStrokeWidth(0);
|
||||
} else {
|
||||
paint.setStrokeWidth(lineWidth);
|
||||
}
|
||||
|
||||
lineStyle.applyToPaint(paint);
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/13
|
||||
* Time: 7:37 PM
|
||||
*/
|
||||
public enum FunctionLineStyle {
|
||||
|
||||
solid {
|
||||
@Override
|
||||
public void applyToPaint(@NotNull Paint paint) {
|
||||
paint.setPathEffect(null);
|
||||
}
|
||||
},
|
||||
|
||||
dashed {
|
||||
@Override
|
||||
public void applyToPaint(@NotNull Paint paint) {
|
||||
paint.setPathEffect(new DashPathEffect(new float[] {10, 20}, 0));
|
||||
}
|
||||
},
|
||||
|
||||
dotted {
|
||||
@Override
|
||||
public void applyToPaint(@NotNull Paint paint) {
|
||||
paint.setPathEffect(new DashPathEffect(new float[] {5, 1}, 0));
|
||||
}
|
||||
},
|
||||
|
||||
dash_dotted {
|
||||
@Override
|
||||
public void applyToPaint(@NotNull Paint paint) {
|
||||
paint.setPathEffect(new DashPathEffect(new float[] {10, 20, 5, 1}, 0));
|
||||
}
|
||||
};
|
||||
|
||||
public abstract void applyToPaint(@NotNull Paint paint);
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import org.javia.arity.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/13
|
||||
* Time: 7:35 PM
|
||||
*/
|
||||
public class FunctionPlotDef {
|
||||
|
||||
@NotNull
|
||||
private Function function;
|
||||
|
||||
@NotNull
|
||||
private FunctionLineDef lineDef;
|
||||
|
||||
private FunctionPlotDef() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionPlotDef newInstance(@NotNull Function function) {
|
||||
return newInstance(function, FunctionLineDef.newDefaultInstance());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionPlotDef newInstance(@NotNull Function function, @NotNull FunctionLineDef lineDef) {
|
||||
final FunctionPlotDef result = new FunctionPlotDef();
|
||||
|
||||
result.function = function;
|
||||
result.lineDef = lineDef;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Function getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionLineDef getLineDef() {
|
||||
return lineDef;
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.graphics.Color;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/13
|
||||
* Time: 9:11 PM
|
||||
*/
|
||||
public class FunctionViewDef {
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* CONSTANTS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
private static final int DEFAULT_AXIS_COLOR = 0xff00a000;
|
||||
private static final int DEFAULT_GRID_COLOR = 0xff004000;
|
||||
private static final int DEFAULT_BACKGROUND_COLOR = Color.BLACK;
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* FIELDS
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
private int axisColor = DEFAULT_AXIS_COLOR;
|
||||
|
||||
private int axisLabelsColor = DEFAULT_AXIS_COLOR;
|
||||
|
||||
private int gridColor = DEFAULT_GRID_COLOR;
|
||||
|
||||
private int backgroundColor = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
private FunctionViewDef() {
|
||||
}
|
||||
|
||||
private FunctionViewDef(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
|
||||
this.axisColor = axisColor;
|
||||
this.axisLabelsColor = axisLabelColor;
|
||||
this.gridColor = gridColor;
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionViewDef newDefaultInstance() {
|
||||
return new FunctionViewDef();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionViewDef newInstance(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
|
||||
return new FunctionViewDef(axisColor, axisLabelColor, gridColor, backgroundColor);
|
||||
}
|
||||
|
||||
public int getAxisColor() {
|
||||
return axisColor;
|
||||
}
|
||||
|
||||
public int getAxisLabelsColor() {
|
||||
return axisLabelsColor;
|
||||
}
|
||||
|
||||
public int getGridColor() {
|
||||
return gridColor;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return backgroundColor;
|
||||
}
|
||||
}
|
190
android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java
Executable file
190
android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java
Executable file
@@ -0,0 +1,190 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import org.solovyev.android.AndroidUtils2;
|
||||
|
||||
import javax.microedition.khronos.egl.*;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private boolean hasSurface;
|
||||
private boolean paused;
|
||||
private EGL10 egl;
|
||||
private EGLDisplay display;
|
||||
private EGLConfig config;
|
||||
private EGLSurface surface;
|
||||
private EGLContext eglContext;
|
||||
private GL11 gl;
|
||||
protected int width, height;
|
||||
private boolean mIsLooping;
|
||||
|
||||
abstract void onDrawFrame(GL10 gl);
|
||||
abstract void onSurfaceCreated(GL10 gl, int width, int height);
|
||||
|
||||
public String captureScreenshot() {
|
||||
Bitmap bitmap = getRawPixels(gl, width, height);
|
||||
bitmapBGRtoRGB(bitmap, width, height);
|
||||
return AndroidUtils2.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
|
||||
}
|
||||
|
||||
private static Bitmap getRawPixels(GL10 gl, int width, int height) {
|
||||
int size = width * height;
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(size * 4);
|
||||
buf.order(ByteOrder.nativeOrder());
|
||||
gl.glReadPixels(0, 0, width, height, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buf);
|
||||
int data[] = new int[size];
|
||||
buf.asIntBuffer().get(data);
|
||||
buf = null;
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
bitmap.setPixels(data, size-width, -width, 0, 0, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Handler handler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
glDraw();
|
||||
}
|
||||
};
|
||||
|
||||
public GLView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public GLView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
SurfaceHolder holder = getHolder();
|
||||
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
|
||||
holder.addCallback(this);
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
paused = false;
|
||||
if (hasSurface) {
|
||||
initGL();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
deinitGL();
|
||||
}
|
||||
|
||||
private void initGL() {
|
||||
egl = (EGL10) EGLContext.getEGL();
|
||||
display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
||||
int[] ver = new int[2];
|
||||
egl.eglInitialize(display, ver);
|
||||
|
||||
int[] configSpec = {EGL10.EGL_NONE};
|
||||
EGLConfig[] configOut = new EGLConfig[1];
|
||||
int[] nConfig = new int[1];
|
||||
egl.eglChooseConfig(display, configSpec, configOut, 1, nConfig);
|
||||
config = configOut[0];
|
||||
eglContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, null);
|
||||
surface = egl.eglCreateWindowSurface(display, config, getHolder(), null);
|
||||
egl.eglMakeCurrent(display, surface, surface, eglContext);
|
||||
gl = (GL11) eglContext.getGL();
|
||||
onSurfaceCreated(gl, width, height);
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
private void deinitGL() {
|
||||
paused = true;
|
||||
if (display != null) {
|
||||
egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
||||
egl.eglDestroySurface(display, surface);
|
||||
egl.eglDestroyContext(display, eglContext);
|
||||
egl.eglTerminate(display);
|
||||
|
||||
egl = null;
|
||||
config = null;
|
||||
eglContext = null;
|
||||
surface = null;
|
||||
display = null;
|
||||
gl = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void glDraw() {
|
||||
if (hasSurface && !paused) {
|
||||
onDrawFrame(gl);
|
||||
if (!egl.eglSwapBuffers(display, surface)) {
|
||||
}
|
||||
if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) {
|
||||
paused = true;
|
||||
}
|
||||
if (mIsLooping) {
|
||||
requestDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
boolean doInit = !hasSurface && !paused;
|
||||
hasSurface = true;
|
||||
if (doInit) {
|
||||
initGL();
|
||||
}
|
||||
}
|
||||
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
hasSurface = false;
|
||||
deinitGL();
|
||||
}
|
||||
|
||||
public void startLooping() {
|
||||
if (!mIsLooping) {
|
||||
mIsLooping = true;
|
||||
glDraw();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopLooping() {
|
||||
if (mIsLooping) {
|
||||
mIsLooping = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLooping() {
|
||||
return mIsLooping;
|
||||
}
|
||||
|
||||
public void requestDraw() {
|
||||
handler.sendEmptyMessage(1);
|
||||
}
|
||||
|
||||
static void bitmapBGRtoRGB(Bitmap bitmap, int width, int height) {
|
||||
int size = width * height;
|
||||
short data[] = new short[size];
|
||||
ShortBuffer buf = ShortBuffer.wrap(data);
|
||||
bitmap.copyPixelsToBuffer(buf);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
//BGR-565 to RGB-565
|
||||
short v = data[i];
|
||||
data[i] = (short) (((v&0x1f) << 11) | (v&0x7e0) | ((v&0xf800) >> 11));
|
||||
}
|
||||
buf.rewind();
|
||||
bitmap.copyPixelsFromBuffer(buf);
|
||||
}
|
||||
}
|
596
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java
Executable file
596
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java
Executable file
@@ -0,0 +1,596 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.*;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
import android.widget.ZoomButtonsController;
|
||||
import org.javia.arity.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.AndroidUtils2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Graph2dView extends View implements GraphView {
|
||||
|
||||
private int width, height;
|
||||
private Matrix matrix = new Matrix();
|
||||
private Paint paint = new Paint(), textPaint = new Paint(), fillPaint = new Paint();
|
||||
|
||||
@NotNull
|
||||
private GraphViewHelper graphViewHelper = GraphViewHelper.newDefaultInstance();
|
||||
|
||||
private final GraphData next = GraphData.newEmptyInstance();
|
||||
|
||||
private final GraphData endGraph = GraphData.newEmptyInstance();
|
||||
|
||||
@NotNull
|
||||
private List<GraphData> graphs = new ArrayList<GraphData>(graphViewHelper.getFunctionPlotDefs().size());
|
||||
|
||||
private float gwidth = 8;
|
||||
private float currentX, currentY;
|
||||
private float lastMinX;
|
||||
private Scroller scroller;
|
||||
private float boundMinY, boundMaxY;
|
||||
protected ZoomButtonsController zoomController = new ZoomButtonsController(this);
|
||||
private ZoomTracker zoomTracker = new ZoomTracker();
|
||||
private TouchHandler touchHandler;
|
||||
private float lastTouchX, lastTouchY;
|
||||
|
||||
private static final int
|
||||
COL_ZOOM = 0x40ffffff,
|
||||
COL_ZOOM_TEXT1 = 0xd0ffffff,
|
||||
COL_ZOOM_TEXT2 = 0x30ffffff;
|
||||
|
||||
public Graph2dView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public Graph2dView(Context context) {
|
||||
super(context);
|
||||
touchHandler = new TouchHandler(this);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
zoomController.setOnZoomListener(this);
|
||||
scroller = new Scroller(context);
|
||||
paint.setAntiAlias(false);
|
||||
textPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public String captureScreenshot() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
onDraw(canvas);
|
||||
return AndroidUtils2.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
|
||||
}
|
||||
|
||||
private void clearAllGraphs() {
|
||||
for (GraphData graph : graphs) {
|
||||
graph.clear();
|
||||
}
|
||||
|
||||
while ( graphViewHelper.getFunctionPlotDefs().size() > graphs.size() ) {
|
||||
graphs.add(GraphData.newEmptyInstance());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(@NotNull FunctionViewDef functionViewDef) {
|
||||
this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.<FunctionPlotDef>emptyList());
|
||||
}
|
||||
|
||||
public void setFunctionPlotDefs(@NotNull List<FunctionPlotDef> functionPlotDefs) {
|
||||
|
||||
for (FunctionPlotDef functionPlotDef: functionPlotDefs) {
|
||||
final int arity = functionPlotDef.getFunction().arity();
|
||||
if (arity != 0 && arity != 1) {
|
||||
throw new IllegalArgumentException("Function must have arity 0 or 1 for 2d plot!");
|
||||
}
|
||||
}
|
||||
|
||||
this.graphViewHelper = this.graphViewHelper.copy(functionPlotDefs);
|
||||
clearAllGraphs();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void onVisibilityChanged(boolean visible) {
|
||||
}
|
||||
|
||||
public void onZoom(boolean zoomIn) {
|
||||
if (zoomIn) {
|
||||
if (canZoomIn()) {
|
||||
gwidth /= 2;
|
||||
invalidateGraphs();
|
||||
}
|
||||
} else {
|
||||
if (canZoomOut()) {
|
||||
gwidth *= 2;
|
||||
invalidateGraphs();
|
||||
}
|
||||
}
|
||||
zoomController.setZoomInEnabled(canZoomIn());
|
||||
zoomController.setZoomOutEnabled(canZoomOut());
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
}
|
||||
|
||||
public void onDetachedFromWindow() {
|
||||
zoomController.setVisible(false);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
protected void onSizeChanged(int w, int h, int ow, int oh) {
|
||||
width = w;
|
||||
height = h;
|
||||
clearAllGraphs();
|
||||
// points = new float[w+w];
|
||||
}
|
||||
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (graphViewHelper.getFunctionPlotDefs().size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (scroller.computeScrollOffset()) {
|
||||
final float scale = gwidth / width;
|
||||
currentX = scroller.getCurrX() * scale;
|
||||
currentY = scroller.getCurrY() * scale;
|
||||
if (!scroller.isFinished()) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
drawGraph(canvas);
|
||||
}
|
||||
|
||||
private float eval(Function f, float x) {
|
||||
float v = (float) f.eval(x);
|
||||
// Calculator.log("eval " + x + "; " + v);
|
||||
if (v < -10000f) {
|
||||
return -10000f;
|
||||
}
|
||||
if (v > 10000f) {
|
||||
return 10000f;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// distance from (x,y) to the line (x1,y1) to (x2,y2), squared, multiplied by 4
|
||||
/*
|
||||
private float distance(float x1, float y1, float x2, float y2, float x, float y) {
|
||||
float dx = x2 - x1;
|
||||
float dy = y2 - y1;
|
||||
float mx = x - x1;
|
||||
float my = y - y1;
|
||||
float up = dx*my - dy*mx;
|
||||
return up*up*4/(dx*dx + dy*dy);
|
||||
}
|
||||
*/
|
||||
|
||||
// distance as above when x==(x1+x2)/2.
|
||||
private float distance2(float x1, float y1, float x2, float y2, float y) {
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
final float up = dx * (y1 + y2 - y - y);
|
||||
return up * up / (dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private void computeGraph(@NotNull Function function,
|
||||
float minX,
|
||||
float maxX,
|
||||
float minY,
|
||||
float maxY,
|
||||
@NotNull GraphData graph) {
|
||||
if (function.arity() == 0) {
|
||||
float v = (float) function.eval();
|
||||
if (v < -10000f) {
|
||||
v = -10000f;
|
||||
}
|
||||
if (v > 10000f) {
|
||||
v = 10000f;
|
||||
}
|
||||
graph.clear();
|
||||
graph.push(minX, v);
|
||||
graph.push(maxX, v);
|
||||
return;
|
||||
}
|
||||
|
||||
final float scale = width / gwidth;
|
||||
final float maxStep = 15.8976f / scale;
|
||||
final float minStep = .05f / scale;
|
||||
float ythresh = 1 / scale;
|
||||
ythresh = ythresh * ythresh;
|
||||
// next.clear();
|
||||
// endGraph.clear();
|
||||
if (!graph.empty()) {
|
||||
// Calculator.log("last " + lastMinX + " min " + minX);
|
||||
if (minX >= lastMinX) {
|
||||
graph.eraseBefore(minX);
|
||||
} else {
|
||||
graph.eraseAfter(maxX);
|
||||
maxX = Math.min(maxX, graph.firstX());
|
||||
graph.swap(endGraph);
|
||||
}
|
||||
}
|
||||
if (graph.empty()) {
|
||||
graph.push(minX, eval(function, minX));
|
||||
}
|
||||
float leftX, leftY;
|
||||
float rightX = graph.topX(), rightY = graph.topY();
|
||||
int nEval = 1;
|
||||
while (true) {
|
||||
leftX = rightX;
|
||||
leftY = rightY;
|
||||
if (leftX > maxX) {
|
||||
break;
|
||||
}
|
||||
if (next.empty()) {
|
||||
float x = leftX + maxStep;
|
||||
next.push(x, eval(function, x));
|
||||
++nEval;
|
||||
}
|
||||
rightX = next.topX();
|
||||
rightY = next.topY();
|
||||
next.pop();
|
||||
|
||||
if (leftY != leftY && rightY != rightY) { // NaN
|
||||
continue;
|
||||
}
|
||||
|
||||
float dx = rightX - leftX;
|
||||
float middleX = (leftX + rightX) / 2;
|
||||
float middleY = eval(function, middleX);
|
||||
++nEval;
|
||||
boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY);
|
||||
if (dx < minStep) {
|
||||
// Calculator.log("minStep");
|
||||
if (middleIsOutside) {
|
||||
graph.push(rightX, Float.NaN);
|
||||
}
|
||||
graph.push(rightX, rightY);
|
||||
continue;
|
||||
}
|
||||
if (middleIsOutside && ((leftY < minY && rightY > maxY) || (leftY > maxY && rightY < minY))) {
|
||||
graph.push(rightX, Float.NaN);
|
||||
graph.push(rightX, rightY);
|
||||
// Calculator.log("+-inf");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!middleIsOutside) {
|
||||
/*
|
||||
float diff = leftY + rightY - middleY - middleY;
|
||||
float dy = rightY - leftY;
|
||||
float dx2 = dx*dx;
|
||||
float distance = dx2*diff*diff/(dx2+dy*dy);
|
||||
*/
|
||||
// Calculator.log("" + dx + ' ' + leftY + ' ' + middleY + ' ' + rightY + ' ' + distance + ' ' + ythresh);
|
||||
if (distance2(leftX, leftY, rightX, rightY, middleY) < ythresh) {
|
||||
graph.push(rightX, rightY);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next.push(rightX, rightY);
|
||||
next.push(middleX, middleY);
|
||||
rightX = leftX;
|
||||
rightY = leftY;
|
||||
}
|
||||
if (!endGraph.empty()) {
|
||||
graph.append(endGraph);
|
||||
}
|
||||
long t2 = System.currentTimeMillis();
|
||||
// Calculator.log("graph points " + graph.size + " evals " + nEval + " time " + (t2-t1));
|
||||
|
||||
next.clear();
|
||||
endGraph.clear();
|
||||
}
|
||||
|
||||
private static void graphToPath(@NotNull GraphData graph, @NotNull Path path) {
|
||||
|
||||
final int size = graph.getSize();
|
||||
final float[] xs = graph.getXs();
|
||||
final float[] ys = graph.getYs();
|
||||
|
||||
path.rewind();
|
||||
|
||||
boolean newCurve = true;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
||||
final float y = ys[i];
|
||||
final float x = xs[i];
|
||||
|
||||
if (y != y) {
|
||||
newCurve = true;
|
||||
} else { // !NaN
|
||||
if (newCurve) {
|
||||
path.moveTo(x, y);
|
||||
newCurve = false;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final float NTICKS = 15;
|
||||
|
||||
private static float stepFactor(float w) {
|
||||
float f = 1;
|
||||
while (w / f > NTICKS) {
|
||||
f *= 10;
|
||||
}
|
||||
while (w / f < NTICKS / 10) {
|
||||
f /= 10;
|
||||
}
|
||||
float r = w / f;
|
||||
if (r < NTICKS / 5) {
|
||||
return f / 5;
|
||||
} else if (r < NTICKS / 2) {
|
||||
return f / 2;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder b = new StringBuilder();
|
||||
private static char[] buf = new char[20];
|
||||
|
||||
private static StringBuilder format(float fv) {
|
||||
int pos = 0;
|
||||
boolean addDot = false;
|
||||
int v = Math.round(fv * 100);
|
||||
boolean isNeg = v < 0;
|
||||
v = isNeg ? -v : v;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int digit = v % 10;
|
||||
v /= 10;
|
||||
if (digit != 0 || addDot) {
|
||||
buf[pos++] = (char) ('0' + digit);
|
||||
addDot = true;
|
||||
}
|
||||
}
|
||||
if (addDot) {
|
||||
buf[pos++] = '.';
|
||||
}
|
||||
if (v == 0) {
|
||||
buf[pos++] = '0';
|
||||
}
|
||||
while (v != 0) {
|
||||
buf[pos++] = (char) ('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
if (isNeg) {
|
||||
buf[pos++] = '-';
|
||||
}
|
||||
b.setLength(0);
|
||||
b.append(buf, 0, pos);
|
||||
b.reverse();
|
||||
return b;
|
||||
}
|
||||
|
||||
private void drawGraph(Canvas canvas) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
float minX = getXMin();
|
||||
float maxX = getXMax(minX);
|
||||
float ywidth = gwidth * height / width;
|
||||
float minY = currentY - ywidth / 2;
|
||||
float maxY = minY + ywidth;
|
||||
if (minY < boundMinY || maxY > boundMaxY) {
|
||||
float halfw = ywidth / 2;
|
||||
boundMinY = minY - halfw;
|
||||
boundMaxY = maxY + halfw;
|
||||
clearAllGraphs();
|
||||
}
|
||||
|
||||
canvas.drawColor(graphViewHelper.getFunctionViewDef().getBackgroundColor());
|
||||
|
||||
paint.setStrokeWidth(0);
|
||||
paint.setAntiAlias(false);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
final float h2 = height / 2f;
|
||||
final float scale = width / gwidth;
|
||||
|
||||
float x0 = -minX * scale;
|
||||
boolean drawYAxis = true;
|
||||
if (x0 < 25) {
|
||||
x0 = 25;
|
||||
// drawYAxis = false;
|
||||
} else if (x0 > width - 3) {
|
||||
x0 = width - 3;
|
||||
// drawYAxis = false;
|
||||
}
|
||||
float y0 = maxY * scale;
|
||||
if (y0 < 3) {
|
||||
y0 = 3;
|
||||
} else if (y0 > height - 15) {
|
||||
y0 = height - 15;
|
||||
}
|
||||
|
||||
final float tickSize = 3;
|
||||
final float y2 = y0 + tickSize;
|
||||
|
||||
|
||||
{
|
||||
// GRID
|
||||
|
||||
paint.setPathEffect(new DashPathEffect(new float[]{5, 10}, 0));
|
||||
paint.setColor(graphViewHelper.getFunctionViewDef().getGridColor());
|
||||
|
||||
float step = stepFactor(gwidth);
|
||||
// Calculator.log("width " + gwidth + " step " + step);
|
||||
float v = ((int) (minX / step)) * step;
|
||||
textPaint.setColor(graphViewHelper.getFunctionViewDef().getAxisLabelsColor());
|
||||
textPaint.setTextSize(12);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
float stepScale = step * scale;
|
||||
for (float x = (v - minX) * scale; x <= width; x += stepScale, v += step) {
|
||||
canvas.drawLine(x, 0, x, height, paint);
|
||||
if (!(-.001f < v && v < .001f)) {
|
||||
StringBuilder b = format(v);
|
||||
canvas.drawText(b, 0, b.length(), x, y2 + 10, textPaint);
|
||||
}
|
||||
}
|
||||
|
||||
final float x1 = x0 - tickSize;
|
||||
v = ((int) (minY / step)) * step;
|
||||
textPaint.setTextAlign(Paint.Align.RIGHT);
|
||||
for (float y = height - (v - minY) * scale; y >= 0; y -= stepScale, v += step) {
|
||||
canvas.drawLine(0, y, width, y, paint);
|
||||
if (!(-.001f < v && v < .001f)) {
|
||||
StringBuilder b = format(v);
|
||||
canvas.drawText(b, 0, b.length(), x1, y + 4, textPaint);
|
||||
}
|
||||
}
|
||||
|
||||
paint.setPathEffect(null);
|
||||
}
|
||||
|
||||
// AXIS
|
||||
|
||||
paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor());
|
||||
if (drawYAxis) {
|
||||
canvas.drawLine(x0, 0, x0, height, paint);
|
||||
}
|
||||
canvas.drawLine(0, y0, width, y0, paint);
|
||||
|
||||
matrix.reset();
|
||||
matrix.preTranslate(-currentX, -currentY);
|
||||
matrix.postScale(scale, -scale);
|
||||
matrix.postTranslate(width / 2, height / 2);
|
||||
|
||||
paint.setAntiAlias(false);
|
||||
|
||||
final List<FunctionPlotDef> functionPlotDefs = graphViewHelper.getFunctionPlotDefs();
|
||||
|
||||
// create path once
|
||||
final Path path = new Path();
|
||||
|
||||
for (int i = 0; i < functionPlotDefs.size(); i++) {
|
||||
final FunctionPlotDef fpd = functionPlotDefs.get(i);
|
||||
computeGraph(fpd.getFunction(), minX, maxX, boundMinY, boundMaxY, graphs.get(i));
|
||||
|
||||
graphToPath(graphs.get(i), path);
|
||||
|
||||
path.transform(matrix);
|
||||
|
||||
fpd.getLineDef().applyToPaint(paint);
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
lastMinX = minX;
|
||||
}
|
||||
|
||||
private float getXMax(float minX) {
|
||||
return minX + gwidth;
|
||||
}
|
||||
|
||||
private float getXMax() {
|
||||
return getXMax(getXMin());
|
||||
}
|
||||
|
||||
private float getXMin() {
|
||||
return currentX - gwidth / 2;
|
||||
}
|
||||
|
||||
private boolean canZoomIn() {
|
||||
return gwidth > 1f;
|
||||
}
|
||||
|
||||
private boolean canZoomOut() {
|
||||
return gwidth < 50;
|
||||
}
|
||||
|
||||
private void invalidateGraphs() {
|
||||
clearAllGraphs();
|
||||
boundMinY = boundMaxY = 0;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void onTouchDown(float x, float y) {
|
||||
zoomController.setVisible(true);
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.abortAnimation();
|
||||
}
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
|
||||
public void onTouchMove(float x, float y) {
|
||||
float deltaX = x - lastTouchX;
|
||||
float deltaY = y - lastTouchY;
|
||||
if (deltaX < -1 || deltaX > 1 || deltaY < -1 || deltaY > 1) {
|
||||
scroll(-deltaX, deltaY);
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchUp(float x, float y) {
|
||||
final float scale = width / gwidth;
|
||||
float sx = -touchHandler.velocityTracker.getXVelocity();
|
||||
float sy = touchHandler.velocityTracker.getYVelocity();
|
||||
final float asx = Math.abs(sx);
|
||||
final float asy = Math.abs(sy);
|
||||
if (asx < asy / 3) {
|
||||
sx = 0;
|
||||
} else if (asy < asx / 3) {
|
||||
sy = 0;
|
||||
}
|
||||
scroller.fling(Math.round(currentX * scale),
|
||||
Math.round(currentY * scale),
|
||||
Math.round(sx), Math.round(sy), -10000, 10000, -10000, 10000);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void onTouchZoomDown(float x1, float y1, float x2, float y2) {
|
||||
zoomTracker.start(gwidth, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
public void onTouchZoomMove(float x1, float y1, float x2, float y2) {
|
||||
if (!zoomTracker.update(x1, y1, x2, y2)) {
|
||||
return;
|
||||
}
|
||||
float targetGwidth = zoomTracker.value;
|
||||
if (targetGwidth > .25f && targetGwidth < 200) {
|
||||
gwidth = targetGwidth;
|
||||
}
|
||||
// scroll(-zoomTracker.moveX, zoomTracker.moveY);
|
||||
invalidateGraphs();
|
||||
// Calculator.log("zoom redraw");
|
||||
}
|
||||
|
||||
private void scroll(float deltaX, float deltaY) {
|
||||
final float scale = gwidth / width;
|
||||
float dx = deltaX * scale;
|
||||
float dy = deltaY * scale;
|
||||
final float adx = Math.abs(dx);
|
||||
final float ady = Math.abs(dy);
|
||||
if (adx < ady / 3) {
|
||||
dx = 0;
|
||||
} else if (ady < adx / 3) {
|
||||
dy = 0;
|
||||
}
|
||||
currentX += dx;
|
||||
currentY += dy;
|
||||
}
|
||||
}
|
281
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java
Executable file
281
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java
Executable file
@@ -0,0 +1,281 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import org.javia.arity.Function;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
class Graph3d {
|
||||
|
||||
private final int N;
|
||||
private final boolean useHighQuality3d;
|
||||
private ShortBuffer verticeIdx;
|
||||
private FloatBuffer vertexBuf;
|
||||
private ByteBuffer colorBuf;
|
||||
private int vertexVbo, colorVbo, vertexElementVbo;
|
||||
private boolean useVBO;
|
||||
private int nVertex;
|
||||
|
||||
Graph3d(GL11 gl, boolean useHighQuality3d) {
|
||||
this.useHighQuality3d = useHighQuality3d;
|
||||
this.N = useHighQuality3d ? 36 : 24;
|
||||
|
||||
short[] b = new short[N * N];
|
||||
int p = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
short v = 0;
|
||||
for (int j = 0; j < N; v += N + N, j += 2) {
|
||||
b[p++] = (short) (v + i);
|
||||
b[p++] = (short) (v + N + N - 1 - i);
|
||||
}
|
||||
v = (short) (N * (N - 2));
|
||||
i++;
|
||||
for (int j = N - 1; j >= 0; v -= N + N, j -= 2) {
|
||||
b[p++] = (short) (v + N + N - 1 - i);
|
||||
b[p++] = (short) (v + i);
|
||||
}
|
||||
}
|
||||
verticeIdx = buildBuffer(b);
|
||||
|
||||
String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
|
||||
useVBO = extensions.indexOf("vertex_buffer_object") != -1;
|
||||
//Calculator.log("VBOs support: " + useVBO + " version " + gl.glGetString(GL10.GL_VERSION));
|
||||
|
||||
if (useVBO) {
|
||||
int[] out = new int[3];
|
||||
gl.glGenBuffers(3, out, 0);
|
||||
vertexVbo = out[0];
|
||||
colorVbo = out[1];
|
||||
vertexElementVbo = out[2];
|
||||
}
|
||||
}
|
||||
|
||||
private static FloatBuffer buildBuffer(float[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 2);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
FloatBuffer sb = bb.asFloatBuffer();
|
||||
sb.put(b);
|
||||
sb.position(0);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static ShortBuffer buildBuffer(short[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
ShortBuffer sb = bb.asShortBuffer();
|
||||
sb.put(b);
|
||||
sb.position(0);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private static ByteBuffer buildBuffer(byte[] b) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
bb.put(b);
|
||||
bb.position(0);
|
||||
return bb;
|
||||
}
|
||||
|
||||
public void update(GL11 gl, Function f, float zoom) {
|
||||
final int NTICK = useHighQuality3d ? 5 : 0;
|
||||
final float size = 4 * zoom;
|
||||
final float minX = -size, maxX = size, minY = -size, maxY = size;
|
||||
|
||||
//Calculator.log("update VBOs " + vertexVbo + ' ' + colorVbo + ' ' + vertexElementVbo);
|
||||
nVertex = N * N + 6 + 8 + NTICK * 6;
|
||||
int nFloats = nVertex * 3;
|
||||
float vertices[] = new float[nFloats];
|
||||
byte colors[] = new byte[nVertex << 2];
|
||||
if (f != null) {
|
||||
//Calculator.log("Graph3d update");
|
||||
float sizeX = maxX - minX;
|
||||
float sizeY = maxY - minY;
|
||||
float stepX = sizeX / (N - 1);
|
||||
float stepY = sizeY / (N - 1);
|
||||
int pos = 0;
|
||||
double sum = 0;
|
||||
float y = minY;
|
||||
float x = minX - stepX;
|
||||
int nRealPoints = 0;
|
||||
for (int i = 0; i < N; i++, y += stepY) {
|
||||
float xinc = (i & 1) == 0 ? stepX : -stepX;
|
||||
x += xinc;
|
||||
for (int j = 0; j < N; ++j, x += xinc, pos += 3) {
|
||||
float z = (float) f.eval(x, y);
|
||||
vertices[pos] = x;
|
||||
vertices[pos + 1] = y;
|
||||
vertices[pos + 2] = z;
|
||||
if (z == z) { // not NAN
|
||||
sum += z * z;
|
||||
++nRealPoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxAbs = (float) Math.sqrt(sum / nRealPoints);
|
||||
maxAbs *= .9f;
|
||||
maxAbs = Math.min(maxAbs, 15);
|
||||
maxAbs = Math.max(maxAbs, .001f);
|
||||
|
||||
final int limitColor = N * N * 4;
|
||||
for (int i = 0, j = 2; i < limitColor; i += 4, j += 3) {
|
||||
float z = vertices[j];
|
||||
if (z == z) {
|
||||
final float a = z / maxAbs;
|
||||
final float abs = a < 0 ? -a : a;
|
||||
colors[i] = floatToByte(a);
|
||||
colors[i + 1] = floatToByte(1 - abs * .3f);
|
||||
colors[i + 2] = floatToByte(-a);
|
||||
colors[i + 3] = (byte) 255;
|
||||
} else {
|
||||
vertices[j] = 0;
|
||||
z = 0;
|
||||
colors[i] = 0;
|
||||
colors[i + 1] = 0;
|
||||
colors[i + 2] = 0;
|
||||
colors[i + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
int base = N * N * 3;
|
||||
int colorBase = N * N * 4;
|
||||
int p = base;
|
||||
final int baseSize = 2;
|
||||
for (int i = -baseSize; i <= baseSize; i += 2 * baseSize) {
|
||||
vertices[p] = i;
|
||||
vertices[p + 1] = -baseSize;
|
||||
vertices[p + 2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = i;
|
||||
vertices[p + 1] = baseSize;
|
||||
vertices[p + 2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = -baseSize;
|
||||
vertices[p + 1] = i;
|
||||
vertices[p + 2] = 0;
|
||||
p += 3;
|
||||
vertices[p] = baseSize;
|
||||
vertices[p + 1] = i;
|
||||
vertices[p + 2] = 0;
|
||||
p += 3;
|
||||
}
|
||||
for (int i = colorBase; i < colorBase + 8 * 4; i += 4) {
|
||||
colors[i] = 0;
|
||||
colors[i + 1] = 0;
|
||||
colors[i + 2] = (byte) 255;
|
||||
colors[i + 3] = (byte) 255;
|
||||
}
|
||||
base += 8 * 3;
|
||||
colorBase += 8 * 4;
|
||||
|
||||
final float unit = 2;
|
||||
final float axis[] = {
|
||||
0, 0, 0,
|
||||
unit, 0, 0,
|
||||
0, 0, 0,
|
||||
0, unit, 0,
|
||||
0, 0, 0,
|
||||
0, 0, unit,
|
||||
};
|
||||
System.arraycopy(axis, 0, vertices, base, 6 * 3);
|
||||
for (int i = colorBase; i < colorBase + 6 * 4; i += 4) {
|
||||
colors[i] = (byte) 255;
|
||||
colors[i + 1] = (byte) 255;
|
||||
colors[i + 2] = (byte) 255;
|
||||
colors[i + 3] = (byte) 255;
|
||||
}
|
||||
base += 6 * 3;
|
||||
colorBase += 6 * 4;
|
||||
|
||||
p = base;
|
||||
final float tick = .03f;
|
||||
final float offset = .01f;
|
||||
for (int i = 1; i <= NTICK; ++i) {
|
||||
vertices[p] = i - tick;
|
||||
vertices[p + 1] = -offset;
|
||||
vertices[p + 2] = -offset;
|
||||
|
||||
vertices[p + 3] = i + tick;
|
||||
vertices[p + 4] = offset;
|
||||
vertices[p + 5] = offset;
|
||||
p += 6;
|
||||
|
||||
vertices[p] = -offset;
|
||||
vertices[p + 1] = i - tick;
|
||||
vertices[p + 2] = -offset;
|
||||
|
||||
vertices[p + 3] = offset;
|
||||
vertices[p + 4] = i + tick;
|
||||
vertices[p + 5] = offset;
|
||||
p += 6;
|
||||
|
||||
vertices[p] = -offset;
|
||||
vertices[p + 1] = -offset;
|
||||
vertices[p + 2] = i - tick;
|
||||
|
||||
vertices[p + 3] = offset;
|
||||
vertices[p + 4] = offset;
|
||||
vertices[p + 5] = i + tick;
|
||||
p += 6;
|
||||
|
||||
}
|
||||
for (int i = colorBase + NTICK * 6 * 4 - 1; i >= colorBase; --i) {
|
||||
colors[i] = (byte) 255;
|
||||
}
|
||||
|
||||
vertexBuf = buildBuffer(vertices);
|
||||
colorBuf = buildBuffer(colors);
|
||||
|
||||
if (useVBO) {
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexBuf.capacity() * 4, vertexBuf, GL11.GL_STATIC_DRAW);
|
||||
vertexBuf = null;
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, colorBuf.capacity(), colorBuf, GL11.GL_STATIC_DRAW);
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
|
||||
colorBuf = null;
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
|
||||
gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, verticeIdx.capacity() * 2, verticeIdx, GL11.GL_STATIC_DRAW);
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte floatToByte(float v) {
|
||||
return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int) (v * 255));
|
||||
}
|
||||
|
||||
public void draw(GL11 gl) {
|
||||
if (useVBO) {
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
|
||||
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
|
||||
gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, 0);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
|
||||
// gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N*N);
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
|
||||
gl.glDrawElements(GL10.GL_LINE_STRIP, N * N, GL10.GL_UNSIGNED_SHORT, 0);
|
||||
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
} else {
|
||||
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf);
|
||||
gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, colorBuf);
|
||||
gl.glDrawElements(GL10.GL_LINE_STRIP, N * N, GL10.GL_UNSIGNED_SHORT, verticeIdx);
|
||||
}
|
||||
final int N2 = N * N;
|
||||
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N2);
|
||||
gl.glDrawArrays(GL10.GL_LINES, N2, nVertex - N2);
|
||||
}
|
||||
|
||||
public boolean isUseHighQuality3d() {
|
||||
return useHighQuality3d;
|
||||
}
|
||||
}
|
259
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java
Executable file
259
android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java
Executable file
@@ -0,0 +1,259 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.opengl.Matrix;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.ZoomButtonsController;
|
||||
import org.javia.arity.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import java.util.List;
|
||||
|
||||
public class Graph3dView extends GLView implements GraphView {
|
||||
|
||||
private boolean useHighQuality3d = Build.VERSION.SDK_INT >= 5;
|
||||
|
||||
private float lastTouchX, lastTouchY;
|
||||
private TouchHandler touchHandler;
|
||||
private ZoomButtonsController zoomController = new ZoomButtonsController(this);
|
||||
private float zoomLevel = 1, targetZoom, zoomStep = 0, currentZoom;
|
||||
private FPS fps = new FPS();
|
||||
private Graph3d graph;
|
||||
|
||||
public Graph3dView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public Graph3dView(Context context) {
|
||||
super(context);
|
||||
touchHandler = new TouchHandler(this);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
startLooping();
|
||||
zoomController.setOnZoomListener(this);
|
||||
|
||||
Matrix.setIdentityM(matrix1, 0);
|
||||
Matrix.rotateM(matrix1, 0, -75, 1, 0, 0);
|
||||
}
|
||||
|
||||
public void onVisibilityChanged(boolean visible) {
|
||||
}
|
||||
|
||||
public void onZoom(boolean zoomIn) {
|
||||
boolean changed = false;
|
||||
if (zoomIn) {
|
||||
if (canZoomIn(zoomLevel)) {
|
||||
targetZoom = zoomLevel * .625f;
|
||||
zoomStep = -zoomLevel / 40;
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (canZoomOut(zoomLevel)) {
|
||||
targetZoom = zoomLevel * 1.6f;
|
||||
zoomStep = zoomLevel / 20;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
zoomController.setZoomInEnabled(canZoomIn(targetZoom));
|
||||
zoomController.setZoomOutEnabled(canZoomOut(targetZoom));
|
||||
if (!shouldRotate()) {
|
||||
setRotation(0, 0);
|
||||
}
|
||||
startLooping();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glDraw() {
|
||||
if ((zoomStep < 0 && zoomLevel > targetZoom) ||
|
||||
(zoomStep > 0 && zoomLevel < targetZoom)) {
|
||||
zoomLevel += zoomStep;
|
||||
} else if (zoomStep != 0) {
|
||||
zoomStep = 0;
|
||||
zoomLevel = targetZoom;
|
||||
isDirty = true;
|
||||
if (!shouldRotate()) {
|
||||
stopLooping();
|
||||
}
|
||||
}
|
||||
super.glDraw();
|
||||
}
|
||||
|
||||
private boolean canZoomIn(float zoom) {
|
||||
return zoom > .2f;
|
||||
}
|
||||
|
||||
private boolean canZoomOut(float zoom) {
|
||||
return zoom < 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
zoomController.setVisible(false);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void onTouchDown(float x, float y) {
|
||||
zoomController.setVisible(true);
|
||||
stopLooping();
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
|
||||
public void onTouchMove(float x, float y) {
|
||||
float deltaX = x - lastTouchX;
|
||||
float deltaY = y - lastTouchY;
|
||||
if (deltaX > 1 || deltaX < -1 || deltaY > 1 || deltaY < -1) {
|
||||
setRotation(deltaX, deltaY);
|
||||
glDraw();
|
||||
lastTouchX = x;
|
||||
lastTouchY = y;
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchUp(float x, float y) {
|
||||
float vx = touchHandler.velocityTracker.getXVelocity();
|
||||
float vy = touchHandler.velocityTracker.getYVelocity();
|
||||
// Calculator.log("velocity " + vx + ' ' + vy);
|
||||
setRotation(vx / 100, vy / 100);
|
||||
if (shouldRotate()) {
|
||||
startLooping();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchZoomDown(float x1, float y1, float x2, float y2) {
|
||||
|
||||
}
|
||||
|
||||
public void onTouchZoomMove(float x1, float y1, float x2, float y2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
private float[] matrix1 = new float[16], matrix2 = new float[16], matrix3 = new float[16];
|
||||
private float angleX, angleY;
|
||||
private boolean isDirty;
|
||||
private Function function;
|
||||
private static final float DISTANCE = 15f;
|
||||
|
||||
void setRotation(float x, float y) {
|
||||
angleX = x;
|
||||
angleY = y;
|
||||
}
|
||||
|
||||
boolean shouldRotate() {
|
||||
final float limit = .5f;
|
||||
return angleX < -limit || angleX > limit || angleY < -limit || angleY > limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(@NotNull FunctionViewDef functionViewDef) {
|
||||
}
|
||||
|
||||
public void setFunctionPlotDefs(@NotNull List<FunctionPlotDef> functionPlotDefs) {
|
||||
if (functionPlotDefs.size() > 0) {
|
||||
function = functionPlotDefs.get(0).getFunction();
|
||||
} else {
|
||||
function = null;
|
||||
}
|
||||
zoomLevel = 1;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl, int width, int height) {
|
||||
gl.glDisable(GL10.GL_DITHER);
|
||||
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
|
||||
gl.glClearColor(0, 0, 0, 1);
|
||||
gl.glShadeModel(useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT);
|
||||
gl.glDisable(GL10.GL_LIGHTING);
|
||||
graph = new Graph3d((GL11) gl, useHighQuality3d);
|
||||
isDirty = true;
|
||||
angleX = .5f;
|
||||
angleY = 0;
|
||||
|
||||
gl.glViewport(0, 0, width, height);
|
||||
initFrustum(gl, DISTANCE * zoomLevel);
|
||||
currentZoom = zoomLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl10) {
|
||||
GL11 gl = (GL11) gl10;
|
||||
if (currentZoom != zoomLevel) {
|
||||
initFrustum(gl, DISTANCE * zoomLevel);
|
||||
currentZoom = zoomLevel;
|
||||
}
|
||||
if (isDirty) {
|
||||
graph.update(gl, function, zoomLevel);
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
/*if (fps.incFrame()) {
|
||||
Calculator.log("f/s " + fps.getValue());
|
||||
}*/
|
||||
|
||||
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
|
||||
gl.glMatrixMode(GL10.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
gl.glTranslatef(0, 0, -DISTANCE * zoomLevel);
|
||||
|
||||
Matrix.setIdentityM(matrix2, 0);
|
||||
float ax = Math.abs(angleX);
|
||||
float ay = Math.abs(angleY);
|
||||
if (ay * 3 < ax) {
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
} else if (ax * 3 < ay) {
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
} else {
|
||||
if (ax > ay) {
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
} else {
|
||||
Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
|
||||
Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
Matrix.multiplyMM(matrix3, 0, matrix2, 0, matrix1, 0);
|
||||
gl.glMultMatrixf(matrix3, 0);
|
||||
System.arraycopy(matrix3, 0, matrix1, 0, 16);
|
||||
graph.draw(gl);
|
||||
}
|
||||
|
||||
private void initFrustum(GL10 gl, float distance) {
|
||||
gl.glMatrixMode(GL10.GL_PROJECTION);
|
||||
gl.glLoadIdentity();
|
||||
float near = distance * (1 / 3f);
|
||||
float far = distance * 3f;
|
||||
float dimen = near / 5f;
|
||||
float h = dimen * height / width;
|
||||
gl.glFrustumf(-dimen, dimen, -h, h, near, far);
|
||||
gl.glMatrixMode(GL10.GL_MODELVIEW);
|
||||
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
|
||||
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
|
||||
}
|
||||
|
||||
private void printMatrix(float[] m, String name) {
|
||||
StringBuffer b = new StringBuffer();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
b.append(m[i]).append(' ');
|
||||
}
|
||||
//Calculator.log(name + ' ' + b.toString());
|
||||
}
|
||||
}
|
170
android-app/src/main/java/org/solovyev/android/calculator/plot/GraphData.java
Executable file
170
android-app/src/main/java/org/solovyev/android/calculator/plot/GraphData.java
Executable file
@@ -0,0 +1,170 @@
|
||||
// Copyright (C) 2009 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class GraphData {
|
||||
|
||||
private int size = 0;
|
||||
|
||||
private int allocatedSize = 4;
|
||||
private float[] xs = new float[allocatedSize];
|
||||
private float[] ys = new float[allocatedSize];
|
||||
|
||||
private GraphData() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static GraphData newEmptyInstance() {
|
||||
return new GraphData();
|
||||
}
|
||||
|
||||
void swap(@NotNull GraphData that) {
|
||||
float savedXs[] = that.xs;
|
||||
float savedYs[] = that.ys;
|
||||
int savedSize = that.size;
|
||||
int savedAllocatedSize = that.allocatedSize;
|
||||
|
||||
that.xs = this.xs;
|
||||
that.ys = this.ys;
|
||||
that.size = this.size;
|
||||
that.allocatedSize = this.allocatedSize;
|
||||
|
||||
this.xs = savedXs;
|
||||
this.ys = savedYs;
|
||||
this.size = savedSize;
|
||||
this.allocatedSize = savedAllocatedSize;
|
||||
}
|
||||
|
||||
void push(float x, float y) {
|
||||
if (size >= allocatedSize) {
|
||||
makeSpace(size + 1);
|
||||
}
|
||||
|
||||
xs[size] = x;
|
||||
ys[size] = y;
|
||||
++size;
|
||||
}
|
||||
|
||||
private void makeSpace(int spaceSize) {
|
||||
int oldAllocatedSize = allocatedSize;
|
||||
while (spaceSize > allocatedSize) {
|
||||
allocatedSize += allocatedSize;
|
||||
}
|
||||
|
||||
if (oldAllocatedSize != allocatedSize) {
|
||||
float[] a = new float[allocatedSize];
|
||||
System.arraycopy(xs, 0, a, 0, this.size);
|
||||
xs = a;
|
||||
a = new float[allocatedSize];
|
||||
System.arraycopy(ys, 0, a, 0, this.size);
|
||||
ys = a;
|
||||
}
|
||||
}
|
||||
|
||||
float topX() {
|
||||
return xs[size - 1];
|
||||
}
|
||||
|
||||
float topY() {
|
||||
return ys[size - 1];
|
||||
}
|
||||
|
||||
float firstX() {
|
||||
return xs[0];
|
||||
}
|
||||
|
||||
float firstY() {
|
||||
return ys[0];
|
||||
}
|
||||
|
||||
void pop() {
|
||||
--size;
|
||||
}
|
||||
|
||||
boolean empty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void eraseBefore(float x) {
|
||||
int pos = 0;
|
||||
while (pos < size && xs[pos] < x) {
|
||||
++pos;
|
||||
}
|
||||
--pos;
|
||||
if (pos > 0) {
|
||||
size -= pos;
|
||||
System.arraycopy(xs, pos, xs, 0, size);
|
||||
System.arraycopy(ys, pos, ys, 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
void eraseAfter(float x) {
|
||||
int pos = size - 1;
|
||||
while (pos >= 0 && x < xs[pos]) {
|
||||
--pos;
|
||||
}
|
||||
++pos;
|
||||
if (pos < size - 1) {
|
||||
size = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int findPosAfter(float x, float y) {
|
||||
int pos = 0;
|
||||
while (pos < size && xs[pos] <= x) {
|
||||
++pos;
|
||||
}
|
||||
if (Float.isNaN(y)) {
|
||||
while (pos < size && ys[pos] != ys[pos]) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// Calculator.log("pos " + pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
void append(GraphData d) {
|
||||
makeSpace(size + d.size);
|
||||
int pos = d.findPosAfter(xs[size - 1], ys[size - 1]);
|
||||
/*
|
||||
while (pos < d.size && d.xs[pos] <= last) {
|
||||
++pos;
|
||||
}
|
||||
if (last != last) {
|
||||
while (pos < d.size && d.ys[pos] != d.ys[pos]) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
*/
|
||||
System.arraycopy(d.xs, pos, xs, size, d.size - pos);
|
||||
System.arraycopy(d.ys, pos, ys, size, d.size - pos);
|
||||
size += d.size - pos;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(size).append(": ");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
b.append(xs[i]).append(", ");
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public float[] getXs() {
|
||||
return xs;
|
||||
}
|
||||
|
||||
public float[] getYs() {
|
||||
return ys;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.widget.ZoomButtonsController;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GraphView extends ZoomButtonsController.OnZoomListener, TouchHandler.TouchHandlerInterface {
|
||||
|
||||
static final String SCREENSHOT_DIR = "/screenshots";
|
||||
|
||||
public void init(@NotNull FunctionViewDef functionViewDef);
|
||||
|
||||
public void setFunctionPlotDefs(@NotNull List<FunctionPlotDef> functionPlotDefs);
|
||||
|
||||
public void onPause();
|
||||
public void onResume();
|
||||
|
||||
public String captureScreenshot();
|
||||
|
||||
/*
|
||||
**********************************************************************
|
||||
*
|
||||
* CUSTOMIZATION
|
||||
*
|
||||
**********************************************************************
|
||||
*/
|
||||
|
||||
/* void setBgColor(int color);
|
||||
void setAxisColor(int color);*/
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/13
|
||||
* Time: 8:06 PM
|
||||
*/
|
||||
public class GraphViewHelper {
|
||||
|
||||
@NotNull
|
||||
private FunctionViewDef functionViewDef = FunctionViewDef.newDefaultInstance();
|
||||
|
||||
@NotNull
|
||||
private List<FunctionPlotDef> functionPlotDefs = Collections.emptyList();
|
||||
|
||||
private GraphViewHelper() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static GraphViewHelper newDefaultInstance() {
|
||||
return new GraphViewHelper();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static GraphViewHelper newInstance(@NotNull FunctionViewDef functionViewDef,
|
||||
@NotNull List<FunctionPlotDef> functionPlotDefs) {
|
||||
final GraphViewHelper result = new GraphViewHelper();
|
||||
|
||||
result.functionViewDef = functionViewDef;
|
||||
result.functionPlotDefs = Collections.unmodifiableList(functionPlotDefs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GraphViewHelper copy(@NotNull List<FunctionPlotDef> newFunctionPlotDefs) {
|
||||
final GraphViewHelper result = new GraphViewHelper();
|
||||
|
||||
result.functionViewDef = functionViewDef;
|
||||
result.functionPlotDefs = Collections.unmodifiableList(newFunctionPlotDefs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<FunctionPlotDef> getFunctionPlotDefs() {
|
||||
return functionPlotDefs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FunctionViewDef getFunctionViewDef() {
|
||||
return functionViewDef;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class MotionEventWrap {
|
||||
private static final boolean IS_API_5 = Build.VERSION.SDK_INT >= 5;
|
||||
|
||||
static int getPointerCount(MotionEvent event) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getPointerCount(event) : 1;
|
||||
}
|
||||
|
||||
static float getX(MotionEvent event, int idx) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getX(event, idx) : 0;
|
||||
}
|
||||
|
||||
static float getY(MotionEvent event, int idx) {
|
||||
return IS_API_5 ? MotionEventWrapNew.getX(event, idx) : 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class MotionEventWrapNew {
|
||||
static int getPointerCount(MotionEvent event) {
|
||||
return event.getPointerCount();
|
||||
}
|
||||
|
||||
static float getX(MotionEvent event, int idx) {
|
||||
return event.getX(idx);
|
||||
}
|
||||
|
||||
static float getY(MotionEvent event, int idx) {
|
||||
return event.getY(idx);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2009-2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
|
||||
class TouchHandler {
|
||||
static interface TouchHandlerInterface {
|
||||
void onTouchDown(float x, float y);
|
||||
void onTouchMove(float x, float y);
|
||||
void onTouchUp(float x, float y);
|
||||
void onTouchZoomDown(float x1, float y1, float x2, float y2);
|
||||
void onTouchZoomMove(float x1, float y1, float x2, float y2);
|
||||
}
|
||||
|
||||
VelocityTracker velocityTracker = VelocityTracker.obtain();
|
||||
|
||||
private boolean isAfterZoom;
|
||||
private TouchHandlerInterface listener;
|
||||
|
||||
TouchHandler(TouchHandlerInterface listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Calculator.log("touch " + event + ' ' + event.getPointerCount() + event.getPointerId(0));
|
||||
|
||||
int fullAction = event.getAction();
|
||||
int action = fullAction & MotionEvent.ACTION_MASK;
|
||||
int pointer = (fullAction & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
int nPoints = MotionEventWrap.getPointerCount(event);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
isAfterZoom = false;
|
||||
velocityTracker.clear();
|
||||
velocityTracker.addMovement(event);
|
||||
listener.onTouchDown(x, y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (nPoints == 1) {
|
||||
if (isAfterZoom) {
|
||||
velocityTracker.clear();
|
||||
listener.onTouchDown(x, y);
|
||||
isAfterZoom = false;
|
||||
}
|
||||
velocityTracker.addMovement(event);
|
||||
listener.onTouchMove(x, y);
|
||||
} else if (nPoints == 2) {
|
||||
listener.onTouchZoomMove(x, y, MotionEventWrap.getX(event, 1), MotionEventWrap.getY(event, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
velocityTracker.addMovement(event);
|
||||
velocityTracker.computeCurrentVelocity(1000);
|
||||
listener.onTouchUp(x, y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
if (nPoints == 2) {
|
||||
listener.onTouchZoomDown(x, y, MotionEventWrap.getX(event, 1), MotionEventWrap.getY(event, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
if (nPoints == 2) {
|
||||
isAfterZoom = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2010 Mihai Preda
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
class ZoomTracker {
|
||||
private float sx1, sy1, sx2, sy2;
|
||||
private float initialDist;
|
||||
private float initialValue;
|
||||
|
||||
float value;
|
||||
float moveX, moveY;
|
||||
|
||||
void start(float value, float x1, float y1, float x2, float y2) {
|
||||
sx1 = x1;
|
||||
sy1 = y1;
|
||||
sx2 = x2;
|
||||
sy2 = y2;
|
||||
initialDist = distance(x1, y1, x2, y2);
|
||||
initialValue = value;
|
||||
}
|
||||
|
||||
boolean update(float x1, float y1, float x2, float y2) {
|
||||
final float LIMIT = 1.5f;
|
||||
if (Math.abs(x1 - sx1) < LIMIT && Math.abs(y1 - sy1) < LIMIT &&
|
||||
Math.abs(x2 - sx2) < LIMIT && Math.abs(y2 - sy2) < LIMIT) {
|
||||
return false;
|
||||
}
|
||||
moveX = common(x1, sx1, x2, sx2);
|
||||
moveY = common(y1, sy1, y2, sy2);
|
||||
float dist = distance(x1, y1, x2, y2);
|
||||
value = initialDist / dist * initialValue;
|
||||
sx1 = x1;
|
||||
sx2 = x2;
|
||||
sy1 = y1;
|
||||
sy2 = y2;
|
||||
return true;
|
||||
}
|
||||
|
||||
private float distance(float x1, float y1, float x2, float y2) {
|
||||
final float dx = x1-x2;
|
||||
final float dy = y1-y2;
|
||||
// return (float) Math.sqrt(dx*dx+dy*dy);
|
||||
return Math.max(dx*dx, dy*dy);
|
||||
}
|
||||
|
||||
private float common(float x1, float sx1, float x2, float sx2) {
|
||||
float dx1 = x1 - sx1;
|
||||
float dx2 = x2 - sx2;
|
||||
return (dx1 < 0 && dx2 < 0) ? Math.max(dx1, dx2) :
|
||||
(dx1 > 0 && dx2 > 0) ? Math.min(dx1, dx2):
|
||||
0;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user