From b0608b8204ba0aa8dc6d9626acfd80a4dd5e2f9d Mon Sep 17 00:00:00 2001 From: Sergey Solovyev Date: Sun, 6 Jan 2013 00:59:35 +0400 Subject: [PATCH] new plotter --- android-app-core/res/values/text_strings.xml | 1 + .../calculator/CalculatorDisplayMenuItem.java | 8 +- .../android/calculator/plot/PlotInput.java | 6 +- android-app/res/menu/plot_menu.xml | 6 + .../calculator/AndroidCalculatorNotifier.java | 4 +- .../CalculatorActivityLauncher.java | 4 +- .../plot/AbstractCalculatorPlotFragment.java | 111 +++++++++++++----- .../plot/CalculatorArityPlotFragment.java | 53 +++++++-- .../plot/CalculatorPlotFragment.java | 5 + .../plot/FunctionLineColorType.java | 12 ++ .../calculator/plot/FunctionLineDef.java | 18 +++ .../android/calculator/plot/GLView.java | 50 ++++---- .../android/calculator/plot/Graph3d.java | 92 +++++++++++---- .../android/calculator/plot/Graph3dView.java | 47 ++++++-- .../android/calculator/plot/PlotUtils.java | 41 +++++-- .../calculator/CalculatorEventType.java | 3 + .../android/calculator/CalculatorUtils.java | 4 +- 17 files changed, 350 insertions(+), 115 deletions(-) create mode 100644 android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java diff --git a/android-app-core/res/values/text_strings.xml b/android-app-core/res/values/text_strings.xml index ce5a05be..0e5870c3 100644 --- a/android-app-core/res/values/text_strings.xml +++ b/android-app-core/res/values/text_strings.xml @@ -282,5 +282,6 @@ You can remove second icon in applications\' list from application settings or by pressing next button Remove icon This change may require reboot + 3D \ No newline at end of file diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java index 1a54fa49..aa40fcae 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java @@ -85,7 +85,13 @@ public enum CalculatorDisplayMenuItem implements LabeledMenuItem variables = new ArrayList(CalculatorUtils.getNotSystemConstants(generic)); - final Constant xVariable = variables.get(0); + + final Constant xVariable; + if ( variables.size() > 0 ) { + xVariable = variables.get(0); + } else { + xVariable = null; + } final Constant yVariable; if ( variables.size() > 1 ) { diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java index 5f17a629..6a5084b8 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java @@ -15,7 +15,7 @@ public class PlotInput { @NotNull private Generic function; - @NotNull + @Nullable private Constant xVariable; @Nullable @@ -26,7 +26,7 @@ public class PlotInput { @NotNull public static PlotInput newInstance(@NotNull Generic function, - @NotNull Constant xVariable, + @Nullable Constant xVariable, @Nullable Constant yVariable) { PlotInput result = new PlotInput(); @@ -42,7 +42,7 @@ public class PlotInput { return function; } - @NotNull + @Nullable public Constant getXVariable() { return xVariable; } diff --git a/android-app/res/menu/plot_menu.xml b/android-app/res/menu/plot_menu.xml index 0e734ab4..2e9fd473 100644 --- a/android-app/res/menu/plot_menu.xml +++ b/android-app/res/menu/plot_menu.xml @@ -8,8 +8,14 @@ + + + \ No newline at end of file diff --git a/android-app/src/main/java/org/solovyev/android/calculator/AndroidCalculatorNotifier.java b/android-app/src/main/java/org/solovyev/android/calculator/AndroidCalculatorNotifier.java index a720f01d..532bf880 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/AndroidCalculatorNotifier.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/AndroidCalculatorNotifier.java @@ -48,9 +48,9 @@ public class AndroidCalculatorNotifier implements CalculatorNotifier { @Override public void showDebugMessage(@Nullable final String tag, @NotNull final String message) { - /*if (AndroidUtils.isDebuggable(application)) { + if (AndroidUtils.isDebuggable(application)) { showMessageInUiThread(tag == null ? message : tag + ": " + message); - }*/ + } } private void showMessageInUiThread(@NotNull final String message) { diff --git a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java index 10e7a93c..6de8517f 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java @@ -102,11 +102,11 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, - @NotNull Constant xVariable, + @Nullable Constant xVariable, @Nullable Constant yVariable){ final Intent intent = new Intent(); intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph)); - final AbstractCalculatorPlotFragment.Input input = new CalculatorPlotFragment.Input(generic.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName()); + final AbstractCalculatorPlotFragment.Input input = new CalculatorPlotFragment.Input(generic.toString(), xVariable == null ? null : xVariable.getName(), yVariable == null ? null : yVariable.getName()); intent.putExtra(CalculatorPlotFragment.INPUT, input); intent.setClass(context, CalculatorPlotActivity.class); AndroidUtils2.addFlags(intent, false, context); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java index 85112169..68ad6236 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java @@ -75,7 +75,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment private PreparedInput preparedInput; @NotNull - private ActivityMenu fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, PlotMenu.class, SherlockMenuHelper.getInstance()); + private ActivityMenu fragmentMenu; // thread which calculated data for graph view @NotNull @@ -150,32 +150,34 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData); if (result.isNewAfter()) { preparedInput = prepareInputFromDisplay(((CalculatorDisplayChangeEventData) data).getNewValue(), null); - this.preparedInput = preparedInput; - - - final PreparedInput finalPreparedInput = preparedInput; - getUiHandler().post(new Runnable() { - @Override - public void run() { - - if (!finalPreparedInput.isError()) { - createChart(finalPreparedInput); - - final View view = getView(); - if (view != null) { - createGraphicalView(view, finalPreparedInput); - } - } else { - onError(); - } - } - }); + onNewPreparedInput(preparedInput); } - } } } + private void onNewPreparedInput(@NotNull PreparedInput preparedInput) { + this.preparedInput = preparedInput; + + final PreparedInput finalPreparedInput = preparedInput; + getUiHandler().post(new Runnable() { + @Override + public void run() { + + if (!finalPreparedInput.isError()) { + createChart(finalPreparedInput); + + final View view = getView(); + if (view != null) { + createGraphicalView(view, finalPreparedInput); + } + } else { + onError(); + } + } + }); + } + protected abstract void onError(); protected abstract void createGraphicalView(@NotNull View view, @NotNull PreparedInput preparedInput); @@ -230,12 +232,32 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); + final List> menuItems = new ArrayList>(); + menuItems.add(PlotMenu.preferences); + if ( is3dPlotSupported() ) { + menuItems.add(new IdentifiableMenuItem() { + @NotNull + @Override + public Integer getItemId() { + return R.id.menu_plot_3d; + } + + @Override + public void onClick(@NotNull MenuItem data, @NotNull Context context) { + onNewPreparedInput(PreparedInput.force3dInstance(preparedInput)); + } + }); + } + fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, menuItems, SherlockMenuHelper.getInstance()); + final FragmentActivity activity = this.getActivity(); if (activity != null) { fragmentMenu.onCreateOptionsMenu(activity, menu); } } + protected abstract boolean is3dPlotSupported(); + @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -266,7 +288,13 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment final Generic expression = displayState.getResult(); if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) { final List variables = new ArrayList(CalculatorUtils.getNotSystemConstants(expression)); - final Constant xVariable = variables.get(0); + + final Constant xVariable; + if ( variables.size() > 0 ) { + xVariable = variables.get(0); + } else { + xVariable = null; + } final Constant yVariable; if ( variables.size() > 1 ) { @@ -275,7 +303,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment yVariable = null; } - final Input input = new Input(expression.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName()); + final Input input = new Input(expression.toString(), xVariable == null ? null : xVariable.getName(), yVariable == null ? null : yVariable.getName()); return prepareInput(input, false, savedInstanceState); } } @@ -293,7 +321,13 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment try { final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression()); final Generic expression = Expression.valueOf(preparedExpression.getExpression()); - final Constant xVar = new Constant(input.getXVariableName()); + + final Constant xVar; + if (input.getXVariableName() != null) { + xVar = new Constant(input.getXVariableName()); + } else { + xVar = null; + } final Constant yVar; if (input.getYVariableName() != null) { @@ -416,6 +450,8 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment private boolean fromInputArgs; + private boolean force3d = false; + @NotNull private PlotBoundaries plotBoundaries = PlotBoundaries.newDefaultInstance(); @@ -425,7 +461,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment @NotNull public static PreparedInput newInstance(@NotNull Input input, @NotNull Generic expression, - @NotNull Constant xVariable, + @Nullable Constant xVariable, @Nullable Constant yVariable, boolean fromInputArgs, @NotNull PlotBoundaries plotBoundaries) { @@ -454,6 +490,17 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment return result; } + @NotNull + public static PreparedInput force3dInstance(final PreparedInput that) { + if (!that.isError()) { + final PreparedInput result = PreparedInput.newInstance(that.input, that.expression, that.xVariable, that.yVariable, that.fromInputArgs, that.plotBoundaries); + result.force3d = true; + return result; + } else { + return that; + } + } + public boolean isFromInputArgs() { return fromInputArgs; } @@ -483,8 +530,12 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment return yVariable; } + public boolean isForce3d() { + return force3d; + } + public boolean isError() { - return input == null || expression == null || xVariable == null; + return input == null || expression == null; } } @@ -493,14 +544,14 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment @NotNull private String expression; - @NotNull + @Nullable private String xVariableName; @Nullable private String yVariableName; public Input(@NotNull String expression, - @NotNull String xVariableName, + @Nullable String xVariableName, @Nullable String yVariableName) { this.expression = expression; this.xVariableName = xVariableName; @@ -512,7 +563,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment return expression; } - @NotNull + @Nullable public String getXVariableName() { return xVariableName; } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java index 3bcf3625..d891e8e6 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java @@ -57,19 +57,21 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment final Constant xVariable = preparedInput.getXVariable(); final Constant yVariable = preparedInput.getYVariable(); - final int arity = yVariable == null ? 1 : 2; + final int arity = xVariable == null ? 0 : (yVariable == null ? 1 : 2); final List functions = new ArrayList(); functions.add(FunctionPlotDef.newInstance(new RealArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(realLineColor.getColor(), FunctionLineStyle.solid, 3f))); - - if (arity == 1) { - functions.add(FunctionPlotDef.newInstance(new ImaginaryArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(imagLineColor.getColor(), FunctionLineStyle.solid, 3f))); - } + functions.add(FunctionPlotDef.newInstance(new ImaginaryArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(imagLineColor.getColor(), FunctionLineStyle.solid, 3f))); switch (arity) { + case 0: case 1: - graphView = new Graph2dView(getActivity()); + if (preparedInput.isForce3d()) { + graphView = new Graph3dView(getActivity()); + } else { + graphView = new Graph2dView(getActivity()); + } break; case 2: graphView = new Graph3dView(getActivity()); @@ -91,6 +93,11 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment protected void createChart(@NotNull PreparedInput preparedInput) { } + @Override + protected boolean is3dPlotSupported() { + return true; + } + @Override public void onResume() { @@ -133,19 +140,35 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment @NotNull protected final Generic expression; - @NotNull + @Nullable protected final Constant xVariable; @Nullable protected final Constant yVariable; - public AbstractArityFunction(int arity, @NotNull Generic expression, @NotNull Constant xVariable, @Nullable Constant yVariable) { + @Nullable + private Double constant = null; + + public AbstractArityFunction(int arity, + @NotNull Generic expression, + @Nullable Constant xVariable, + @Nullable Constant yVariable) { this.arity = arity; this.expression = expression; this.xVariable = xVariable; this.yVariable = yVariable; } + @Override + public final double eval() { + if (constant == null) { + constant = eval0(); + } + return constant; + } + + protected abstract double eval0(); + @Override public final int arity() { return arity; @@ -157,11 +180,16 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment private RealArityFunction(int arity, @NotNull Generic expression, - @NotNull Constant xVariable, + @Nullable Constant xVariable, @Nullable Constant yVariable) { super(arity, expression, xVariable, yVariable); } + @Override + public double eval0() { + return PlotUtils.calculatorExpression(expression).realPart(); + } + @Override public double eval(double x) { return PlotUtils.calculatorExpression(expression, xVariable, x).realPart(); @@ -177,11 +205,16 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment private ImaginaryArityFunction(int arity, @NotNull Generic expression, - @NotNull Constant xVariable, + @Nullable Constant xVariable, @Nullable Constant yVariable) { super(arity, expression, xVariable, yVariable); } + @Override + public double eval0() { + return PlotUtils.calculatorExpression(expression).imaginaryPart(); + } + @Override public double eval(double x) { return PlotUtils.calculatorExpression(expression, xVariable, x).imaginaryPart(); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java index 610550ab..39c2a2ed 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java @@ -57,6 +57,11 @@ public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment { } } + @Override + protected boolean is3dPlotSupported() { + return false; + } + @Nullable @Override protected PlotBoundaries getPlotBoundaries() { diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java new file mode 100644 index 00000000..9b2adee5 --- /dev/null +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java @@ -0,0 +1,12 @@ +package org.solovyev.android.calculator.plot; + +/** + * User: serso + * Date: 1/5/13 + * Time: 10:45 PM + */ +public enum FunctionLineColorType { + + color_map, + solid; +} diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java index 0475c083..2924c1fb 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java @@ -30,6 +30,9 @@ public class FunctionLineDef { ********************************************************************** */ + @NotNull + private FunctionLineColorType lineColorType = FunctionLineColorType.solid; + private int lineColor = Color.WHITE; @NotNull @@ -57,6 +60,16 @@ public class FunctionLineDef { return result; } + @NotNull + public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle, float lineWidth, @NotNull FunctionLineColorType lineColorType) { + final FunctionLineDef result = new FunctionLineDef(); + result.lineColor = lineColor; + result.lineColorType = lineColorType; + result.lineStyle = lineStyle; + result.lineWidth = lineWidth; + return result; + } + @NotNull public static FunctionLineDef newDefaultInstance() { return new FunctionLineDef(); @@ -76,6 +89,11 @@ public class FunctionLineDef { return lineWidth; } + @NotNull + public FunctionLineColorType getLineColorType() { + return lineColorType; + } + public void applyToPaint(@NotNull Paint paint) { paint.setColor(lineColor); paint.setStyle(Paint.Style.STROKE); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java index a9b405b0..f8184816 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/GLView.java @@ -7,8 +7,10 @@ import android.graphics.Bitmap; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; +import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; +import org.jetbrains.annotations.NotNull; import org.solovyev.android.AndroidUtils2; import javax.microedition.khronos.egl.*; @@ -23,14 +25,15 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { private boolean paused; private EGL10 egl; private EGLDisplay display; - private EGLConfig config; + private EGLConfig config; private EGLSurface surface; private EGLContext eglContext; private GL11 gl; protected int width, height; - private boolean mIsLooping; + private volatile boolean looping; abstract void onDrawFrame(GL10 gl); + abstract void onSurfaceCreated(GL10 gl, int width, int height); public String captureScreenshot() { @@ -48,15 +51,22 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { 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); + bitmap.setPixels(data, size - width, -width, 0, 0, width, height); return bitmap; } - private Handler handler = new Handler() { - public void handleMessage(Message msg) { - glDraw(); + @NotNull + private final Handler uiHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + glDraw(); + break; + default: + Log.e("GLView", "Incorrect message id: " + msg.what); } - }; + } + }; public GLView(Context context) { super(context); @@ -69,11 +79,11 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { } private void init() { - SurfaceHolder holder = getHolder(); + final SurfaceHolder holder = getHolder(); holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); holder.addCallback(this); } - + public void onResume() { paused = false; if (hasSurface) { @@ -90,7 +100,7 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { 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]; @@ -129,7 +139,7 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) { paused = true; } - if (mIsLooping) { + if (looping) { requestDraw(); } } @@ -137,9 +147,9 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { public void surfaceCreated(SurfaceHolder holder) { } - + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - this.width = width; + this.width = width; this.height = height; boolean doInit = !hasSurface && !paused; hasSurface = true; @@ -154,24 +164,24 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { } public void startLooping() { - if (!mIsLooping) { - mIsLooping = true; + if (!looping) { + looping = true; glDraw(); } } public void stopLooping() { - if (mIsLooping) { - mIsLooping = false; + if (looping) { + looping = false; } } public boolean isLooping() { - return mIsLooping; + return looping; } public void requestDraw() { - handler.sendEmptyMessage(1); + uiHandler.sendEmptyMessage(1); } static void bitmapBGRtoRGB(Bitmap bitmap, int width, int height) { @@ -182,7 +192,7 @@ abstract class GLView extends SurfaceView implements SurfaceHolder.Callback { 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)); + data[i] = (short) (((v & 0x1f) << 11) | (v & 0x7e0) | ((v & 0xf800) >> 11)); } buf.rewind(); bitmap.copyPixelsFromBuffer(buf); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java index e630e2c8..c395b564 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java @@ -2,7 +2,9 @@ package org.solovyev.android.calculator.plot; +import android.graphics.Color; import org.javia.arity.Function; +import org.jetbrains.annotations.NotNull; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; @@ -82,17 +84,20 @@ class Graph3d { return bb; } - public void update(GL11 gl, Function f, float zoom) { + public void update(@NotNull GL11 gl, @NotNull FunctionPlotDef fpd, float zoom) { + final Function function = fpd.getFunction(); + final FunctionLineDef lineDef = fpd.getLineDef(); 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) { + + final float vertices[] = new float[nVertex * 3]; + final byte colors[] = new byte[nVertex * 4]; + + if (fpd != null) { //Calculator.log("Graph3d update"); float sizeX = maxX - minX; float sizeY = maxY - minY; @@ -103,43 +108,74 @@ class Graph3d { float y = minY; float x = minX - stepX; int nRealPoints = 0; + + final int arity = function.arity(); + 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); + + final float z; + switch (arity) { + case 2: + z = (float) function.eval(x, y); + break; + case 1: + // todo serso: optimize (can be calculated once before loop) + z = (float) function.eval(x); + break; + case 0: + // todo serso: optimize (can be calculated once) + z = (float) function.eval(); + break; + default: + throw new IllegalArgumentException(); + } + vertices[pos] = x; vertices[pos + 1] = y; vertices[pos + 2] = z; - if (z == z) { // not NAN + + if (!Float.isNaN(z)) { 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 lineColor = lineDef.getLineColor(); 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; - } + final float z = vertices[j]; + + if (!Float.isNaN(z)) { + if (lineDef.getLineColorType() == FunctionLineColorType.color_map) { + 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); + } else { + colors[i] = (byte) Color.red(lineColor); + colors[i + 1] = (byte) Color.green(lineColor); + colors[i + 2] = (byte) Color.blue(lineColor); + } + colors[i + 3] = (byte) 255; + } else { + vertices[j] = 0; + colors[i] = 0; + colors[i + 1] = 0; + colors[i + 2] = 0; + colors[i + 3] = 0; + } + } } int base = N * N * 3; @@ -248,7 +284,15 @@ class Graph3d { } private byte floatToByte(float v) { - return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int) (v * 255)); + if (v <= 0) { + return (byte) 0; + } else { + if (v >= 1) { + return (byte) 255; + } else { + return (byte) (v * 255); + } + } } public void draw(GL11 gl) { diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java index c061dc40..eba152d0 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java @@ -3,16 +3,18 @@ package org.solovyev.android.calculator.plot; import android.content.Context; +import android.graphics.Color; 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.ArrayList; +import java.util.Collections; import java.util.List; public class Graph3dView extends GLView implements GraphView { @@ -24,7 +26,12 @@ public class Graph3dView extends GLView implements GraphView { private ZoomButtonsController zoomController = new ZoomButtonsController(this); private float zoomLevel = 1, targetZoom, zoomStep = 0, currentZoom; private FPS fps = new FPS(); - private Graph3d graph; + + @NotNull + private List graphs = new ArrayList(); + + @NotNull + private GraphViewHelper graphViewHelper = GraphViewHelper.newDefaultInstance(); public Graph3dView(Context context, AttributeSet attrs) { super(context, attrs); @@ -149,7 +156,6 @@ public class Graph3dView extends GLView implements GraphView { 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) { @@ -164,14 +170,18 @@ public class Graph3dView extends GLView implements GraphView { @Override public void init(@NotNull FunctionViewDef functionViewDef) { + this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); } public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { - if (functionPlotDefs.size() > 0) { - function = functionPlotDefs.get(0).getFunction(); - } else { - function = null; + for (FunctionPlotDef functionPlotDef: functionPlotDefs) { + final int arity = functionPlotDef.getFunction().arity(); + if (arity != 0 && arity != 1 && arity != 2) { + throw new IllegalArgumentException("Function must have arity 0 or 1 or 2 for 3d plot!"); + } } + + this.graphViewHelper = this.graphViewHelper.copy(functionPlotDefs); zoomLevel = 1; isDirty = true; } @@ -180,10 +190,13 @@ public class Graph3dView extends GLView implements GraphView { 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); + + final int backgroundColor = graphViewHelper.getFunctionViewDef().getBackgroundColor(); + gl.glClearColor(Color.red(backgroundColor) / 255f, Color.green(backgroundColor) / 255f, Color.blue(backgroundColor) / 255f, Color.alpha(backgroundColor) / 255f); + gl.glShadeModel(useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT); gl.glDisable(GL10.GL_LIGHTING); - graph = new Graph3d((GL11) gl, useHighQuality3d); + ensureGraphsSize((GL11) gl); isDirty = true; angleX = .5f; angleY = 0; @@ -201,7 +214,11 @@ public class Graph3dView extends GLView implements GraphView { currentZoom = zoomLevel; } if (isDirty) { - graph.update(gl, function, zoomLevel); + ensureGraphsSize(gl); + for (int i = 0; i < graphViewHelper.getFunctionPlotDefs().size(); i++) { + graphs.get(i).update(gl, graphViewHelper.getFunctionPlotDefs().get(i), zoomLevel); + + } isDirty = false; } @@ -233,7 +250,15 @@ public class Graph3dView extends GLView implements GraphView { Matrix.multiplyMM(matrix3, 0, matrix2, 0, matrix1, 0); gl.glMultMatrixf(matrix3, 0); System.arraycopy(matrix3, 0, matrix1, 0, 16); - graph.draw(gl); + for (Graph3d graph : graphs) { + graph.draw(gl); + } + } + + private void ensureGraphsSize(@NotNull GL11 gl) { + while (graphViewHelper.getFunctionPlotDefs().size() > graphs.size()) { + graphs.add(new Graph3d(gl, useHighQuality3d)); + } } private void initFrustum(GL10 gl, float distance) { diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java index 7e9c8dfb..96ec1292 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java @@ -51,7 +51,7 @@ public final class PlotUtils { public static boolean addXY(double minValue, double maxValue, @NotNull Generic expression, - @NotNull Constant variable, + @Nullable Constant variable, @NotNull MyXYSeries realSeries, @Nullable MyXYSeries imagSeries, boolean addExtra, @@ -82,7 +82,7 @@ public final class PlotUtils { boolean needToCalculateRealY = realSeries.needToAdd(step, x); if (needToCalculateRealY) { - final Complex c = calculatorExpression(expression, variable, x); + final Complex c = variable == null ? calculatorExpression(expression) : calculatorExpression(expression, variable, x); Double y = prepareY(c.realPart()); if (y != null) { @@ -106,7 +106,7 @@ public final class PlotUtils { } else { boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x); if (needToCalculateImagY) { - final Complex c = calculatorExpression(expression, variable, x); + final Complex c = variable == null ? calculatorExpression(expression) : calculatorExpression(expression, variable, x); Double y = prepareY(c.imaginaryPart()); if (y != null) { imag.moveToNextPoint(x, y); @@ -128,20 +128,28 @@ public final class PlotUtils { } @NotNull - static String getImagFunctionName(@NotNull Constant variable) { - return "g(" + variable.getName() + ")" + " = " + "Im(ƒ(" + variable.getName() + "))"; + static String getImagFunctionName(@Nullable Constant variable) { + if (variable != null) { + return "g(" + variable.getName() + ")" + " = " + "Im(ƒ(" + variable.getName() + "))"; + } else { + return "g = Im(ƒ)"; + } } @NotNull - private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) { - return "ƒ(" + variable.getName() + ")" + " = " + expression.toString(); + private static String getRealFunctionName(@NotNull Generic expression, @Nullable Constant variable) { + if (variable != null) { + return "ƒ(" + variable.getName() + ")" + " = " + expression.toString(); + } else { + return "ƒ = " + expression.toString(); + } } @NotNull static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, - @NotNull final Constant variable, + @Nullable final Constant variable, int bgColor, boolean interpolate, int realLineColor, @@ -159,8 +167,12 @@ public final class PlotUtils { final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); renderer.setShowGrid(true); - renderer.setXTitle(variable.getName()); - renderer.setYTitle("f(" + variable.getName() + ")"); + renderer.setXTitle(variable != null ? variable.getName() : null); + if (variable != null) { + renderer.setYTitle("f(" + variable.getName() + ")"); + } else { + renderer.setYTitle("f"); + } renderer.setChartTitleTextSize(25); renderer.setAxisTitleTextSize(25); renderer.setLabelsTextSize(25); @@ -370,6 +382,15 @@ public final class PlotUtils { } } + @NotNull + public static Complex calculatorExpression(@NotNull Generic expression) { + try { + return unwrap(expression.numeric()); + } catch (RuntimeException e) { + return NaN; + } + } + @NotNull public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant xVar, double x) { try { diff --git a/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java b/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java index 0d133286..295aa6da 100644 --- a/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java +++ b/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java @@ -162,6 +162,9 @@ public enum CalculatorEventType { //org.solovyev.android.calculator.plot.PlotInput plot_graph, + + plot_graph_3d, + //String show_evaluation_error; diff --git a/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java b/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java index cd30ac70..19005fb2 100644 --- a/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java +++ b/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java @@ -44,9 +44,9 @@ public final class CalculatorUtils { public static boolean isPlotPossible(@NotNull Generic expression, @NotNull JsclOperation operation) { boolean result = false; - if (operation == JsclOperation.simplify) { + if (operation == JsclOperation.simplify || operation == JsclOperation.numeric) { int size = getNotSystemConstants(expression).size(); - if (size == 1 || size == 2) { + if (size == 0 || size == 1 || size == 2) { result = true; } }