454 lines
35 KiB
MQL5
454 lines
35 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| DemoMorphMath3D.mq5 |
|
|
//| Copyright 2000-2025, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2000-2025, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property version "1.00"
|
|
|
|
//---
|
|
#include <Controls\Picture.mqh>
|
|
#include <Canvas\Canvas3D.mqh>
|
|
#include <Canvas\DX\DXSurface.mqh>
|
|
#include "Functions.mqh"
|
|
|
|
//--- resources
|
|
#define TEXTURE_CHECKER "::Textures/checker.bmp"
|
|
#resource "Textures/checker.bmp"
|
|
|
|
//-- input data
|
|
input double Inp_Morph_Time =3.0; // Morphing time
|
|
input double Inp_Freeze_Time=1.0; // Fixing on result time
|
|
input int Inp_Grid_Size =150; // Grid size
|
|
input color Inp_Background =clrWhite;// Background color
|
|
input bool Inp_UseChess =false; // Use chess texture
|
|
|
|
|
|
//+---------------------------------------------------------------------+
|
|
//| Chart constants |
|
|
//+---------------------------------------------------------------------+
|
|
#define CAMERA_ANGLES_TIMEOUT 3.0f
|
|
#define CAMERA_DISTANCE_TIMEOUT 3.0f
|
|
#define CAMERA_DISTANCE 40.0f
|
|
#define CAMERA_RETURN_STRENGTH 0.01f
|
|
#define CAMERA_ANGLE_Y_DEFAULT DX_PI/6.0f
|
|
#define CAMERA_ANGLE_Y_SPEED 0.3f
|
|
//+------------------------------------------------------------------+
|
|
//| GenerateDataFixedSize |
|
|
//+------------------------------------------------------------------+
|
|
bool NormalizeData(double &data[],double &data_min,double &data_max)
|
|
{
|
|
int data_size=ArraySize(data);
|
|
if(data_size<1)
|
|
return(false);
|
|
data_min=DBL_MAX;
|
|
data_max=-DBL_MAX;
|
|
for(int i=0; i<data_size; i++)
|
|
{
|
|
if(data_min>data[i])
|
|
data_min=data[i];
|
|
if(data_max<data[i])
|
|
data_max=data[i];
|
|
}
|
|
if(data_max-data_min<0.00001)
|
|
{
|
|
data_min=(data_max+data_min)/2.0-0.000005;
|
|
data_max=data_min+0.00001;
|
|
}
|
|
double scale=1.0/(data_max-data_min);
|
|
for(int i=0; i<data_size; i++)
|
|
{
|
|
data[i]*=scale;
|
|
}
|
|
data_min*=scale;
|
|
data_max*=scale;
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Application window |
|
|
//+------------------------------------------------------------------+
|
|
class CCanvas3DWindow
|
|
{
|
|
protected:
|
|
CPicture m_picture;
|
|
CCanvas3D m_canvas;
|
|
//--- canvas data
|
|
int m_width;
|
|
int m_height;
|
|
uint m_background_color;
|
|
uint m_text_color;
|
|
//--- source functions data
|
|
int morph_id1;
|
|
int morph_id2;
|
|
int m_data_size;
|
|
double m_data1[];
|
|
double m_data2[];
|
|
double m_data1_min;
|
|
double m_data2_min;
|
|
double m_data1_max;
|
|
double m_data2_max;
|
|
int m_functions_count;
|
|
//--- last frame time
|
|
double m_last_frame;
|
|
//--- morph data
|
|
double m_current_morph_factor;
|
|
double m_morpth_time;
|
|
double m_freeze_time;
|
|
//--- camera data
|
|
DXVector2 m_camera_angles;
|
|
double m_camera_angles_timeout;
|
|
double m_camera_distance;
|
|
double m_camera_distance_timeout;
|
|
//--- input data
|
|
int m_mouse_x_old;
|
|
int m_mouse_y_old;
|
|
//---
|
|
CDXSurface m_surface;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CCanvas3DWindow(void):m_mouse_x_old(-1),m_mouse_y_old(-1)
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CCanvas3DWindow(void)
|
|
{
|
|
//--- shutdown mesh
|
|
m_surface.Shutdown();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| DrawMesh |
|
|
//+------------------------------------------------------------------+
|
|
bool UpdateMesh(void)
|
|
{
|
|
//--- calculate blend value
|
|
double blend=sin(DX_PI*m_current_morph_factor/m_morpth_time-DX_PI/2.0)*0.5+0.5;
|
|
//--- blend data
|
|
double data[];
|
|
int data_size=m_data_size*m_data_size;
|
|
if(ArraySize(m_data1)<data_size || ArraySize(m_data2)<data_size || ArrayResize(data,data_size)<data_size)
|
|
return(false);
|
|
float min_value=(float)((1.0-blend)*m_data1_min+blend*m_data2_min);
|
|
float max_value=(float)((1.0-blend)*m_data1_max+blend*m_data2_max);
|
|
for(int j=0; j<m_data_size; j++)
|
|
for(int i=0; i<m_data_size; i++)
|
|
{
|
|
int idx=j*m_data_size+i;
|
|
data[idx]=(1.0-blend)*m_data1[idx]+blend*m_data2[idx];
|
|
}
|
|
//--- create new object
|
|
m_surface.Update(data,(uint)m_data_size,(uint)m_data_size,max_value-min_value,DXVector3(-10,-5,-10),DXVector3(10,5,10),DXVector2(1.0f,1.0f),CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT);
|
|
//---
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Update camera position |
|
|
//+------------------------------------------------------------------+
|
|
bool UpdateCamera(void)
|
|
{
|
|
DXVector4 camera=DXVector4(0.0f,0.0f,-(float)m_camera_distance,1.0f);
|
|
DXVector4 light =DXVector4(0.25f,-0.25f,1.0f,0.0f);
|
|
DXMatrix rotation;
|
|
DXMatrixRotationX(rotation,m_camera_angles.x);
|
|
DXVec4Transform(camera,camera,rotation);
|
|
DXVec4Transform(light, light, rotation);
|
|
DXMatrixRotationY(rotation,m_camera_angles.y);
|
|
DXVec4Transform(camera,camera,rotation);
|
|
DXVec4Transform(light, light, rotation);
|
|
m_canvas.ViewPositionSet(DXVector3(camera));
|
|
m_canvas.LightDirectionSet(DXVector3(light));
|
|
//---
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Create |
|
|
//+------------------------------------------------------------------+
|
|
virtual bool Create(const int width,const int height)
|
|
{
|
|
//--- prepare the chart
|
|
ChartSetInteger(0,CHART_SHOW,false);
|
|
ChartRedraw();
|
|
//--- save sizes
|
|
m_width=width;
|
|
m_height=height;
|
|
//---
|
|
ResetLastError();
|
|
if(!m_canvas.CreateBitmapLabel("Morphing",0,0,m_width,m_height,COLOR_FORMAT_ARGB_NORMALIZE))
|
|
{
|
|
Print("Error creating canvas: ",GetLastError());
|
|
return(false);
|
|
}
|
|
//--- set colors
|
|
m_background_color=ColorToARGB(Inp_Background);
|
|
if((GETRGBR(m_background_color)+GETRGBG(m_background_color)+GETRGBB(m_background_color))/3<128)
|
|
m_text_color=ColorToARGB(clrWhite);
|
|
else
|
|
m_text_color=ColorToARGB(clrNavy);
|
|
//---
|
|
m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
|
|
m_canvas.ViewTargetSet(DXVector3(0.0,-2.0,0.0));
|
|
m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));
|
|
m_canvas.LightColorSet(DXColor(1.0f,1.0f,0.9f,0.27f));
|
|
m_canvas.AmbientColorSet(DXColor(0.9f,0.9f,1.0f,0.8f));
|
|
//--- set camera parameters
|
|
m_camera_distance=CAMERA_DISTANCE;
|
|
m_camera_distance_timeout=0.0;
|
|
m_camera_angles=DXVector2(CAMERA_ANGLE_Y_DEFAULT,0.0);
|
|
m_camera_angles_timeout=0.0;
|
|
//--- set timers
|
|
m_morpth_time=fabs(Inp_Morph_Time);
|
|
m_freeze_time=fabs(Inp_Freeze_Time);
|
|
//--- initial and final morph function
|
|
m_functions_count=ArraySize(ExtFunctionsNames);
|
|
morph_id1=MathRand()%m_functions_count;
|
|
morph_id2=MathRand()%m_functions_count;
|
|
if(morph_id2==morph_id1)
|
|
{
|
|
morph_id2=(morph_id1+1)%m_functions_count;
|
|
}
|
|
//--- initial morph settings
|
|
m_current_morph_factor=0.0;
|
|
m_last_frame=GetMicrosecondCount()/1000000.0;
|
|
//--- save grid size with up and top limits
|
|
m_data_size=Inp_Grid_Size;
|
|
if(m_data_size<5)
|
|
m_data_size=5;
|
|
if(m_data_size>500)
|
|
m_data_size=500;
|
|
//--- generate end states data
|
|
GenerateDataFixedSize(m_data_size,m_data_size,(EnMathFunction)morph_id1,m_data1);
|
|
NormalizeData(m_data1,m_data1_min,m_data1_max);
|
|
GenerateDataFixedSize(m_data_size,m_data_size,(EnMathFunction)morph_id2,m_data2);
|
|
NormalizeData(m_data2,m_data2_min,m_data2_max);
|
|
//--- create mesh
|
|
if(!m_surface.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),m_data1,(uint)m_data_size,(uint)m_data_size,float(m_data1_max-m_data1_min),DXVector3(-10,-5,-10),DXVector3(10,5,10),DXVector2(1.0f,1.0f),CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS))
|
|
{
|
|
m_canvas.Destroy();
|
|
return(false);
|
|
}
|
|
//--- set mesh parameters
|
|
m_surface.SpecularColorSet(DXColor(1.0f,1.0f,1.0f,1.0f));
|
|
if(Inp_UseChess)
|
|
m_surface.TextureSet(m_canvas.DXDispatcher(),TEXTURE_CHECKER);
|
|
//--- add mesh to scene
|
|
m_canvas.ObjectAdd(&m_surface);
|
|
//--- succeed
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Update frame |
|
|
//+------------------------------------------------------------------+
|
|
void Redraw()
|
|
{
|
|
//--- render 3D
|
|
m_canvas.Render(DX_CLEAR_COLOR|DX_CLEAR_DEPTH,m_background_color);
|
|
//--- draw text label
|
|
int left=25,top=15;
|
|
static int pos=left;
|
|
m_canvas.FontSet("Arial",64,FW_BLACK);
|
|
//---
|
|
if(m_current_morph_factor<m_morpth_time)
|
|
{
|
|
//--- first function
|
|
m_canvas.TextOut(left,top,ExtFunctionsNames[morph_id1],m_text_color,0);
|
|
left+=m_canvas.TextWidth(ExtFunctionsNames[morph_id1]);
|
|
//--- arrow
|
|
m_canvas.TextOut(left,top-3," \x2192 ",m_text_color,0);
|
|
left+=m_canvas.TextWidth(" \x2192 ");
|
|
pos=left;
|
|
}
|
|
else
|
|
{
|
|
//--- move second function label to left while morphing freezed
|
|
double t=(m_current_morph_factor-m_morpth_time)/m_freeze_time;
|
|
t=0.5+0.5*sin(M_PI*t-M_PI/2.0);
|
|
left=(int)((1-t)*pos+t*left);
|
|
}
|
|
//--- second function
|
|
m_canvas.TextOut(left,top,ExtFunctionsNames[morph_id2],m_text_color,0);
|
|
//--- update chart
|
|
m_canvas.Update();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Process mouse moving event |
|
|
//+------------------------------------------------------------------+
|
|
void OnMouseMove(int x,int y,uint flags)
|
|
{
|
|
if((flags&1)==1)
|
|
{
|
|
if(m_mouse_x_old!=-1)
|
|
{
|
|
m_camera_angles.y+=(x-m_mouse_x_old)/300.0f;
|
|
m_camera_angles.x+=(y-m_mouse_y_old)/300.0f;
|
|
if(m_camera_angles.x<-DX_PI*0.49f)
|
|
m_camera_angles.x=-DX_PI*0.49f;
|
|
if(m_camera_angles.x>DX_PI*0.49f)
|
|
m_camera_angles.x=DX_PI*0.49f;
|
|
//---
|
|
UpdateCamera();
|
|
}
|
|
//---
|
|
m_mouse_x_old=x;
|
|
m_mouse_y_old=y;
|
|
//---
|
|
m_camera_angles_timeout=CAMERA_ANGLES_TIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
m_mouse_x_old=-1;
|
|
m_mouse_y_old=-1;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Process mouse moving event |
|
|
//+------------------------------------------------------------------+
|
|
void OnMouseWheel(double delta)
|
|
{
|
|
m_camera_distance*=1.0-delta*0.001;
|
|
if(m_camera_distance>75.0)
|
|
m_camera_distance=75.0;
|
|
if(m_camera_distance<20.0)
|
|
m_camera_distance=20.0;
|
|
UpdateCamera();
|
|
m_camera_distance_timeout=CAMERA_DISTANCE_TIMEOUT;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Process chart change event |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartChange(void)
|
|
{
|
|
//--- get current chart window size
|
|
int w=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
|
|
int h=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
|
|
//--- update size everywhere it needed
|
|
if(w!=m_width || h!=m_height)
|
|
{
|
|
m_width =w;
|
|
m_height=h;
|
|
m_canvas.Resize(w,h);
|
|
DXContextSetSize(m_canvas.DXContext(),w,h);
|
|
m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
|
|
Redraw();
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Timer handler |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer(void)
|
|
{
|
|
double current_frame=GetMicrosecondCount()/1000000.0;
|
|
double deltatime=current_frame-m_last_frame;
|
|
if(deltatime>0.1)
|
|
deltatime=0.1;
|
|
m_last_frame=current_frame;
|
|
m_current_morph_factor+=deltatime;
|
|
|
|
|
|
if(m_current_morph_factor>=m_morpth_time+m_freeze_time)
|
|
{
|
|
m_current_morph_factor=0.0;
|
|
int prev_id=morph_id1;
|
|
//--- save second data to first
|
|
morph_id1=morph_id2;
|
|
ArraySwap(m_data1,m_data2);
|
|
m_data1_min=m_data2_min;
|
|
m_data1_max=m_data2_max;
|
|
//--- generate new second data
|
|
while(morph_id2==morph_id1 || morph_id2==prev_id)
|
|
{
|
|
morph_id2=MathRand()%m_functions_count;
|
|
}
|
|
GenerateDataFixedSize(m_data_size,m_data_size,(EnMathFunction)morph_id2,m_data2);
|
|
NormalizeData(m_data2,m_data2_min,m_data2_max);
|
|
|
|
}
|
|
//--- generate morphed mesh
|
|
if(m_current_morph_factor<=m_morpth_time)
|
|
UpdateMesh();
|
|
//---
|
|
m_camera_angles_timeout-=deltatime;
|
|
if(m_camera_angles_timeout<0.0)
|
|
{
|
|
m_camera_angles.y+=(float)deltatime*CAMERA_ANGLE_Y_SPEED;
|
|
m_camera_angles.x=(1.0f-CAMERA_RETURN_STRENGTH)*m_camera_angles.x+CAMERA_RETURN_STRENGTH*CAMERA_ANGLE_Y_DEFAULT;
|
|
UpdateCamera();
|
|
}
|
|
m_camera_distance_timeout-=deltatime;
|
|
if(m_camera_distance_timeout<0.0 && m_camera_angles_timeout<0.0)
|
|
{
|
|
m_camera_distance=(1.0f-CAMERA_RETURN_STRENGTH)*m_camera_distance+CAMERA_RETURN_STRENGTH*CAMERA_DISTANCE;
|
|
UpdateCamera();
|
|
}
|
|
//---
|
|
Redraw();
|
|
}
|
|
};
|
|
|
|
//--- Global window
|
|
CCanvas3DWindow *ExtAppWindow=NULL;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
|
|
ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1);
|
|
//--- get current chart window size
|
|
int width =(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
|
|
int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
|
|
//--- create canvas
|
|
ExtAppWindow=new CCanvas3DWindow();
|
|
if(!ExtAppWindow.Create(width,height))
|
|
return(INIT_FAILED);
|
|
//--- set timer
|
|
EventSetMillisecondTimer(10);
|
|
//---
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
//--- destroy
|
|
delete ExtAppWindow;
|
|
//--- kill timer
|
|
EventKillTimer();
|
|
//--- revert chart showing mode
|
|
ChartSetInteger(0,CHART_SHOW,true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
ExtAppWindow.OnTimer();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| ChartEvent function |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
|
|
{
|
|
//---
|
|
if(id==CHARTEVENT_KEYDOWN)
|
|
{
|
|
if(lparam==27)
|
|
ExpertRemove();
|
|
}
|
|
if(id==CHARTEVENT_CHART_CHANGE)
|
|
ExtAppWindow.OnChartChange();
|
|
//--- process mouse moving
|
|
if(id==CHARTEVENT_MOUSE_MOVE)
|
|
ExtAppWindow.OnMouseMove((int)lparam,(int)dparam,(uint)sparam);
|
|
if(id==CHARTEVENT_MOUSE_WHEEL)
|
|
ExtAppWindow.OnMouseWheel(dparam);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|