I studied computer science in the 1980s, however I never made a career in
this field. But back in university I knew that I liked computer graphics and
compiler construction.
Introduction
This ASP.NET Web Pages website lets you draw self defined functions of x and
y in 3D. This project is for novice programming hobbyists who are about to discover or have already discovered WebMatrix. MVC is not easy it is for professional programmers, Web Pages however are user friendly and are fun to work with. When you know the basics of Razor and how to do certain things like for instance how to let a cshtml file generate an image or how to pass data between files that the possibilities with WebMatrix are endless.
Background
In 2006 I started coding in C# with the new Visual Studio C# Express Edition,
and coded a program to do 3D graphics. Years later I coded a Silverlight
application that drew a function of x. When WebMatrix was first released it
occured to me that I could make a Web Pages website with the Razor syntax which
used code from projects I had written already before. So I set out to make a website where the visitor
enters a text string with a formula for a function of x and y together with a
range and that then my website would produce an image with the 3D function. When
I first started working with WebMatrix Web Pages I felt and still do like a kid
in a candy store!
CoCo\R the Compiler Compiler
To generate a scanner and a parser I used CoCo\R. This is a program which you feed
an ATG language definition file and it turns it then into a C# source. This was easier
than I expected the process of creating a stack based calculator from a text
string is really rather straight forward.
Here are a few lines from my ATG file:
COMPILER FunctionCalc
CHARACTERS
letter = 'A'..'Z' + 'a'..'z'.
digit = '0'..'9'.
TOKENS
VARIABLEX = 'x' | 'X'.
VARIABLEY = 'y' | 'Y'.
NUMBER = digit { digit } [ "." digit { digit } ].
SIN = "sin" | "Sin" | "SIN".
COS = "cos" | "Cos" | "COS".
and:
PRODUCTIONS
FunctionCalc =
Expression (. assemble.AddMnemonic(Mnemonic.Actions.end, 0.0); .)
.
Expression =
Term
{ '+' Term (. assemble.AddMnemonic(Mnemonic.Actions.add, 0.0); .)
| '-' Term (. assemble.AddMnemonic(Mnemonic.Actions.subtract, 0.0); .)
}
.
I will add the ATG file to the source code.
Three Components.
There are three components, 1) the source that draws a 3D object, 2) the code
that turns a text string into a sequence of instructions to compute the entered
Function of x and y. And part 3) is the actual website.
Drawing in 3D.
I read a lot of books about C#, but I didn't see any example sources, so I
coded this module in a way I thought was best.
public class Storage
{
public double CenterX = 295.0;
public double CenterY = 295.0;
public int NumberOfVectors = 0;
public int NumberOfVertices = 0;
public int NumberOfFaces = 0;
public int MAXNumberOfVectors = 0;
public int MAXNumberOfVertices = 0;
public int MAXNumberOfFaces = 0;
public Vector[] StoreVectors;
public int[] StoreVerticesFrom;
public int[] StoreVerticesTo;
public Point2D[] StorePoints;
public double[] StoreDistanceToEyePerPoint;
public Face[] StoreFaces;
public double[] StoreFaceDistances;
public double[] StoreSortedFacesValue;
public int[] StoreSortedFacesKey;
I made operators for multiplying Matrices and Vectors which both are class
types in my source.
public class myMatrix
{
public double a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, a42, a43, a44;
and:
public class Vector
{
public double v1, v2, v3, v4;
public Vector(double v1, double v2, double v3, double v4)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
}
Computing and drawing the function:
public class myImage
{
public static myMatrix P;
public static double sizeObject = 100.0;
public static Vector Eye = new Vector(120, 135, 175, 1.0);
public static string fstring = "cos(x)*sin(y)*pi";
public static int raster = 20;
public static double xmin = -3.14;
public static double xmax = 3.14;
public myImage(Graphics g, Vector vantagepoint, String fxystring,
double range, int resolution, double scale, Function.SolidType st)
{
Eye = vantagepoint;
fstring = fxystring;
xmin = -range;
xmax = +range;
sizeObject = scale;
raster = resolution;
string errorstring = "";
g.FillRectangle(Brushes.Black, new Rectangle(0, 0, 590, 590));
Execute ex = new Execute(10000, 5000, fstring);
if (!ex.parsingerror)
{
P = new myMatrix(Eye);
Function F = new Function(ex, raster, xmin, xmax, sizeObject, Eye, out errorstring);
if (errorstring == "") { F.DrawSolid(P, g, Color.Blue, st); }
else g.DrawString("Computing Error:" + '\n' + errorstring, new Font("Times New Roman", 30), Brushes.Red, 20, 20);
}
else g.DrawString("Parsing Error" + '\n' + "check formula", new Font("Times New Roman", 30), Brushes.Red, 20, 20);
}
}
The Translator.
By changing the code generated by Coco\R just a little bit, it was
relatively easy to make a C# module to translate the string with the function into a series of instructions to compute the function. I wrote a few classes to generate the program to handle the computation
of the stack based calculator. Inspired by a book I read back when I got my
first computer a Commodore 64.
public class Mnemonic
{
public enum Actions
{
sin, cos, tan, add, subtract, multiply, divide, negate, number, variablex,
variabley, e, pi, abs, acos, asin, atan, cosh, exp, log, sinh, sqrt,
tanh, pow, end
}
public Actions action;
public double argument;
public Mnemonic(Actions action, double argument)
{
this.action = action;
this.argument = argument;
}
public void AddMnemonic(Actions action, double argument)
{
this.action = action;
this.argument = argument;
}
}
Then I wrote a couple of methods to execute the program that the translator
generated.
public double FunctionExecute(double x, double y, out string errorString)
{
int programcounter = 0;
Mnemonic m = assemble.GetMnemonic(programcounter);
errorString = "";
double Loperand, Roperand, Result;
while (m.action != Mnemonic.Actions.end)
{
try
{
switch (m.action)
{
case Mnemonic.Actions.sin: Loperand = stack.Pop(); Result = Math.Sin(Loperand); TestResult(Result, "Sin"); stack.Push(Result); break;
case Mnemonic.Actions.cos: Loperand = stack.Pop(); Result = Math.Cos(Loperand); TestResult(Result, "Cos"); stack.Push(Result); break;
case Mnemonic.Actions.tan: Loperand = stack.Pop(); Result = Math.Tan(Loperand); TestResult(Result, "Tan"); stack.Push(Result); break;
case Mnemonic.Actions.add: Roperand = stack.Pop(); Loperand = stack.Pop(); Result = Loperand + Roperand; ; TestResult(Result, "Add"); stack.Push(Result); break;
case Mnemonic.Actions.subtract: Roperand = stack.Pop(); Loperand = stack.Pop(); Result = Loperand - Roperand; TestResult(Result, "Subtract"); stack.Push(Result); break;
case Mnemonic.Actions.multiply: Roperand = stack.Pop(); Loperand = stack.Pop(); Result = Loperand * Roperand; TestResult(Result, "Multiply"); stack.Push(Result); break;
case Mnemonic.Actions.divide: Roperand = stack.Pop(); Loperand = stack.Pop(); Result = Loperand / Roperand; TestResult(Result, "Divide"); stack.Push(Result); break;
case Mnemonic.Actions.negate: Loperand = stack.Pop(); Result = -Loperand; TestResult(Result, "Negate"); stack.Push(Result); break;
case Mnemonic.Actions.variablex: stack.Push(x); break;
case Mnemonic.Actions.variabley: stack.Push(y); break;
case Mnemonic.Actions.number: stack.Push(m.argument); break;
case Mnemonic.Actions.e: stack.Push(Math.E); break;
case Mnemonic.Actions.pi: stack.Push(Math.PI); break;
case Mnemonic.Actions.abs: Loperand = stack.Pop(); Result = Math.Abs(Loperand); TestResult(Result, "Abs"); stack.Push(Result); break;
case Mnemonic.Actions.acos: Loperand = stack.Pop(); Result = Math.Acos(Loperand); TestResult(Result, "Acos"); stack.Push(Result); break;
case Mnemonic.Actions.asin: Loperand = stack.Pop(); Result = Math.Asin(Loperand); TestResult(Result, "Asin"); stack.Push(Result); break;
case Mnemonic.Actions.atan: Loperand = stack.Pop(); Result = Math.Atan(Loperand); TestResult(Result, "Atan"); stack.Push(Result); break;
case Mnemonic.Actions.cosh: Loperand = stack.Pop(); Result = Math.Cosh(Loperand); TestResult(Result, "Cosh"); stack.Push(Result); break;
case Mnemonic.Actions.exp: Loperand = stack.Pop(); Result = Math.Exp(Loperand); TestResult(Result, "Exp"); stack.Push(Result); break;
case Mnemonic.Actions.log: Loperand = stack.Pop(); Result = Math.Log(Loperand); TestResult(Result, "Log"); stack.Push(Result); break;
case Mnemonic.Actions.sinh: Loperand = stack.Pop(); Result = Math.Sinh(Loperand); TestResult(Result, "Sinh"); stack.Push(Result); break;
case Mnemonic.Actions.sqrt: Loperand = stack.Pop(); Result = Math.Sqrt(Loperand); TestResult(Result, "Sqrt"); stack.Push(Result); break;
case Mnemonic.Actions.tanh: Loperand = stack.Pop(); Result = Math.Tanh(Loperand); TestResult(Result, "Tanh"); stack.Push(Result); break;
case Mnemonic.Actions.pow: Roperand = stack.Pop(); Loperand = stack.Pop(); Result = Math.Pow(Loperand, Roperand); stack.Push(Result); break;
default: throw new Exception("default in Function Execute");
}
}
catch (ArithmeticException aex)
{
errorString = aex.Message;
return (0.0);
}
catch (ArgumentOutOfRangeException aor)
{
errorString = aor.Message;
return (0.0);
}
m = assemble.GetMnemonic(++programcounter);
if (programcounter > _memorySize)
{
throw new Exception("Program Counter out of bounds");
}
}
return (stack.Pop());
}
The Website.
The template for the website I downloaded for free from the Internet, the URL: http://www.freecsstemplates.org/. It is
pretty and it was easy to incorporate into the website. This template is like a framework, first you have the downloaded template-website with sample content and then you simply replace the sample content with your own. The next code snippet is
the Image.cshtml file that generates the jpg file with the graphics containing the
3D-function.
@using System.Drawing.Drawing2D;
@using System.Drawing.Imaging;
@using System.Globalization;
@using _3DFxy1_110909;
@{
Bitmap bitmap = new Bitmap(600, 600);
Graphics g = Graphics.FromImage(bitmap);
Vector vantagepoint = new Vector((double)AppState["EyeX"], (double)AppState["EyeY"], (double)AppState["EyeZ"], 1.0);
String fxystring = AppState["fstring"].ToString();
double range = (double)AppState["range"];
int raster = (int)AppState["raster"];
double scale = (double)AppState["scale"];
Function.SolidType st = Function.SolidType.Intelligent;
string ststr = AppState["solidtype"].ToString();
if (ststr == "White") { st = Function.SolidType.White; }
if (ststr == "Random") { st = Function.SolidType.Random; }
if (ststr == "Intelligent") { st = Function.SolidType.Intelligent; }
myImage img = new myImage(g,vantagepoint,fxystring,range,raster,scale, st);
Response.ContentType = "image/jpeg";
Response.AddHeader("content-disposition", "inline; filename=test.jpg");
bitmap.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
g.Dispose();
bitmap.Dispose();
}
From the _AppStart.cshtml, these are the default settings which are loaded into the variables when the application is started:
@{
AppState["EyeX"] = 120.0;
AppState["EyeY"] = 135.0;
AppState["EyeZ"] = 175.0;
AppState["vp"] = 1;
AppState["fstring"] = "cos(x)*sin(y) * pi";
AppState["range"] = 3.14;
AppState["raster"] = 20;
AppState["scale"] = 100.0;
AppState["solidtype"] = "Intelligent";
}
In the Default.cshtml you can enter the variables via the website:
@if (IsPost) {
{AppState["fstring"] = Request["finput"].ToString();}
try {AppState["range"] = Math.Abs(double.Parse(Request["domain"]));}
catch {AppState["range"] = 3.14; <h3>You entered an illegal value for Domain, will enter the default value.</h3>}
try{AppState["scale"] = Math.Abs(double.Parse(Request["scale"]));}
catch { AppState["scale"] = 100.0; <h3>You entered an illegal value for Scale, will enter the default value.</h3>}
{AppState["raster"] = int.Parse(Request["resolution"]);}
{AppState["solidtype"] = Request["solidtype"].ToString();}
{AppState["vp"] = int.Parse(Request["vantagepoint"]);}
}
Followed by this line which inserts a jpg image from the Image.cshtml file which I listed above:
<img src="@Href("~/Image.cshtml")" alt="Fxy" />
Here are two screenshots, one with input the other with the result:
There are three render modes this mode (intelligent) generates an image like a temperature chart.
Points of Interest
I really enjoyed producing this website using WebMatrix. ASP.NET Web Pages with the Razor
syntax makes it so easy to build a website with the functionality and appearance
as you would desire it to be. It is easy to understand. And fun!
History
This is my first ever article that I wrote for the Code Project website.