WHY數學表達式的3D可視化
很早之前我就有這種想法,將數學表達式的圖形顯示出來.最近終於實現了這套較為完善的版本,將其代碼公布,也為開源做點貢獻.首先系統中定義一套腳本語言格式,用於描述數學表達式.使用時先要將數學表達式寫成該腳本的形式,解析腳本代碼以生成相應的圖形.該系統能夠生成三維曲線圖形和曲面圖形.
開發環境:VS2008,圖形渲染用的是D3D9,用於解析數學表達式的核心代碼MathExpression只使用了C的基本庫函數,可以跨平台使用.
軟件下載地址:
http://files.cnblogs.com/WhyEngine/MathGraph_2_0.zip
代碼下載地址:
http://pan.baidu.com/s/1pJkD4Bh
早期版本地址:
http://www.cnblogs.com/WhyEngine/p/3535903.html
補丁下載地址:
http://files.cnblogs.com/WhyEngine/MathExpression.zip修改了幾個表達式解析錯誤的BUG,支持科學計數格式.
[一]軟件介紹
(1)主界面



將數學腳本文件拖入到主界面中,可以顯示其圖形.亦可以通過菜單項打開腳本文件.
(2)數學腳本面板

用於打開腳本文件,編輯腳本,保存文件,解析腳本,輸出編譯信息.
(3)可視化屬性面板
用於對圖形顯示的設置,如果設置顏色,紋理等.有兩種類型的圖形,MESH和曲線.將紋理文件拖入軟件界面中后,會自動將其設置為MESH紋理.
(4)包圍盒面板
圖形包圍盒的顯示.
(5)系統配置面板

設置場景相關的屬性,並能夠保存配置文件.
(6)快捷方式
F5: 數學腳本編輯界面
F6: 可視化屬性面板
F7: 包圍盒面板
F9: 系統配置面板
F11: 全屏切換
ESC: 退出全屏
‘X’: 恢復為默認視角
'L': 開關燈光
'B': 包圍盒的可見屬性切換
'G': 地平面網格的可見屬性切換
'M': 坐標系軸的可見屬性切換
'N': 地面的可見屬性切換
'1': MESH圖形中,面片可見,線框不可見
'2': MESH圖形中,面片不可見,線框可見
'3': MESH圖形中,面片可見,線框可見
'4': MESH圖形中,使用紋理色
'5': MESH圖形中,使用頂點色
'6': MESH圖形中,使用默認色
'0': 重新編譯腳本(如果腳本中使用了隨機數函數,則重新編譯后,生成的圖形會不一樣)
[二]腳本語法
(1)常量
系統中默認有兩個常量值
PI 3.1415927
E 2.7182818
常量名通常為大寫字母,如 (PI + E) 或 sin(PI*0.5)
(2)數值解析
對整數的解析支持2,8,10,16四種進制
0X開頭為16進制, XABCDEF大小寫都可以, 如0xffff
0Y開頭為2進制, Y大小寫都可以, 如0y10101010000
0開頭為8進制, 如07523, 注意出現了"08"之類的會解析失敗
默認為10進制
對浮點數的解析只支持形如”0.12”的格式,不能省略前面的0,目前尚不支持科學表達.
(3)頂點數目
所謂頂點數目是指表達式運算時所需要的輸入數據.數據分為兩類:一維數據用於生成曲線圖形,其定義如下:
vertices = 3600 // 設置頂點數目
二維數據用於生成生成MESH圖形數據,其定義如下:
vertices = dimension1:80 dimension2:160 或
vertices = D1:80 D2:160
表示第一個維度的輸入為80,第二個維度的輸入為160,整體輸入的頂點數目為80*160.
(4)變量及其賦值
系統中使用a-z的26個英文小寫字母表示變量,變量能夠存儲單個實數,或一個實數數組.如果為實數數據,則其大小為之前設置的頂點數目(見3).
設置為單個實數
a = 3.1415 // 將a賦值為3.1415
一維數組的設置
a = from 0 to 100 // a為一個實數數組,數組大小為vertices的設置,數值為線性插值求得.
二維數據的設置
a = from 0 to (2*PI) dimension1
b = from (-PI*0.5) to (PI*0.5) dimension2
或者
a = from 0 to (2*PI) D1
b = from (-PI*0.5) to (PI*0.5) D2
變量中x,y,z將組成3D頂點位置坐標
r,g,b將組成頂點顏色.其值范圍在0-1.0之間.如果沒有設置r,g,b,將使用默認方式生成頂點色.
u,v為頂點的紋理坐標,如果沒有設置u,v,將使用x,z生成頂點的紋理坐標.
(5)運算符
a.標准單目運算符
+,-
如:a = -b
b.標准雙目運算符
+,-,*,/,%,^
如:c = a * b 如果a,b都為單個實數則運算結果c也是單個實數,否則c為實數數組
c.函數單目運算符 形如sin(a)
positive,negative,abs,floor,ceil,sign,sqrt,exp,log,log2,log10,sin,cos,tan,asin,acos,atan,rand
d.函數雙目運算符 形如pow(a, b)
add,sub,multiply,divide,max,min,mod,pow,atan2,rand2,
e.函數三目運算符 形如lerp(a, b, r)
lerp,clamp,gray,add3,min3,max3,average3
f.函數四目運算符 形如average4(a, b, c, d)
add4,min4,max4,average4
g.函數數組運算符(輸入實數數組,輸出一個浮點數,如求最大值,最小值,數組加和等)
add_array,min_array,max_array,ave_array
h.函數數組運算符(輸入實數數組,輸出也是實數數組,如求數組左移,數組右移,前向累加等)
array_move_right,array_move_left,array_cumulate
(6)運算符嵌套
支持{}, [], ()這三類括號標志符,括號必需成對出現.支持最大32級括號的嵌套.如:
(2+(-2+(5.0*(9 + ((1+2)*3))/3) + 2))
-{exp[5*sqrt(1 -abs[sin{rand(100)}]) + 6]}
(7)示例
Sin曲線
vertices = 1200
x = from (-4*PI) to (4*PI)
y = sin(x)

圓
vertices = 360
w = from 0 to (2*PI)
r = 10.0
x = r*sin(w)
y = r*cos(w)
曲線球
vertices = 3600
w = from 0 to 32
a = mod(w, 1) * 2 * PI
b = from 0 to PI
r = 10.0
x = r*sin(a)*sin(b)
y = r*cos(a)*sin(b)
z = r*cos(b)
線圈
vertices = 36000
a = rand2(8, 64)
b = rand2(4, 64)
c = a + b
s = c / b
o = rand2(4, b)
i = from 0 to (360*2*PI)
j = mod(i, 2*PI)
k = mod(s*i, 2*PI)
m = a*sin(j)
n = a*cos(j)
x = m + o*sin(k)
y = n + o*cos(k)
地形面
vertices = dimension1:320 dimension2:320
x = from (-4) to (4) dimension1
z = from (-4) to (4) dimension2
r = x^2 + z^2
y = sin(x^2 + z^2*3)/(0.05 + r) + (x^2 + z^2*5)*exp(1 -r)/2
拋物線曲面
vertices = dimension1:101 dimension2:101
x = from (-100) to (100) dimension1
z = from (-100) to (100) dimension2
y = (20000 - x^2 - z^2)*0.005
三維球體
vertices = dimension1:36 dimension2:72
a = from 0 to (2*PI) dimension1
b = from (-PI*0.5) to (PI*0.5) dimension2
r = 10.0
x = r*cos(b)*sin(a)
y = r*sin(b)
z = r*cos(b)*cos(a)
圓錐體
vertices = D1:72 D2:72
u = from 0 to (2) D2
v = from 0 to (2*PI) D1
a = 1.0
b = 0.5
c = sin(v);d = cos(v);
e = sin(b);f = cos(b);
g = sin(a);h = cos(a);
x = f*h*d - f*g*c + e*3
y = g*d + h*c
z = -e*h*d + e*g*c + f*3
x = x*u
y = y*u
z = z*u
心形
vertices = dimension1:80 dimension2:160
a = from 0 to (2*PI) dimension1
b = from (-PI*0.5) to (PI*0.5) dimension2
r = 10.0
c = sqrt(abs(a - PI))*1.5
x = r*cos(b)*sin(a)*c
y = -r*cos(b)*cos(a)*c
z = r*sin(b)*0.5
海螺
vertices = dimension1:160 dimension2:160
u = from 0 to (6*PI) dimension1
v = from 0 to (2*PI) dimension2
k = 1.2
a = 1.5
w = (k^u) * (1+cos(v))
x = w*cos(u)
y = w*sin(u)
z = (k^u)*sin(v) - (k^u)*a

環
vertices = D1:20 D2:500
u = from 0 to (2*PI) D1
v = from 0 to (2*PI) D2
a = sin(u)
b = cos(u)
c = sin(v)
d = cos(v)
r = (12 + c + b) *(1+d)
v = 10 * v
x = r*sin(v)
y = a + 32*d
z = r*cos(v)
[三]代碼與用戶自定義擴展
代碼中MathExpression模塊中為數學表達式及腳本解析的代碼邏輯.
(1)主要接口
文件”math_expression_parser.h”提供了對表達式的解析函數
// 設置參數值
void SetParameterValue(char c, float value);
// 清空參數值
void ClearParameterValues();
// 表達式解析
bool ParseExpression(const char* szExpression, float& rst);
// 括號內的子表達式解析
bool ParseBracketExpression(const char* szExpression,
unsigned int& pos, float& rst,
unsigned int bracketIndex, bool endComma);
可以針求獨立表達式的結果,如:
bool sus = ParseExpression(“sin(PI*3) + 10*E*log(100)”, rst);
文件”math_script_parser.h”提供了一個對象MathScriptParser,用於對數學腳本的解析
class MathScriptParser
{
public:
...
// 加載腳本文件
bool LoadFile(const char* szFile);
// 處理內存數據
bool ProcessMemory(const char* bufPtr, unsigned int bufSize);
...
};
(2)添加或設置常量值
開發者可以通過”const_value.h”文件中的如下函數對常量進行添加或修改
// 添加或設置常量值
bool SetConstValue(const char* szName, float value);
(3)添加或設置函數運算符
程序中的運算符操作是以函數指針的方式使用,開發者可以將自己實現的函數設置到系統中.
有6種運算符函數類型:
// 單目運算函數
typedef float (*SINGLE_OPERATOR_FUNC)(float);
// 雙目運算函數
typedef float (*TWIN_OPERATOR_FUNC)(float, float);
// 三目運算函數
typedef float (*THREE_OPERATOR_FUNC)(float, float, float);
// 四目運算函數
typedef float (*FOUR_OPERATOR_FUNC)(float, float, float, float);
// 對ValueNode的運算函數(輸入ValueNode,輸出一個浮點數)
typedef float (*VALUENODE_TO_FLOAT_OPERATOR_FUNC)(const ValueNode*);
// 對ValueNode的運算函數(輸入ValueNode,輸出ValueNode)
typedef void (*VALUENODE_CONVERT_OPERATOR_FUNC)(const ValueNode*, ValueNode*);
開發者可自定義運算函數后,使用如下接口,將其添加到系統中.
// 添加或設置運算函數
bool SetSingleOperator(const char* szName, SINGLE_OPERATOR_FUNC func);
bool SetTwinOperator(const char* szName, TWIN_OPERATOR_FUNC func);
bool SetThreeOperator(const char* szName, THREE_OPERATOR_FUNC func);
bool SetFourOperator(const char* szName, FOUR_OPERATOR_FUNC func);
bool SetValueNodeToFloatOperator(const char* szName, VALUENODE_TO_FLOAT_OPERATOR_FUNC func);
bool SetValueNodeConvertOperator2(const char* szName, VALUENODE_CONVERT_OPERATOR_FUNC func);
需要注意這兩個宏:
#define OPERATOR_NAME_LENGTH 24 // 操作符名的最大長度
#define MAX_FUNCTIONS_COUNT 256 // 運算函數的最大數目
如果不滿足要求,請自己修改這兩個宏值,不要出現內存越界.
