new plotter

This commit is contained in:
Sergey Solovyev 2013-01-05 22:56:52 +04:00
parent 5f7ee1e64e
commit 2c0803da74
24 changed files with 932 additions and 655 deletions

View File

@ -5,8 +5,16 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.Log;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/** /**
* User: Solovyev_S * User: Solovyev_S
* Date: 03.10.12 * Date: 03.10.12
@ -55,4 +63,43 @@ public final class AndroidUtils2 {
int componentEnabledSetting = pm.getComponentEnabledSetting(new ComponentName(context, componentClass)); int componentEnabledSetting = pm.getComponentEnabledSetting(new ComponentName(context, componentClass));
return componentEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || componentEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; return componentEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || componentEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
} }
public static String saveBitmap(@NotNull Bitmap bitmap,
@NotNull String path,
@NotNull String fileName) {
final File sdcardPath = Environment.getExternalStorageDirectory();
final File filePath = new File(sdcardPath, path);
filePath.mkdirs();
final String fullFileName = fileName + "_" + System.currentTimeMillis() + ".png";
final File file = new File(path, fullFileName);
if (!file.exists()) {
final String name = file.getAbsolutePath();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(name);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
} catch (FileNotFoundException e) {
Log.e("AndroidUtils", e.getMessage(), e);
} catch (IOException e) {
Log.e("AndroidUtils", e.getMessage(), e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
Log.e("AndroidUtils", e.getMessage(), e);
}
}
}
return name;
}
return null;
}
} }

View File

@ -3,7 +3,6 @@
package arity.calculator; package arity.calculator;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
@ -18,8 +17,9 @@ import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import org.javia.arity.*; import org.javia.arity.*;
import org.javia.arity.Util;
import org.solovyev.android.calculator.Locator; import org.solovyev.android.calculator.Locator;
import org.solovyev.android.calculator.plot.Graph2dView;
import org.solovyev.android.calculator.plot.Graph3dView;
import java.util.ArrayList; import java.util.ArrayList;
@ -183,9 +183,6 @@ public class Calculator extends Activity implements TextWatcher,
//OnClickListener //OnClickListener
public void onClick(View target) { public void onClick(View target) {
if (target == graphView || target == graph3dView) {
startActivity(new Intent(this, ShowGraph.class));
}
} }
// OnItemClickListener // OnItemClickListener
@ -323,7 +320,7 @@ public class Calculator extends Activity implements TextWatcher,
showGraph(null); showGraph(null);
} }
} else { } else {
graphView.setFunctions(auxFuncs); //graphView.setFunctionPlotDefs(auxFuncs);
if (graphView.getVisibility() != View.VISIBLE) { if (graphView.getVisibility() != View.VISIBLE) {
if (isAlphaVisible) { if (isAlphaVisible) {
isAlphaVisible = false; isAlphaVisible = false;
@ -376,7 +373,7 @@ public class Calculator extends Activity implements TextWatcher,
} else { } else {
// graphedFunction = f; // graphedFunction = f;
if (f.arity() == 1) { if (f.arity() == 1) {
graphView.setFunction(f); //graphView.setFunctionPlotDefs(Arrays.asList(f));
if (graphView.getVisibility() != View.VISIBLE) { if (graphView.getVisibility() != View.VISIBLE) {
if (isAlphaVisible) { if (isAlphaVisible) {
isAlphaVisible = false; isAlphaVisible = false;
@ -389,7 +386,7 @@ public class Calculator extends Activity implements TextWatcher,
graphView.setVisibility(View.VISIBLE); graphView.setVisibility(View.VISIBLE);
} }
} else { } else {
graph3dView.setFunction(f); //graph3dView.setFunctionPlotDefs(Arrays.asList(f));
if (graph3dView.getVisibility() != View.VISIBLE) { if (graph3dView.getVisibility() != View.VISIBLE) {
if (isAlphaVisible) { if (isAlphaVisible) {
isAlphaVisible = false; isAlphaVisible = false;

View File

@ -1,85 +0,0 @@
// Copyright (C) 2009 Mihai Preda
package arity.calculator;
import android.text.Editable;
import android.text.SpannableStringBuilder;
class CalculatorEditable extends SpannableStringBuilder {
static class Factory extends Editable.Factory {
public Editable newEditable(CharSequence source) {
return new CalculatorEditable(source);
}
}
static final char MINUS = '\u2212', TIMES = '\u00d7', DIV = '\u00f7';
private boolean isRec;
public CalculatorEditable(CharSequence source) {
super(source);
}
public SpannableStringBuilder replace(int start, int end, CharSequence buf, int bufStart, int bufEnd) {
if (isRec || bufEnd - bufStart != 1) {
return super.replace(start, end, buf, bufStart, bufEnd);
} else {
isRec = true;
try {
char c = buf.charAt(bufStart);
return internalReplace(start, end, c);
} finally {
isRec = false;
}
}
}
private boolean isOperator(char c) {
return "\u2212\u00d7\u00f7+-/*=^,".indexOf(c) != -1;
}
private SpannableStringBuilder internalReplace(int start, int end, char c) {
switch (c) {
case '-': c = MINUS; break;
case '*': c = TIMES; break;
case '/': c = DIV; break;
}
if (c == '.') {
int p = start - 1;
while (p >= 0 && Character.isDigit(charAt(p))) {
--p;
}
if (p >= 0 && charAt(p) == '.') {
return super.replace(start, end, "");
}
}
char prevChar = start > 0 ? charAt(start-1) : '\0';
if (c == MINUS && prevChar == MINUS) {
return super.replace(start, end, "");
}
if (isOperator(c)) {
while (isOperator(prevChar) &&
(c != MINUS || prevChar == '+')) {
--start;
prevChar = start > 0 ? charAt(start-1) : '\0';
}
}
//don't allow leading operator + * /
if (start == 0 && isOperator(c)) { // && c != MINUS
return super.replace(start, end, "ans" + c);
}
//allow at most one '='
if (c == '=') {
for (int pos = 0; pos < start; ++pos) {
if (charAt(pos) == '=') {
return super.replace(start, end, "");
}
}
}
return super.replace(start, end, "" + c);
}
}

View File

@ -1,145 +0,0 @@
// Copyright (C) 2009 Mihai Preda
package arity.calculator;
class Data {
float[] xs = new float[4];
float[] ys = new float[4];
int size = 0;
int allocSize = 4;
void swap(Data o) {
float savex[] = o.xs;
float savey[] = o.ys;
int ssize = o.size;
int salloc = o.allocSize;
o.xs = xs;
o.ys = ys;
o.size = size;
o.allocSize = allocSize;
xs = savex;
ys = savey;
size = ssize;
allocSize = salloc;
}
void push(float x, float y) {
if (size >= allocSize) {
makeSpace(size+1);
}
// Calculator.log("push " + size + ' ' + x + ' ' + y);
xs[size] = x;
ys[size] = y;
++size;
}
private void makeSpace(int sizeNeeded) {
int oldAllocSize = allocSize;
while (sizeNeeded > allocSize) {
allocSize += allocSize;
}
if (oldAllocSize != allocSize) {
float[] a = new float[allocSize];
System.arraycopy(xs, 0, a, 0, size);
xs = a;
a = new float[allocSize];
System.arraycopy(ys, 0, a, 0, 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(Data 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();
}
}

View File

@ -1,16 +0,0 @@
// Copyright (C) 2009 Mihai Preda
package arity.calculator;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class Help extends Activity {
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
WebView view = new WebView(this);
setContentView(view);
view.loadUrl("file:///android_asset/help.html");
}
}

View File

@ -1,69 +0,0 @@
// Copyright (C) 2009 Mihai Preda
package arity.calculator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import org.javia.arity.Function;
import java.util.ArrayList;
public class ShowGraph extends Activity {
private GraphView view;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
ArrayList<Function> funcs = Calculator.graphedFunction;
if (funcs == null) {
finish();
return;
}
int size = funcs.size();
if (size == 1) {
Function f = funcs.get(0);
view = f.arity() == 1 ? new Graph2dView(this) : new Graph3dView(this);
view.setFunction(f);
} else {
view = new Graph2dView(this);
((Graph2dView) view).setFunctions(funcs);
}
setContentView((View) view);
}
protected void onPause() {
super.onPause();
view.onPause();
}
protected void onResume() {
super.onResume();
view.onResume();
}
/* public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
(new MenuInflater(this)).inflate(R.menu.graph, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.capture_screenshot:
String fileName = view.captureScreenshot();
if (fileName != null) {
Toast.makeText(this, "screenshot saved as \n" + fileName, Toast.LENGTH_LONG).show();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.fromFile(new File(fileName)), "image/png");
startActivity(i);
}
break;
default:
return false;
}
return true;
}*/
}

View File

@ -1,70 +0,0 @@
// Copyright (C) 2009-2010 Mihai Preda
package arity.calculator;
import android.graphics.Bitmap;
import android.os.Environment;
import android.os.Build;
import java.nio.ShortBuffer;
import java.io.*;
class Util {
public static final int SDK_VERSION = getSdkVersion();
private static int getSdkVersion() {
try {
return Integer.parseInt(Build.VERSION.SDK);
} catch (NumberFormatException e) {
Calculator.log("invalid SDK " + Build.VERSION.SDK);
return 3;
}
}
static String saveBitmap(Bitmap bitmap, String dir, String baseName) {
try {
File sdcard = Environment.getExternalStorageDirectory();
File pictureDir = new File(sdcard, dir);
pictureDir.mkdirs();
File f = null;
for (int i = 1; i < 200; ++i) {
String name = baseName + i + ".png";
f = new File(pictureDir, name);
if (!f.exists()) {
break;
}
}
if (!f.exists()) {
String name = f.getAbsolutePath();
FileOutputStream fos = new FileOutputStream(name);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return name;
}
} catch (Exception e) {
Calculator.log("exception saving screenshot: " + e);
} finally {
/*
if (fos != null) {
fos.close();
}
*/
}
return null;
}
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);
}
}

View File

@ -1,16 +1,16 @@
package org.solovyev.android.calculator.plot; 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.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import arity.calculator.Graph2dView;
import arity.calculator.Graph3dView;
import arity.calculator.GraphView;
import jscl.math.Generic; import jscl.math.Generic;
import jscl.math.function.Constant; import jscl.math.function.Constant;
import org.javia.arity.Complex;
import org.javia.arity.Function; import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorPreferences;
import org.solovyev.android.calculator.R; import org.solovyev.android.calculator.R;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,6 +39,12 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment
@Override @Override
protected void createGraphicalView(@NotNull View root, @NotNull PreparedInput preparedInput) { 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 // remove old
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout); 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 int arity = yVariable == null ? 1 : 2;
final List<Function> functions = new ArrayList<Function>(); final List<FunctionPlotDef> functions = new ArrayList<FunctionPlotDef>();
functions.add(new Function() {
@Override
public int arity() {
return arity;
}
@Override functions.add(FunctionPlotDef.newInstance(new RealArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(realLineColor.getColor(), FunctionLineStyle.solid, 3f)));
public double eval(double x) {
return PlotUtils.calculatorExpression(expression, xVariable, x).realPart();
}
@Override if (arity == 1) {
public double eval(double x, double y) { functions.add(FunctionPlotDef.newInstance(new ImaginaryArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(imagLineColor.getColor(), FunctionLineStyle.solid, 3f)));
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);
} }
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); graphContainer.addView((View) graphView);
} else { } else {
onError(); onError();
@ -128,4 +117,79 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment
this.graphView.onPause(); 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();
}
}
} }

View File

@ -1,4 +1,4 @@
package arity.calculator; package org.solovyev.android.calculator.plot;
class FPS { class FPS {
private int drawCnt; private int drawCnt;

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -1,6 +1,6 @@
// Copyright (C) 2009 Mihai Preda // Copyright (C) 2009 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -9,12 +9,14 @@ import android.os.Message;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import org.solovyev.android.AndroidUtils2;
import javax.microedition.khronos.egl.*; import javax.microedition.khronos.egl.*;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.ShortBuffer;
abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
private boolean hasSurface; private boolean hasSurface;
@ -33,8 +35,8 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
public String captureScreenshot() { public String captureScreenshot() {
Bitmap bitmap = getRawPixels(gl, width, height); Bitmap bitmap = getRawPixels(gl, width, height);
Util.bitmapBGRtoRGB(bitmap, width, height); bitmapBGRtoRGB(bitmap, width, height);
return Util.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator"); return AndroidUtils2.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
} }
private static Bitmap getRawPixels(GL10 gl, int width, int height) { private static Bitmap getRawPixels(GL10 gl, int width, int height) {
@ -73,7 +75,6 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
} }
public void onResume() { public void onResume() {
Calculator.log("onResume " + this);
paused = false; paused = false;
if (hasSurface) { if (hasSurface) {
initGL(); initGL();
@ -81,7 +82,6 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
} }
public void onPause() { public void onPause() {
Calculator.log("onPause " + this);
deinitGL(); deinitGL();
} }
@ -125,10 +125,8 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
if (hasSurface && !paused) { if (hasSurface && !paused) {
onDrawFrame(gl); onDrawFrame(gl);
if (!egl.eglSwapBuffers(display, surface)) { if (!egl.eglSwapBuffers(display, surface)) {
Calculator.log("swapBuffers error " + egl.eglGetError());
} }
if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) { if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) {
Calculator.log("egl context lost " + this);
paused = true; paused = true;
} }
if (mIsLooping) { if (mIsLooping) {
@ -138,11 +136,9 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
} }
public void surfaceCreated(SurfaceHolder holder) { public void surfaceCreated(SurfaceHolder holder) {
Calculator.log("surfaceCreated " + this);
} }
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Calculator.log("surfaceChanged " + format + ' ' + this);
this.width = width; this.width = width;
this.height = height; this.height = height;
boolean doInit = !hasSurface && !paused; boolean doInit = !hasSurface && !paused;
@ -153,14 +149,12 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
} }
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
Calculator.log("surfaceDestroyed " + this);
hasSurface = false; hasSurface = false;
deinitGL(); deinitGL();
} }
public void startLooping() { public void startLooping() {
if (!mIsLooping) { if (!mIsLooping) {
Calculator.log("start looping");
mIsLooping = true; mIsLooping = true;
glDraw(); glDraw();
} }
@ -168,7 +162,6 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
public void stopLooping() { public void stopLooping() {
if (mIsLooping) { if (mIsLooping) {
Calculator.log("stop looping");
mIsLooping = false; mIsLooping = false;
} }
} }
@ -180,4 +173,18 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback {
public void requestDraw() { public void requestDraw() {
handler.sendEmptyMessage(1); 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);
}
} }

View File

@ -1,6 +1,7 @@
// Copyright (C) 2009-2010 Mihai Preda // Copyright (C) 2009-2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.content.Context; import android.content.Context;
import android.graphics.*; import android.graphics.*;
@ -10,22 +11,29 @@ import android.view.View;
import android.widget.Scroller; import android.widget.Scroller;
import android.widget.ZoomButtonsController; import android.widget.ZoomButtonsController;
import org.javia.arity.Function; import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.AndroidUtils2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Graph2dView extends View implements public class Graph2dView extends View implements GraphView {
GraphView,
ZoomButtonsController.OnZoomListener,
TouchHandler.TouchHandlerInterface {
private int width, height; private int width, height;
private Matrix matrix = new Matrix(); private Matrix matrix = new Matrix();
private Paint paint = new Paint(), textPaint = new Paint(), fillPaint = new Paint(); private Paint paint = new Paint(), textPaint = new Paint(), fillPaint = new Paint();
private ArrayList<Function> funcs = new ArrayList<Function>();
private Data next = new Data(), endGraph = new Data(); @NotNull
private Data graphs[] = {new Data(), new Data(), new Data(), new Data(), new Data()}; private GraphViewHelper graphViewHelper = GraphViewHelper.newDefaultInstance();
private static final int GRAPHS_SIZE = 5;
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 gwidth = 8;
private float currentX, currentY; private float currentX, currentY;
private float lastMinX; private float lastMinX;
@ -36,13 +44,6 @@ public class Graph2dView extends View implements
private TouchHandler touchHandler; private TouchHandler touchHandler;
private float lastTouchX, lastTouchY; private float lastTouchX, lastTouchY;
private static final int
COL_AXIS = 0xff00a000,
COL_GRID = 0xff004000,
COL_TEXT = 0xff00ff00;
private static final int COL_GRAPH[] = {0xffffffff, 0xff00ffff, 0xffffff00, 0xffff00ff, 0xff80ff80};
private static final int private static final int
COL_ZOOM = 0x40ffffff, COL_ZOOM = 0x40ffffff,
COL_ZOOM_TEXT1 = 0xd0ffffff, COL_ZOOM_TEXT1 = 0xd0ffffff,
@ -70,33 +71,35 @@ public class Graph2dView extends View implements
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap); Canvas canvas = new Canvas(bitmap);
onDraw(canvas); onDraw(canvas);
return Util.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator"); return AndroidUtils2.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator");
} }
private void clearAllGraph() { private void clearAllGraphs() {
for (int i = 0; i < GRAPHS_SIZE; ++i) { for (GraphData graph : graphs) {
graphs[i].clear(); graph.clear();
}
while ( graphViewHelper.getFunctionPlotDefs().size() > graphs.size() ) {
graphs.add(GraphData.newEmptyInstance());
} }
} }
public void setFunctions(List<Function> fs) { @Override
funcs.clear(); public void init(@NotNull FunctionViewDef functionViewDef) {
for (Function f : fs) { this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.<FunctionPlotDef>emptyList());
int arity = f.arity(); }
if (arity == 0 || arity == 1) {
funcs.add(f); 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!");
} }
} }
clearAllGraph();
invalidate();
}
public void setFunction(Function f) { this.graphViewHelper = this.graphViewHelper.copy(functionPlotDefs);
funcs.clear(); clearAllGraphs();
if (f != null) {
funcs.add(f);
}
clearAllGraph();
invalidate(); invalidate();
} }
@ -133,12 +136,12 @@ public class Graph2dView extends View implements
protected void onSizeChanged(int w, int h, int ow, int oh) { protected void onSizeChanged(int w, int h, int ow, int oh) {
width = w; width = w;
height = h; height = h;
clearAllGraph(); clearAllGraphs();
// points = new float[w+w]; // points = new float[w+w];
} }
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
if (funcs.size() == 0) { if (graphViewHelper.getFunctionPlotDefs().size() == 0) {
return; return;
} }
if (scroller.computeScrollOffset()) { if (scroller.computeScrollOffset()) {
@ -184,9 +187,14 @@ public class Graph2dView extends View implements
return up * up / (dx * dx + dy * dy); return up * up / (dx * dx + dy * dy);
} }
private void computeGraph(Function f, float minX, float maxX, float minY, float maxY, Data graph) { private void computeGraph(@NotNull Function function,
if (f.arity() == 0) { float minX,
float v = (float) f.eval(); float maxX,
float minY,
float maxY,
@NotNull GraphData graph) {
if (function.arity() == 0) {
float v = (float) function.eval();
if (v < -10000f) { if (v < -10000f) {
v = -10000f; v = -10000f;
} }
@ -217,7 +225,7 @@ public class Graph2dView extends View implements
} }
} }
if (graph.empty()) { if (graph.empty()) {
graph.push(minX, eval(f, minX)); graph.push(minX, eval(function, minX));
} }
float leftX, leftY; float leftX, leftY;
float rightX = graph.topX(), rightY = graph.topY(); float rightX = graph.topX(), rightY = graph.topY();
@ -230,7 +238,7 @@ public class Graph2dView extends View implements
} }
if (next.empty()) { if (next.empty()) {
float x = leftX + maxStep; float x = leftX + maxStep;
next.push(x, eval(f, x)); next.push(x, eval(function, x));
++nEval; ++nEval;
} }
rightX = next.topX(); rightX = next.topX();
@ -243,7 +251,7 @@ public class Graph2dView extends View implements
float dx = rightX - leftX; float dx = rightX - leftX;
float middleX = (leftX + rightX) / 2; float middleX = (leftX + rightX) / 2;
float middleY = eval(f, middleX); float middleY = eval(function, middleX);
++nEval; ++nEval;
boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY); boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY);
if (dx < minStep) { if (dx < minStep) {
@ -289,30 +297,32 @@ public class Graph2dView extends View implements
endGraph.clear(); endGraph.clear();
} }
private static Path path = new Path(); 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();
private Path graphToPath(Data graph) {
boolean first = true;
int size = graph.size;
float[] xs = graph.xs;
float[] ys = graph.ys;
path.rewind(); path.rewind();
for (int i = 0; i < size; ++i) {
float y = ys[i]; boolean newCurve = true;
float x = xs[i];
// Calculator.log("path " + x + ' ' + y); for (int i = 0; i < size; i++) {
if (y == y) { // !NaN
if (first) { final float y = ys[i];
final float x = xs[i];
if (y != y) {
newCurve = true;
} else { // !NaN
if (newCurve) {
path.moveTo(x, y); path.moveTo(x, y);
first = false; newCurve = false;
} else { } else {
path.lineTo(x, y); path.lineTo(x, y);
} }
} else {
first = true;
} }
} }
return path;
} }
private static final float NTICKS = 15; private static final float NTICKS = 15;
@ -382,10 +392,10 @@ public class Graph2dView extends View implements
float halfw = ywidth / 2; float halfw = ywidth / 2;
boundMinY = minY - halfw; boundMinY = minY - halfw;
boundMaxY = maxY + halfw; boundMaxY = maxY + halfw;
clearAllGraph(); clearAllGraphs();
} }
canvas.drawColor(0xff000000); canvas.drawColor(graphViewHelper.getFunctionViewDef().getBackgroundColor());
paint.setStrokeWidth(0); paint.setStrokeWidth(0);
paint.setAntiAlias(false); paint.setAntiAlias(false);
@ -412,34 +422,46 @@ public class Graph2dView extends View implements
final float tickSize = 3; final float tickSize = 3;
final float y2 = y0 + tickSize; final float y2 = y0 + tickSize;
paint.setColor(COL_GRID);
float step = stepFactor(gwidth);
// Calculator.log("width " + gwidth + " step " + step); {
float v = ((int) (minX / step)) * step; // GRID
textPaint.setColor(COL_TEXT);
textPaint.setTextSize(12); paint.setPathEffect(new DashPathEffect(new float[]{5, 10}, 0));
textPaint.setTextAlign(Paint.Align.CENTER); paint.setColor(graphViewHelper.getFunctionViewDef().getGridColor());
float stepScale = step * scale;
for (float x = (v - minX) * scale; x <= width; x += stepScale, v += step) { float step = stepFactor(gwidth);
canvas.drawLine(x, 0, x, height, paint); // Calculator.log("width " + gwidth + " step " + step);
if (!(-.001f < v && v < .001f)) { float v = ((int) (minX / step)) * step;
StringBuilder b = format(v); textPaint.setColor(graphViewHelper.getFunctionViewDef().getAxisLabelsColor());
canvas.drawText(b, 0, b.length(), x, y2 + 10, textPaint); 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);
} }
final float x1 = x0 - tickSize; // AXIS
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.setColor(COL_AXIS); paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor());
if (drawYAxis) { if (drawYAxis) {
canvas.drawLine(x0, 0, x0, height, paint); canvas.drawLine(x0, 0, x0, height, paint);
} }
@ -450,15 +472,23 @@ public class Graph2dView extends View implements
matrix.postScale(scale, -scale); matrix.postScale(scale, -scale);
matrix.postTranslate(width / 2, height / 2); matrix.postTranslate(width / 2, height / 2);
paint.setStrokeWidth(0);
paint.setAntiAlias(false); paint.setAntiAlias(false);
int n = Math.min(funcs.size(), GRAPHS_SIZE); final List<FunctionPlotDef> functionPlotDefs = graphViewHelper.getFunctionPlotDefs();
for (int i = 0; i < n; ++i) {
computeGraph(funcs.get(i), minX, maxX, boundMinY, boundMaxY, graphs[i]); // create path once
Path path = graphToPath(graphs[i]); 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); path.transform(matrix);
paint.setColor(COL_GRAPH[i]);
fpd.getLineDef().applyToPaint(paint);
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
} }
lastMinX = minX; lastMinX = minX;
@ -485,7 +515,7 @@ public class Graph2dView extends View implements
} }
private void invalidateGraphs() { private void invalidateGraphs() {
clearAllGraph(); clearAllGraphs();
boundMinY = boundMaxY = 0; boundMinY = boundMaxY = 0;
invalidate(); invalidate();
} }

View File

@ -1,6 +1,6 @@
// Copyright (C) 2009-2010 Mihai Preda // Copyright (C) 2009-2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import org.javia.arity.Function; import org.javia.arity.Function;
@ -12,7 +12,9 @@ import java.nio.FloatBuffer;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
class Graph3d { class Graph3d {
private final int N = Calculator.useHighQuality3d ? 36 : 24;
private final int N;
private final boolean useHighQuality3d;
private ShortBuffer verticeIdx; private ShortBuffer verticeIdx;
private FloatBuffer vertexBuf; private FloatBuffer vertexBuf;
private ByteBuffer colorBuf; private ByteBuffer colorBuf;
@ -20,33 +22,36 @@ class Graph3d {
private boolean useVBO; private boolean useVBO;
private int nVertex; private int nVertex;
Graph3d(GL11 gl) { Graph3d(GL11 gl, boolean useHighQuality3d) {
short[] b = new short[N*N]; this.useHighQuality3d = useHighQuality3d;
this.N = useHighQuality3d ? 36 : 24;
short[] b = new short[N * N];
int p = 0; int p = 0;
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
short v = 0; short v = 0;
for (int j = 0; j < N; v += N+N, j+=2) { for (int j = 0; j < N; v += N + N, j += 2) {
b[p++] = (short)(v+i); b[p++] = (short) (v + i);
b[p++] = (short)(v+N+N-1-i); b[p++] = (short) (v + N + N - 1 - i);
} }
v = (short) (N*(N-2)); v = (short) (N * (N - 2));
i++; i++;
for (int j = N-1; j >= 0; v -= N+N, j-=2) { for (int j = N - 1; j >= 0; v -= N + N, j -= 2) {
b[p++] = (short)(v+N+N-1-i); b[p++] = (short) (v + N + N - 1 - i);
b[p++] = (short)(v+i); b[p++] = (short) (v + i);
} }
} }
verticeIdx = buildBuffer(b); verticeIdx = buildBuffer(b);
String extensions = gl.glGetString(GL10.GL_EXTENSIONS); String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
useVBO = extensions.indexOf("vertex_buffer_object") != -1; useVBO = extensions.indexOf("vertex_buffer_object") != -1;
Calculator.log("VBOs support: " + useVBO + " version " + gl.glGetString(GL10.GL_VERSION)); //Calculator.log("VBOs support: " + useVBO + " version " + gl.glGetString(GL10.GL_VERSION));
if (useVBO) { if (useVBO) {
int[] out = new int[3]; int[] out = new int[3];
gl.glGenBuffers(3, out, 0); gl.glGenBuffers(3, out, 0);
vertexVbo = out[0]; vertexVbo = out[0];
colorVbo = out[1]; colorVbo = out[1];
vertexElementVbo = out[2]; vertexElementVbo = out[2];
} }
} }
@ -78,34 +83,34 @@ class Graph3d {
} }
public void update(GL11 gl, Function f, float zoom) { public void update(GL11 gl, Function f, float zoom) {
final int NTICK = Calculator.useHighQuality3d ? 5 : 0; final int NTICK = useHighQuality3d ? 5 : 0;
final float size = 4*zoom; final float size = 4 * zoom;
final float minX = -size, maxX = size, minY = -size, maxY = size; final float minX = -size, maxX = size, minY = -size, maxY = size;
Calculator.log("update VBOs " + vertexVbo + ' ' + colorVbo + ' ' + vertexElementVbo); //Calculator.log("update VBOs " + vertexVbo + ' ' + colorVbo + ' ' + vertexElementVbo);
nVertex = N*N+6+8 + NTICK*6; nVertex = N * N + 6 + 8 + NTICK * 6;
int nFloats = nVertex * 3; int nFloats = nVertex * 3;
float vertices[] = new float[nFloats]; float vertices[] = new float[nFloats];
byte colors[] = new byte[nVertex << 2]; byte colors[] = new byte[nVertex << 2];
if (f != null) { if (f != null) {
Calculator.log("Graph3d update"); //Calculator.log("Graph3d update");
float sizeX = maxX - minX; float sizeX = maxX - minX;
float sizeY = maxY - minY; float sizeY = maxY - minY;
float stepX = sizeX / (N-1); float stepX = sizeX / (N - 1);
float stepY = sizeY / (N-1); float stepY = sizeY / (N - 1);
int pos = 0; int pos = 0;
double sum = 0; double sum = 0;
float y = minY; float y = minY;
float x = minX - stepX; float x = minX - stepX;
int nRealPoints = 0; int nRealPoints = 0;
for (int i = 0; i < N; i++, y+=stepY) { for (int i = 0; i < N; i++, y += stepY) {
float xinc = (i & 1) == 0 ? stepX : -stepX; float xinc = (i & 1) == 0 ? stepX : -stepX;
x += xinc; x += xinc;
for (int j = 0; j < N; ++j, x+=xinc, pos+=3) { for (int j = 0; j < N; ++j, x += xinc, pos += 3) {
float z = (float) f.eval(x, y); float z = (float) f.eval(x, y);
vertices[pos] = x; vertices[pos] = x;
vertices[pos+1] = y; vertices[pos + 1] = y;
vertices[pos+2] = z; vertices[pos + 2] = z;
if (z == z) { // not NAN if (z == z) { // not NAN
sum += z * z; sum += z * z;
++nRealPoints; ++nRealPoints;
@ -117,110 +122,118 @@ class Graph3d {
maxAbs = Math.min(maxAbs, 15); maxAbs = Math.min(maxAbs, 15);
maxAbs = Math.max(maxAbs, .001f); maxAbs = Math.max(maxAbs, .001f);
final int limitColor = N*N*4; final int limitColor = N * N * 4;
for (int i = 0, j = 2; i < limitColor; i+=4, j+=3) { for (int i = 0, j = 2; i < limitColor; i += 4, j += 3) {
float z = vertices[j]; float z = vertices[j];
if (z == z) { if (z == z) {
final float a = z / maxAbs; final float a = z / maxAbs;
final float abs = a < 0 ? -a : a; final float abs = a < 0 ? -a : a;
colors[i] = floatToByte(a); colors[i] = floatToByte(a);
colors[i+1] = floatToByte(1-abs*.3f); colors[i + 1] = floatToByte(1 - abs * .3f);
colors[i+2] = floatToByte(-a); colors[i + 2] = floatToByte(-a);
colors[i+3] = (byte) 255; colors[i + 3] = (byte) 255;
} else { } else {
vertices[j] = 0; vertices[j] = 0;
z = 0; z = 0;
colors[i] = 0; colors[i] = 0;
colors[i+1] = 0; colors[i + 1] = 0;
colors[i+2] = 0; colors[i + 2] = 0;
colors[i+3] = 0; colors[i + 3] = 0;
} }
} }
} }
int base = N*N*3; int base = N * N * 3;
int colorBase = N*N*4; int colorBase = N * N * 4;
int p = base; int p = base;
final int baseSize = 2; final int baseSize = 2;
for (int i = -baseSize; i <= baseSize; i+=2*baseSize) { for (int i = -baseSize; i <= baseSize; i += 2 * baseSize) {
vertices[p] = i; vertices[p+1] = -baseSize; vertices[p+2] = 0; vertices[p] = i;
vertices[p + 1] = -baseSize;
vertices[p + 2] = 0;
p += 3; p += 3;
vertices[p] = i; vertices[p+1] = baseSize; vertices[p+2] = 0; vertices[p] = i;
vertices[p + 1] = baseSize;
vertices[p + 2] = 0;
p += 3; p += 3;
vertices[p] = -baseSize; vertices[p+1] = i; vertices[p+2] = 0; vertices[p] = -baseSize;
vertices[p + 1] = i;
vertices[p + 2] = 0;
p += 3; p += 3;
vertices[p] = baseSize; vertices[p+1] = i; vertices[p+2] = 0; vertices[p] = baseSize;
vertices[p + 1] = i;
vertices[p + 2] = 0;
p += 3; p += 3;
} }
for (int i = colorBase; i < colorBase+8*4; i += 4) { for (int i = colorBase; i < colorBase + 8 * 4; i += 4) {
colors[i] = 0; colors[i] = 0;
colors[i+1] = 0; colors[i + 1] = 0;
colors[i+2] = (byte) 255; colors[i + 2] = (byte) 255;
colors[i+3] = (byte) 255; colors[i + 3] = (byte) 255;
} }
base += 8*3; base += 8 * 3;
colorBase += 8*4; colorBase += 8 * 4;
final float unit = 2; final float unit = 2;
final float axis[] = { final float axis[] = {
0, 0, 0, 0, 0, 0,
unit, 0, 0, unit, 0, 0,
0, 0, 0, 0, 0, 0,
0, unit, 0, 0, unit, 0,
0, 0, 0, 0, 0, 0,
0, 0, unit, 0, 0, unit,
}; };
System.arraycopy(axis, 0, vertices, base, 6*3); System.arraycopy(axis, 0, vertices, base, 6 * 3);
for (int i = colorBase; i < colorBase+6*4; i+=4) { for (int i = colorBase; i < colorBase + 6 * 4; i += 4) {
colors[i] = (byte) 255; colors[i] = (byte) 255;
colors[i+1] = (byte) 255; colors[i + 1] = (byte) 255;
colors[i+2] = (byte) 255; colors[i + 2] = (byte) 255;
colors[i+3] = (byte) 255; colors[i + 3] = (byte) 255;
} }
base += 6*3; base += 6 * 3;
colorBase += 6*4; colorBase += 6 * 4;
p = base; p = base;
final float tick = .03f; final float tick = .03f;
final float offset = .01f; final float offset = .01f;
for (int i = 1; i <= NTICK; ++i) { for (int i = 1; i <= NTICK; ++i) {
vertices[p] = i-tick; vertices[p] = i - tick;
vertices[p+1] = -offset; vertices[p + 1] = -offset;
vertices[p+2] = -offset; vertices[p + 2] = -offset;
vertices[p+3] = i+tick; vertices[p + 3] = i + tick;
vertices[p+4] = offset; vertices[p + 4] = offset;
vertices[p+5] = offset; vertices[p + 5] = offset;
p += 6; p += 6;
vertices[p] = -offset; vertices[p] = -offset;
vertices[p+1] = i-tick; vertices[p + 1] = i - tick;
vertices[p+2] = -offset; vertices[p + 2] = -offset;
vertices[p+3] = offset; vertices[p + 3] = offset;
vertices[p+4] = i+tick; vertices[p + 4] = i + tick;
vertices[p+5] = offset; vertices[p + 5] = offset;
p += 6; p += 6;
vertices[p] = -offset; vertices[p] = -offset;
vertices[p+1] = -offset; vertices[p + 1] = -offset;
vertices[p+2] = i-tick; vertices[p + 2] = i - tick;
vertices[p+3] = offset; vertices[p + 3] = offset;
vertices[p+4] = offset; vertices[p + 4] = offset;
vertices[p+5] = i+tick; vertices[p + 5] = i + tick;
p += 6; p += 6;
} }
for (int i = colorBase+NTICK*6*4-1; i >= colorBase; --i) { for (int i = colorBase + NTICK * 6 * 4 - 1; i >= colorBase; --i) {
colors[i] = (byte) 255; colors[i] = (byte) 255;
} }
vertexBuf = buildBuffer(vertices); vertexBuf = buildBuffer(vertices);
colorBuf = buildBuffer(colors); colorBuf = buildBuffer(colors);
if (useVBO) { if (useVBO) {
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo); gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexBuf.capacity()*4, vertexBuf, GL11.GL_STATIC_DRAW); gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexBuf.capacity() * 4, vertexBuf, GL11.GL_STATIC_DRAW);
vertexBuf = null; vertexBuf = null;
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo); gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
@ -229,13 +242,13 @@ class Graph3d {
colorBuf = null; colorBuf = null;
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo); gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, verticeIdx.capacity()*2, verticeIdx, GL11.GL_STATIC_DRAW); gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, verticeIdx.capacity() * 2, verticeIdx, GL11.GL_STATIC_DRAW);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0); gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
} }
} }
private byte floatToByte(float v) { private byte floatToByte(float v) {
return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int)(v*255)); return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int) (v * 255));
} }
public void draw(GL11 gl) { public void draw(GL11 gl) {
@ -250,15 +263,19 @@ class Graph3d {
// gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N*N); // gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N*N);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo); gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, 0); gl.glDrawElements(GL10.GL_LINE_STRIP, N * N, GL10.GL_UNSIGNED_SHORT, 0);
gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0); gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
} else { } else {
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf);
gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, colorBuf); gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, colorBuf);
gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, verticeIdx); gl.glDrawElements(GL10.GL_LINE_STRIP, N * N, GL10.GL_UNSIGNED_SHORT, verticeIdx);
} }
final int N2 = N*N; final int N2 = N * N;
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N2); gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N2);
gl.glDrawArrays(GL10.GL_LINES, N2, nVertex - N2); gl.glDrawArrays(GL10.GL_LINES, N2, nVertex - N2);
} }
public boolean isUseHighQuality3d() {
return useHighQuality3d;
}
} }

View File

@ -1,21 +1,23 @@
// Copyright (C) 2009 Mihai Preda // Copyright (C) 2009 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.content.Context; import android.content.Context;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.widget.ZoomButtonsController; import android.widget.ZoomButtonsController;
import org.javia.arity.Function; import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11;
import java.util.List;
public class Graph3dView extends GLView implements public class Graph3dView extends GLView implements GraphView {
GraphView,
ZoomButtonsController.OnZoomListener, private boolean useHighQuality3d = Build.VERSION.SDK_INT >= 5;
TouchHandler.TouchHandlerInterface {
private float lastTouchX, lastTouchY; private float lastTouchX, lastTouchY;
private TouchHandler touchHandler; private TouchHandler touchHandler;
@ -160,8 +162,16 @@ public class Graph3dView extends GLView implements
return angleX < -limit || angleX > limit || angleY < -limit || angleY > limit; return angleX < -limit || angleX > limit || angleY < -limit || angleY > limit;
} }
public void setFunction(Function f) { @Override
function = f; 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; zoomLevel = 1;
isDirty = true; isDirty = true;
} }
@ -171,9 +181,9 @@ public class Graph3dView extends GLView implements
gl.glDisable(GL10.GL_DITHER); gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glClearColor(0, 0, 0, 1); gl.glClearColor(0, 0, 0, 1);
gl.glShadeModel(Calculator.useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT); gl.glShadeModel(useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT);
gl.glDisable(GL10.GL_LIGHTING); gl.glDisable(GL10.GL_LIGHTING);
graph = new Graph3d((GL11) gl); graph = new Graph3d((GL11) gl, useHighQuality3d);
isDirty = true; isDirty = true;
angleX = .5f; angleX = .5f;
angleY = 0; angleY = 0;
@ -195,9 +205,9 @@ public class Graph3dView extends GLView implements
isDirty = false; isDirty = false;
} }
if (fps.incFrame()) { /*if (fps.incFrame()) {
Calculator.log("f/s " + fps.getValue()); Calculator.log("f/s " + fps.getValue());
} }*/
gl.glClear(GL10.GL_COLOR_BUFFER_BIT); gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glMatrixMode(GL10.GL_MODELVIEW);
@ -244,6 +254,6 @@ public class Graph3dView extends GLView implements
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
b.append(m[i]).append(' '); b.append(m[i]).append(' ');
} }
Calculator.log(name + ' ' + b.toString()); //Calculator.log(name + ' ' + b.toString());
} }
} }

View 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;
}
}

View File

@ -1,22 +1,25 @@
// Copyright (C) 2009-2010 Mihai Preda // Copyright (C) 2009-2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import org.javia.arity.Function; import android.widget.ZoomButtonsController;
import org.jetbrains.annotations.NotNull;
public interface GraphView { import java.util.List;
public interface GraphView extends ZoomButtonsController.OnZoomListener, TouchHandler.TouchHandlerInterface {
static final String SCREENSHOT_DIR = "/screenshots"; static final String SCREENSHOT_DIR = "/screenshots";
public void setFunction(Function f); public void init(@NotNull FunctionViewDef functionViewDef);
public void setFunctionPlotDefs(@NotNull List<FunctionPlotDef> functionPlotDefs);
public void onPause(); public void onPause();
public void onResume(); public void onResume();
public String captureScreenshot(); public String captureScreenshot();
void setId(int id);
/* /*
********************************************************************** **********************************************************************
* *

View File

@ -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;
}
}

View File

@ -1,11 +1,12 @@
// Copyright (C) 2010 Mihai Preda // Copyright (C) 2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.os.Build;
import android.view.MotionEvent; import android.view.MotionEvent;
class MotionEventWrap { class MotionEventWrap {
private static final boolean IS_API_5 = Util.SDK_VERSION >= 5; private static final boolean IS_API_5 = Build.VERSION.SDK_INT >= 5;
static int getPointerCount(MotionEvent event) { static int getPointerCount(MotionEvent event) {
return IS_API_5 ? MotionEventWrapNew.getPointerCount(event) : 1; return IS_API_5 ? MotionEventWrapNew.getPointerCount(event) : 1;

View File

@ -1,6 +1,6 @@
// Copyright (C) 2010 Mihai Preda // Copyright (C) 2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.view.MotionEvent; import android.view.MotionEvent;

View File

@ -1,6 +1,6 @@
// Copyright (C) 2009-2010 Mihai Preda // Copyright (C) 2009-2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.VelocityTracker; import android.view.VelocityTracker;

View File

@ -1,6 +1,6 @@
// Copyright (C) 2010 Mihai Preda // Copyright (C) 2010 Mihai Preda
package arity.calculator; package org.solovyev.android.calculator.plot;
class ZoomTracker { class ZoomTracker {
private float sx1, sy1, sx2, sy2; private float sx1, sy1, sx2, sy2;