گرافیک های سه بعدی بااستفاده از ( DIRECTX در METATRADER 5 )

گرافیک های سه بعدی

گرافیک های سه بعدی کامپیوتری  نمایش اشیای سه بعدی را در یک صفحه تخت فراهم می­کنند. چنین اشیایی و هم­چنین موقعیت بیننده می­توانند با گذشت زمان تغییر کنند. بر این اساس، تصویر دو بعدی نیز باید ایجاد شود تا توهم عمق تصویر ایجاد شود، یعنی باید از چرخش، بزرگنمایی، تغییر در نور و غیره پشتیبانی کند. MQL5 امکان ایجاد و مدیریت گرافیک رایانه را مستقیماً در ترمینال MetaTrader 5 با استفاده از توابع DirectX فراهم می­کند. لطفاً توجه داشته باشید که کارت گرافیک های سه بعدی شما برای کارکرد توابع باید از DX 11 و Shader Model 5.0 پشتیبانی کند.

  • مدل­سازی شی
  • ایجاد یک شکل
  • محاسبه و ارائه صحنه
  • چرخش اشیا حول محور Z و Viewpoint
  • مدیریت موقعیت دوربین
  • مدیریت رنگ شی
  • چرخش و حرکت
  • کار با روشنایی
  • انیمیشن
  • موقعیت دوربین را با استفاده از ماوس کنترل کنید
  • استفاده از Textureها
  • ایجاد اشیای سفارشی
  • سطح سه بعدی مبتنی بر داده

مدل ­سازی گرافیک های سه بعدی شی

برای ترسیم یک جسم سه بعدی در یک فضای مسطح ابتدا باید مدلی از این جسم در مختصات X ، Y و Z به دست آورد. این بدان معناست که هر نقطه از سطح جسم باید با تعیین مختصات آن توصیف شود. در حالت ایده ­آل، برای حفظ کیفیت تصویر در هنگام مقیاس ­گذاری، باید تعداد نامحدودی از نقاط روی سطح جسم را توصیف کنید. در عمل، مدل­ های سه بعدی با استفاده از mesh  متشکل از چند ضلعی توصیف می­شوند. mesh  با جزییات بیش­تر با تعداد چند ضلعی بیش­تر، یک مدل واقع گرایانه­ تر را ارائه می­دهد. با این وجود، برای محاسبه چنین مدلی و ارائه گرافیک­ های سه بعدی، منابع رایانه­ ای بیش­تری لازم است.

 

گرافیک های سه بعدی

 

تقسیم چند ضلعی­ ها به مثلث، مدت­ها پیش ظهور پیدا کرد هنگامی که گرافیک کامپیوتری اولیه باید روی کارت گرافیک ضعیف اجرا می­شد. مثلث، شرح دقیق موقعیت یک قسمت کوچک سطح و هم­چنین محاسبه پارامترهای مربوطه مانند چراغ ­ها و بازتاب نور را امکان پذیر می­کند. مجموعه ای از این مثلث های کوچک امکان ایجاد یک تصویر واقعاً سه بعدی از جسم را فراهم می­کند. از این پس، چند ضلعی و مثلث به عنوان مترادف استفاده خواهند شد، زیرا تصور مثلث بسیار ساده ­تر از چند ضلعی با N رأس است.

 

یکپارچه سازی

 

با توصیف مختصات هر رأس مثلث می­توان یک مدل سه بعدی از یک جسم ایجاد کرد که امکان محاسبه بیش­تر مختصات را برای هر نقطه از جسم فراهم می­کند، حتی اگر جسم حرکت کند یا موقعیت بیننده تغییر کند. بنابراین، ما با رئوس، یال­هایی که آن­ها را متصل می­کند و وجهی که توسط یال­ها تشکیل میشود، سروکار داریم. اگر موقعیت یک مثلث مشخص باشد، می­توانیم با استفاده از قوانین جبر خطی یک حالت نرمال برای وجه ایجاد کنیم (حالت نرمال، برداری است که عمود بر سطح است). این امر اجازه می­دهد تا چگونگی روشن شدن وجه و چگونگی انعکاس نور از آن محاسبه شود.

 

گرافیک های سه بعدی

یک شی مدل را می­توان به روش­ های مختلف ایجاد کرد. توپولوژی، چگونگی تشکیل چند ضلعی­ ها به شکل mesh گرافیک سه بعدی را توصیف می­کند. یک توپولوژی خوب امکان استفاده از مینیمم تعداد چند ضلعی­ها برای توصیف یک شی را فراهم می­کند و می­تواند حرکت و چرخش شی را آسان­تر کند.

 

گرافیک های سه بعدی

 

جلوه حجم با استفاده از نورها و سایه­ ها روی چند ضلعی­های شی ایجاد می­شود. بنابراین، هدف از گرافیک کامپیوتری سه بعدی محاسبه موقعیت هر نقطه از یک جسم، محاسبه نورها و سایه­ ها و نمایش آن بر روی صفحه است.

ایجاد یک شکل باگرافیک های سه بعدی

بگذارید یک برنامه ساده بنویسیم که یک مکعب ایجاد می­کند. از کلاس CCanvas3D از کتابخانه گرافیکی سه بعدی استفاده کنید.

کلاس CCanvas3DWindow، که یک پنجره سه بعدی ارائه می­دهد، حداقل اعضا و متدها را دارد. ما به تدریج با توضیح مفاهیم گرافیکی سه بعدی که در توابع برای کار با یکپارچه سازی DirectX پیاده­ سازی شده­اند، متدهای جدیدی اضافه خواهیم کرد.

//+------------------------------------------------------------------+
//| Application window                                               |
//+------------------------------------------------------------------+
class CCanvas3DWindow
  {
protected:
   CCanvas3D         m_canvas;
   //--- canvas size
   int               m_width;
   int               m_height;
   //--- the Cube object
   CDXBox            m_box;

public:
                     CCanvas3DWindow(void) {}
                    ~CCanvas3DWindow(void) {m_box.Shutdown();}
   //-- create a scene
   virtual bool      Create(const int width,const int height){}
   //--- calculate the scene
   void              Redraw(){}
   //--- handle chart events
   void              OnChartChange(void) {}
  };

ایجاد صحنه با ایجاد صفحه آغاز می­شود. سپس پارامترهای زیر برای ماتریسprojection  تنظیم می­شوند:

  • یک زاویه دید 30 درجه (M_PI / 6)، که از آن به صحنه سه بعدی نگاه می­کنیم.
  • نسبت ابعاد به عنوان نسبت عرض و ارتفاع
  • فاصله تا near clipping plane (1f) و far clipping plane (100. f)

این بدان معنی است که فقط اشیای بین این دو دیوار مجازی (0.1f و 100. f) در ماتریس projection ارائه می­شوند. علاوه بر این، شی باید در زاویه دید 30 درجه افقی قرار گیرد. لطفا توجه داشته باشید که فواصل و هم­چنین همه مختصات موجود در گرافیک رایانه­ ای مجازی هستند. آن­چه مهم است روابط بین فاصله­ ها و اندازه­ ها است، اما نه مقادیر مطلق.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
      //--- save canvas dimensions
      m_width=width;
      m_height=height;
      //--- create a canvas to render a 3D scene
      ResetLastError();
      if(!m_canvas.CreateBitmapLabel("3D Sample_1",0,0,m_width,m_height,COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Error creating canvas: ",GetLastError());
         return(false);
         }
      //--- set projection matrix parameters - angle of view, aspect ratio, distance to the near and far clip planes
      m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,5.0),DXVector3(1.0,1.0,7.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

پس از ایجاد ماتریس projection، می­توانیم به ساخت شی 3D  – مکعبی مبتنی بر کلاس CDXBox بپردازیم. برای ایجاد یک مکعب، کافی است دو بردار را نشان دهید که به گوشه ­های مخالف مکعب اشاره دارند. با تماشای ایجاد مکعب در حالت debug، می­توانید ببینید که در DXComputeBox ()  چه اتفاقی می­افتد: ایجاد همه رئوس مکعب (مختصات آن­ها در آرایه “رئوس” نوشته شده است)، هم­چنین تقسیم یال­ های مکعب به مثلث که در آرایه indiсesبرشمرده و ذخیره می­شوند. در مجموع، مکعب دارای 8 رأس 6 وجهی است که به 12 مثلث تقسیم می­شود و 36 indiсes که رئوس این مثلث ­ها را برشمرده­اند.

اگرچه این مکعب فقط 8 رأس دارد، 24 بردار برای توصیف آن­ها ایجاد می­شود، زیرا مجموعه جداگانه­ ای از رئوس که دارای حالت نرمال هستند باید برای هر 6 وجه مشخص شود. جهت نرمال در محاسبه نور روی هر وجه تأثیر می­گذارد. ترتیب قرارگیری رأس ­های یک مثلث در indiсes تعیین می­کند که کدام یک از اضلاع آن قابل مشاهده باشد. ترتیب پر شدن رئوس و شاخص­ ها در کد DXUtils.mqh نشان داده شده است:

   for(int i=20; i<24; i++)
      vertices[i].normal=DXVector4(0.0,-1.0,0.0,0.0);

مختصات texture  برای نگاشت texture  برای هر وجه در همان کد شرح داده شده است:

//--- texture coordinates
   for(int i=0; i<faces; i++)
     {
      vertices[i*4+0].tcoord=DXVector2(0.0f,0.0f);
      vertices[i*4+1].tcoord=DXVector2(1.0f,0.0f);
      vertices[i*4+2].tcoord=DXVector2(1.0f,1.0f);
      vertices[i*4+3].tcoord=DXVector2(0.0f,1.0f);
      }

هر 4 بردار وجه یکی از 4 زاویه را برای نگاشت texture  تعیین می­کند. این بدان معنی است که یک ساختار دست ه­ای به هر وجه مکعب نگاشت می­شود تا texture،  render شود. البته این فقط در صورت تنظیم texture  مورد نیاز است.

محاسبه و ارائه صحنه در گرافیک های سه بعدی

در هر بار تغییر صحنه 3D ، همه محاسبات باید از نو انجام شود. در این­جا ترتیب محاسبات مورد نیاز آمده است:

  • مرکز هر شی را در مختصات جهانی محاسبه کنید
  • موقعیت هر عنصر از شی، یعنی هر رأس را محاسبه کنید
  • عمق پیکسل و قابلیت مشاهده بودن آن را برای بیننده تعیین کنید
  • موقعیت هر پیکسل را روی چند ضلعی مشخص شده توسط رئوس آن محاسبه کنید
  • موقعیت هر پیکسل را روی چند ضلعی مشخص شده توسط رئوس آن محاسبه کنید
  • رنگ هر پیکسل را متناسب با texture مشخص شده روی چند ضلعی تنظیم کنید
  • جهت پیکسل نور و بازتاب آن را محاسبه کنید
  • نور پراکنده را به هر پیکسل اعمال کنید
  • همه مختصات جهانی را به مختصات دوربین تبدیل کنید
  • مختصات دوربین را به مختصات ماتریس projection تبدیل کنید

همه این عملیات در متد Render از شی CCanvas3D انجام می­شود. پس از render، با فراخوانی متد Update، تصویر محاسبه شده از ماتریس projection به canvas منتقل می­شود.

   //+------------------------------------------------------------------+
   //| Update the scene                                                 |
   //+------------------------------------------------------------------+
   void              Redraw()
     {
      //--- calculate the 3D scene
      m_canvas.Render(DX_CLEAR_COLOR|DX_CLEAR_DEPTH,ColorToARGB(clrBlack));
      //--- update the picture on the canvas in accordance with the current scene
      m_canvas.Update();
      }

در مثال ما، مکعب فقط یک بار ایجاد می­شود و دیگر تغییری نمی­کند. بنابراین، تنها در صورت تغییر در نمودار، مانند تغییر اندازه نمودار، باید فریم روی canvas تغییر یابد. در این حالت، ابعاد canvas با ابعاد نمودار فعلی تنظیم می­شود، ماتریس projection مجدداً تنظیم می­شود و یک تصویر روی canvas به روز می­شود.

   //+------------------------------------------------------------------+
   //| Process chart change event                                       |
   //+------------------------------------------------------------------+
   void              OnChartChange(void)
     {
      //--- get current chart sizes
      int w=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
      int h=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
      //--- update canvas dimensions in accordance with the chart size
      if(w!=m_width || h!=m_height)
        {
         m_width =w;
         m_height=h;
         //--- resize canvas
         m_canvas.Resize(w,h);
         DXContextSetSize(m_canvas.DXContext(),w,h);
         //--- update projection matrix in accordance with the canvas sizes
         m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
         //--- recalculate 3D scene and render it onto the canvas
         Redraw();
         }
      }

“Step1 Create Box.mq5” EA را راه­اندازی کنید. یک مربع سفید روی زمینه سیاه خواهید دید. به طور پیش فرض، رنگ سفید برای سطح اشیا در هنگام ایجاد تنظیم شده است. روشنایی هنوز تنظیم نشده است.

گرافیک های سه بعدی

محور X به سمت راست، Y به سمت بالا و Z به سمت داخل صحنه 3D هدایت می­شود. به چنین سیستم مختصاتی چپ دستی گفته می­شود.

مرکز مکعب در نقطه­ای ­با مختصات X = 0 ، Y = 0 ، Z = 6 قرار دارد. موقعیتی که از آن به مکعب نگاه می­کنیم در مرکز مختصات است که مقدار پیش فرض است. اگر می­خواهید موقعیتی را که صحنه 3D از آن مشاهده می­شود تغییر دهید، مختصات مناسب را با استفاده از تابع ViewPositionSet () مشخص کنید.

برای تکمیل عملیات برنامه،  “Escape” را فشار دهید.

چرخش اشیا حول محور Z و Viewpoint

برای متحرک­ سازی صحنه، اجازه دهید چرخش مکعب را حول محور Z فعال کنیم. برای این کار، یک تایمر اضافه کنید – بر اساس رویدادهای آن مکعب در خلاف جهت عقربه­ های ساعت چرخانده می­شود.

با استفاده از متد DXMatrixRotationZ ()  یک ماتریس چرخش ایجاد کنید تا بتواند چرخش حول محور Z را در یک زاویه مشخص انجام دهد. سپس آن را به عنوان یک پارامتر به متد  TransformMatrixSet () منتقل کنید. این، وضعیت مکعب را در فضای سه بعدی تغییر می­دهد. مجدداً Redraw () را برای به روزرسانی تصویر روی canvas، فراخوانی کنید.

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables for calculating the rotation angle
      static ulong last_time=0;
      static float angle=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the angle of rotation of the cube around the Z axis
      angle+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- set the angle of rotation of the cube around the Z axis
      DXMatrix rotation;
      DXMatrixRotationZ(rotation,angle);
      m_box.TransformMatrixSet(rotation);
      //--- recalculate 3D scene and render it onto the canvas
      Redraw();
      }

پس از راه­ اندازی، یک مربع سفید چرخشی مشاهده خواهید کرد.

برنامه نویسی گرافیک های سه بعدی

کد منبع این مثال در فایل ” ” Step2 Rotation Z.mq5موجود است. لطفاً توجه داشته باشید که اکنون هنگام ایجاد صحنه، زاویه M_PI / 5 مشخص شده است که بزرگ­تر از زاویه M_PI / 6 مثال قبلی است.

      //--- set projection matrix parameters - angle of view, aspect ratio, distance to the near and far clip planes
      m_matrix_view_angle=(float)M_PI/5;
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube

با این حال، ابعاد مکعب در صفحه از نظر بصری کوچک­تر است. هرچه زاویه دید کم­تری هنگام تنظیم ماتریس   projection   مشخص شود، قسمت بزرگ­تر فریم توسط شی اشغال می­شود. این را می­توان با دیدن اشیا با تلسکوپ مقایسه کرد: جسم بزرگ­تر است، گرچه زاویه دید کوچک­تر است.

مدیریت موقعیت دوربین

کلاس CCanvas3D دارای سه متد برای تنظیم پارامترهای مهم صحنه 3D است که بهم پیوسته­ اند:

  • ViewPositionSet، viewpoint صحنه سه بعدی را تنظیم می­کند
  • ViewTargetSet مختصات نقطه gaze را تنظیم می­کند
  • ViewUpDirectionSet جهت مرز بالای فریم را در فضای سه بعدی تنظیم می­کند

همه این پارامترها به صورت ترکیبی استفاده می­شوند – این بدان معنی است که اگر می­خواهید هر یک از این پارامترها را در صحنه سه بعدی تنظیم کنید، دو پارامتر دیگر نیز باید مقداردهی اولیه شوند. این باید حداقل در مرحله تولید صحنه انجام شود. این در مثال زیر نشان داده شده است، که در آن مرز بالای فریم به چپ و راست چرخانده می­شود. این نوسان با اضافه کردن سه خط کد زیر در متد Create() اجرا می­شود:

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
....       
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- set the scene parameters
      m_canvas.ViewUpDirectionSet(DXVector3(0,1,0));  // set the direction vector up, along the Y axis  
      m_canvas.ViewPositionSet(DXVector3(0,0,0));     // set the viewpoint from the center of coordinates
      m_canvas.ViewTargetSet(DXVector3(0,0,6));       // set the gaze point at center of the cube      
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

متد OnTimer () را تغییر دهید تا بردار افقی به چپ و راست تغییر کند.

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables for calculating the rotation angle
      static ulong last_time=0;
      static float max_angle=(float)M_PI/30;
      static float time=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the angle of rotation of the cube around the Z axis
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- set the rotation angle around the Z axis
      DXVector3 direction=DXVector3(0,1,0);     // initial direction of the top
      DXMatrix rotation;                        // rotation vector      
      //--- calculate the rotation matrix 
      DXMatrixRotationZ(rotation,float(MathSin(time)*max_angle));
      DXVec3TransformCoord(direction,direction,rotation);
      m_canvas.ViewUpDirectionSet(direction);   // set the new direction of the top
      //--- recalculate 3D scene and render it onto the canvas
      Redraw();
      }

مثال را با  عنوان  ” Step3 ViewUpDirectionSet.mq5″ذخیره کرده و آن را اجرا کنید. تصویر مکعب در حال چرخش را مشاهده خواهید کرد، گرچه در واقع بی حرکت است. این اثر زمانی بدست می­آید که دوربین خودش به چپ و راست بچرخد.

سه بعدی

مدیریت رنگ شی در گرافیک های سه بعدی

ضمن حرکت دادن دوربین، اجازه دهید کد خود را اصلاح کرده و مکعب را در مرکز مختصات قرار دهیم.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set the color 
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- set positions for camera, gaze and direction of the top
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));  // set the direction vector up, along the Y axis
      m_canvas.ViewPositionSet(DXVector3(3.0,2.0,-5.0));    // set camera on the right, on top and in front of the cube
      m_canvas.ViewTargetSet(DXVector3(0,0,0));             // set the gaze direction at center of the cube
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

علاوه بر این، مکعب را به رنگ آبی رنگ کنید. رنگ در قالب یک رنگ RGB با یک کانال آلفا تنظیم شده است (کانال آلفا بعداً نشان داده شده است)، اگرچه مقادیر به یک، نرمال می­شوند. بنابراین مقدار 1 به معنی 255 و 5.0 به معنی 127 است.

چرخش را به دور محور X اضافه کنید و تغییرات را با عنوان   “Step4 Box Color.mq5” ذخیره کنید.

گرافیک های سه بعدی

چرخش و حرکت درگرافیک های سه بعدی

اشیا را می­توان همزمان در سه جهت جابه ­جا کرد و چرخاند. تمام تغییرات شی با استفاده از ماتریس­ها پیاده­ سازی می­شوند. هر یک از آن­ها، به عنوان مثال چرخش، حرکت و تغییر شکل، می­تواند به طور جداگانه محاسبه شود. بگذارید مثال را تغییر دهیم: نمای دوربین اکنون از بالا و جلو است.

 

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- position the camera in top and in front of the center of coordinates
      m_canvas.ViewPositionSet(DXVector3(0.0,2.0,-5.0));
      m_canvas.ViewTargetSet(DXVector3(0.0,0.0,0.0));
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));      
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set the cube color
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- calculate the cube position and the transfer matrix
      DXMatrix rotation,translation;
      //--- rotate the cube sequentially along the X, Y and Z axes
      DXMatrixRotationYawPitchRoll(rotation,(float)M_PI/4,(float)M_PI/3,(float)M_PI/6);
      //-- move the cube to the right/downward/inward
      DXMatrixTranslation(translation,1.0,-2.0,5.0);
      //--- get the transformation matrix as a product of rotation and transfer
      DXMatrix transform;
      DXMatrixMultiply(transform,rotation,translation);
      //--- set the transformation matrix 
      m_box.TransformMatrixSet(transform);      
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);    
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

ماتریس چرخش و انتقال را به ترتیب ایجاد کنید، ماتریس تبدیل حاصل را اعمال کنید و مکعب را ارائه دهید. تغییرات را در  ” Step5 Translation.mq5″ ذخیره کرده و آن را اجرا کنید.

گرافیک های سه بعدی

دوربین هنوز هست و کمی از بالا به مرکز مختصات اشاره شده است. مکعب در سه جهت چرخانده شد و به سمت راست، پایین و داخل صحنه منتقل شد.

کار با روشنایی

برای به دست آوردن یک تصویر سه بعدی واقع گرایانه، لازم است نور هر نقطه از سطح جسم را محاسبه کنید. این کار با استفاده از مدل سایه زنی Phong انجام می­شود، که شدت رنگ سه مولفه روشنایی زیر را محاسبه می­کند: محیط، انتشار و بازتاب. این پارامترها در این­جا استفاده می­شود:

  • DirectionLight – جهت روشنایی جهت دار در CCanvas3D تنظیم شده است
  • AmbientLight – رنگ و شدت نور محیط در CCanvas3D تنظیم شده است
  • DiffuseColor – مولفه روشنایی انتشار محاسبه شده در CDXMesh و کلاس­های فرزند آن تنظیم شده است
  • EmissionColor – مولفه نور پس زمینه در CDXMesh و کلاس­های فرزند آن تنظیم شده است
  • SpecularColor – مولفه بازتاب در CDXMesh و کلاس­های فرزند آن تنظیم شده است

 محاسبه و ارائه صحنه

مدل روشنایی در سایه زنی استاندارد پیاده­سازی می­شود، پارامترهای مدل در CCanvas3D و پارامترهای شی در CDXMesh و کلاس­های فرزند آن تنظیم می­شوند. مثال را به صورت زیر تغییر دهید:

  • مکعب را به مرکز مختصات برگردانید.
  • آن را روی رنگ سفید قرار دهید.
  • یک منبع جهت دار از رنگ زرد اضافه کنید که صحنه را از بالا به پایین روشن می­کند.
  • رنگ آبی را برای روشنایی غیر جهت دار تنظیم کنید.
      //--- set yellow color for the source and direct it from above downwards
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- set the blue color for the ambient light 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));          
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set the white color for the cube
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0)); 
      //--- add green glow for the cube (emission)
      m_box.EmissionColorSet(DXColor(0.0,1.0,0.0,0.2f));

لطفاً توجه داشته باشید که موقعیت منبع نور هدایت شده در Canvas3D تنظیم نشده است، در حالی که فقط جهتی است که در آن نور پخش می­شود. منبع نور جهت دار در یک فاصله بی نهایت در نظر گرفته می­شود و یک جریان نوری کاملاً موازی صحنه را روشن می­کند.

m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));

در این­جا، بردار پخش نور در امتداد محور Y در جهت منفی نشان داده می­شود، یعنی از بالا به پایین. بعلاوه، اگر پارامترهایی را برای منبع نور هدایت شده تنظیم کنید (LightColorSet و LightDirectionSet)، باید رنگ نور محیط (AmbientColorSet) را نیز تعیین کنید. به طور پیش فرض، رنگ نور محیط با حداکثر شدت روی سفید تنظیم شده و بنابراین تمام سایه­ ها سفید خواهند بود. این بدان معنی است که اشیای موجود در صحنه با نور سفید از نور محیط پرتاب می­شوند، در حالی که نور منبع جهت دار با نور سفید قطع می­شود.

      //--- set yellow color for the source and direct it from above downwards
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- set the blue color for the ambient light 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));  // must be specified

انیمیشن gif زیر نحوه تغییر تصویر هنگام اضافه کردن نور را نشان می­دهد. کد منبع مثال در فایل ” Step6 Add Light.mq5  ” موجود است.

گرافیک های سه بعدی

سعی کنید روش های رنگ را در کد بالا خاموش کنید تا ببینید چطور کار می­کند.

انیمیشن با گرافیک های سه بعدی

انیمیشن به معنای تغییر پارامترهای صحنه و اشیا در طول زمان است. هر ویژگی موجود بسته به زمان یا رویدادها قابل تغییر است. تایمر را برای 10 میلی ثانیه تنظیم کنید – این رویداد روی به روزرسانی صحنه تأثیر می­گذارد:

int OnInit()
  {
...
//--- create canvas
   ExtAppWindow=new CCanvas3DWindow();
   if(!ExtAppWindow.Create(width,height))
      return(INIT_FAILED);
//--- set timer
   EventSetMillisecondTimer(10);
//---
   return(INIT_SUCCEEDED);
   }

کنترل کننده رویداد مناسب را به CCanvas3DWindow اضافه کنید. ما باید پارامترهای شی (مانند چرخش، حرکت و بزرگنمایی) و جهت روشنایی را تغییر دهیم:

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {    
      static ulong last_time=0;
      static float time=0;       
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the elapsed time value
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- calculate the cube position and the rotation matrix
      DXMatrix rotation,translation,scale;
      DXMatrixRotationYawPitchRoll(rotation,time/11.0f,time/7.0f,time/5.0f);
      DXMatrixTranslation(translation,(float)sin(time/3),0.0,0.0);
      //--- calculate the cube compression/extension along the axes
      DXMatrixScaling(scale,1.0f+0.5f*(float)sin(time/1.3f),1.0f+0.5f*(float)sin(time/1.7f),1.0f+0.5f*(float)sin(time/1.9f));
      //--- multiply the matrices to obtain the final transformation
      DXMatrix transform;
      DXMatrixMultiply(transform,scale,rotation);
      DXMatrixMultiply(transform,transform,translation);
      //--- set the transformation matrix
      m_box.TransformMatrixSet(transform);
      //--- calculate the rotation of the light source around the Z axis
      DXMatrixRotationZ(rotation,deltatime);
      DXVector3 light_direction;
      //--- get the current direction of the light source
      m_canvas.LightDirectionGet(light_direction);
      //--- calculate the new direction of the light source and set it
      DXVec3TransformCoord(light_direction,light_direction,rotation);
      m_canvas.LightDirectionSet(light_direction);
      //--- recalculate the 3D scene and draw it in the canvas
      Redraw();
      }

لطفاً توجه داشته باشید که تغییرات شی بر روی مقادیر اولیه اعمال می­شوند، گویی که ما همیشه با وضعیت مکعب اولیه سروکار داریم و همه عملیات مربوط به چرخش / حرکت / فشرده­ سازی را از ابتدا اعمال می­کنیم، به این معنی که حالت فعلی مکعب ذخیره نشده است. با این حال، جهت منبع نور با افزایش زمان deltatime از مقدار فعلی تغییر می­کند.

گرافیک های سه بعدی

نتیجه، یک انیمیشن سه بعدی بسیار پیچیده است. کد نمونه در فایل ” Step7 Animation.mq5″ موجود است.

موقعیت دوربین را با استفاده از ماوس کنترل کنید

بگذارید آخرین عنصر انیمیشن را در گرافیک سه بعدی، واکنش به اقدامات کاربر، در نظر بگیریم. با استفاده از ماوس در مثال ما مدیریت دوربین را اضافه کنید. ابتدا، رویدادهای ماوس را  تأیید کنید و کنترل­ کننده­ های مربوطه را ایجاد کنید:

int OnInit()
  {
...
//--- set the timer
   EventSetMillisecondTimer(10);
//--- enable receiving of mouse events: moving and button clicks
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1)
//---
   return(INIT_SUCCEEDED);
   }
void OnDeinit(const int reason)
  {
//--- Deleting the timer
   EventKillTimer();
//--- disable the receiving of mouse events
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,0);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,0);
//--- delete the object
   delete ExtAppWindow;
//--- return chart to the usual display mode with price charts
   ChartSetInteger(0,CHART_SHOW,true);
   }
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- chart change event
   if(id==CHARTEVENT_CHART_CHANGE)
      ExtAppWindow.OnChartChange();
//--- mouse movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
      ExtAppWindow.OnMouseMove((int)lparam,(int)dparam,(uint)sparam);
//--- mouse wheel scroll event
   if(id==CHARTEVENT_MOUSE_WHEEL)
      ExtAppWindow.OnMouseWheel(dparam);

در CCanvas3DWindow، کنترل کننده رویداد حرکت ماوس را ایجاد کنید. با حرکت دکمه سمت چپ ماوس، زوایای جهت دوربین تغییر خواهد کرد:

   //+------------------------------------------------------------------+
   //| Handle mouse movements                                           |
   //+------------------------------------------------------------------+
   void              OnMouseMove(int x,int y,uint flags)
     {
      //--- left mouse button
      if((flags&1)==1)
        {
         //--- there is no information about the previous mouse position
         if(m_mouse_x!=-1)
           {
            //--- update the camera angle upon change of position
            m_camera_angles.y+=(x-m_mouse_x)/300.0f;
            m_camera_angles.x+=(y-m_mouse_y)/300.0f;
            //--- set the vertical angle in the range between (-Pi/2,Pi2)
            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;
            //--- update camera position
            UpdateCameraPosition();
            }
         //--- save mouse position
         m_mouse_x=x;
         m_mouse_y=y;
         }
      else
        {
         //--- reset the saved position if the left mouse button is not pressed
         m_mouse_x=-1;
         m_mouse_y=-1;
         }
      }

در اینجا کنترل کننده رویداد چرخ ماوس قرار دارد که فاصله دوربین و مرکز صحنه را تغییر می­دهد:

   //+------------------------------------------------------------------+
   //| Handling mouse wheel events                                      |
   //+------------------------------------------------------------------+
   void              OnMouseWheel(double delta)
     {
      //--- update the distance between the camera and the center upon a mouse scroll
      m_camera_distance*=1.0-delta*0.001;
      //--- set the distance in the range between [3,50]
      if(m_camera_distance>50.0)
         m_camera_distance=50.0;
      if(m_camera_distance<3.0)
         m_camera_distance=3.0;
      //--- update camera position
      UpdateCameraPosition();
      }

هر دو کنترل کننده برای به روزرسانی موقعیت دوربین با توجه به پارامترهای به روز شده، از متد  UpdateCameraPosition ()  استفاده می­کنند:

   //+------------------------------------------------------------------+
   //| Updates the camera position                                      |
   //+------------------------------------------------------------------+
   void              UpdateCameraPosition(void)
     {
      //--- the position of the camera taking into account the distance to the center of coordinates
      DXVector4 camera=DXVector4(0.0f,0.0f,-(float)m_camera_distance,1.0f);
      //--- camera rotation around the X axis
      DXMatrix rotation;
      DXMatrixRotationX(rotation,m_camera_angles.x);
      DXVec4Transform(camera,camera,rotation);
      //--- camera rotation around the Y axis
      DXMatrixRotationY(rotation,m_camera_angles.y);
      DXVec4Transform(camera,camera,rotation);
      //--- set camera to position
      m_canvas.ViewPositionSet(DXVector3(camera));
      }

کد منبع در فایل  ” Step8 Mouse Control.mq5 “در زیر موجود است.

یکپارچه سازی

استفاده از Textureها در گرافیک های سه بعدی

Texture یک تصویر bitmap است که برای نشان دادن الگوها یا مواد، روی سطح چند ضلعی اعمال می­شود. استفاده از texture باعث تولید مجدد اشیای کوچک روی سطح می­شود که اگر ما آن­ها را با استفاده از چند ضلعی ایجاد کنیم، به منابع زیادی نیاز خواهیم داشت. به عنوان مثال، این می­تواند تقلیدی از یک سنگ، چوب، خاک و سایر مواد باشد.

CDXMesh و کلاس­ های فرزند آن امکان تعیین texture را دارند. در سایه زن پیکسل استاندارد این texture همراه با DiffuseColor استفاده می­شود. انیمیشن شی را بردارید و یک texture سنگی اعمال کنید. این باید در فولدر MQL5 \ Files از دایرکتوری کاری ترمینال قرار داشته باشد:

   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- set the white color for the non-directional lighting
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0));

      //--- add texture to draw the cube faces
      m_box.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

گرافیک های سه بعدی

ایجاد اشیای سفارشی

همه اجسام از رئوس (DXVector3) تشکیل شده ­اند که با استفاده از شاخص ­ها به primitive ها متصل می­شوند. معمول­ ترین primitive  مثلث است. یک شی سه بعدی اساسی با ایجاد لیستی از رئوس که حداقل شامل مختصات است (اما هم­چنین می­تواند حاوی بسیاری از داده ­های اضافی مانند نرمال، رنگی و غیره باشد)، نوع primitiveهایی که در آن ترکیب می­شوند و یک لیستی از شاخص ­های رأس که توسط آن­ها در primitive  ترکیب می­شوند ایجاد می­شود.

کتابخانه استاندارد از نوع رأس DXVertex برخوردار است که شامل مختصات آن است، که برای محاسبه روشنایی، مختصات texture و رنگ، طبیعی است. سایه زن رأس استاندارد با این نوع رأس کار می­کند.

struct DXVertex
  {
   DXVector4         position;  // vertex coordinates
   DXVector4         normal;    // normal vector
   DXVector2         tcoord;    // face coordinate to apply the texture
   DXColor           vcolor;    // color
  };

نوع کمکی MQL5 \ Include \ Canvas \ DXDXUtils.mqh شامل مجموعه­ ای از متدها برای تولید هندسه (رئوس و شاخص ­ها) از primitiveهای اولیه و بارگیری هندسه سه بعدی از فایل­های .OBJ است.

ایجاد کره و torus را اضافه کنید، همان texture سنگ را اعمال کنید:

   virtual bool      Create(const int width,const int height)
     {
 ...     
      // --- vertices and indexes for manually created objects
      DXVertex vertices[];
      uint indices[];
      //--- prepare vertices and indices for the sphere
      if(!DXComputeSphere(0.3f,50,vertices,indices))
         return(false);
      //--- set white color for the vertices
      DXColor white=DXColor(1.0f,1.0f,1.0f,1.0f);
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- create the sphere object
      if(!m_sphere.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set diffuse color for the sphere
      m_sphere.DiffuseColorSet(DXColor(0.0,1.0,0.0,1.0));
      //--- set white specular color
      m_sphere.SpecularColorSet(white);
      m_sphere.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the sphere to a scene
      m_canvas.ObjectAdd(&m_sphere);
      //--- prepare vertices and indices for the torus
      if(!DXComputeTorus(0.3f,0.1f,50,vertices,indices))
         return(false);
      //--- set white color for the vertices
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- create the torus object
      if(!m_torus.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set diffuse color for the torus
      m_torus.DiffuseColorSet(DXColor(0.0,0.0,1.0,1.0));
      m_torus.SpecularColorSet(white);
      m_torus.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the torus to a scene
      m_canvas.ObjectAdd(&m_torus);      
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

برای اشیای جدید انیمیشن در  گرافیک های سه بعدی اضافه کنید:

   void              OnTimer(void)
     {
...
      m_canvas.LightDirectionSet(light_direction);
      //--- sphere orbit
      DXMatrix translation;
      DXMatrixTranslation(translation,1.1f,0,0);
      DXMatrixRotationY(rotation,time);
      DXMatrix transform;
      DXMatrixMultiply(transform,translation,rotation);
      m_sphere.TransformMatrixSet(transform);
      //--- torus orbit with rotation around its axis
      DXMatrixRotationX(rotation,time*1.3f);
      DXMatrixTranslation(translation,-2,0,0);
      DXMatrixMultiply(transform,rotation,translation);
      DXMatrixRotationY(rotation,time/1.3f);
      DXMatrixMultiply(transform,transform,rotation);
      m_torus.TransformMatrixSet(transform);           
      //--- recalculate the 3D scene and draw it in the canvas
      Redraw();
      }

تغییرات را به صورت Three Objects.mq5 ذخیره کرده و آن را اجرا کنید.

( DIRECTXدرMETATRADER 5)

سطح گرافیک های سه بعدی مبتنی بر داده

نمودارهای مختلفی معمولاً برای ایجاد گزارش و تجزیه و تحلیل داده ­ها مانند نمودارهای خطی، هیستوگرام­ ها، نمودارهای دایره­ای و غیره استفاده میشود.

کلاس CDXSurface امکان تجسم یک سطح را با استفاده از داده­ های سفارشی ذخیره شده در یک آرایه دو بعدی فراهم می­کند. اجازه دهید مثالی از تابع ریاضی زیر را مشاهده کنیم.

z=sin(2.0*pi*sqrt(x*x+y*y))

یک شی برای رسم سطح و یک آرایه برای ذخیره داده ایجاد کنید:

   virtual bool      Create(const int width,const int height)
     {
...
      //--- prepare an array to store data
      m_data_width=m_data_height=100;
      ArrayResize(m_data,m_data_width*m_data_height);
      for(int i=0;i<m_data_width*m_data_height;i++)
         m_data[i]=0.0;
      //--- create a surface object
      if(!m_surface.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),m_data,m_data_width,m_data_height,2.0f,
                           DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                           CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- create texture and reflection
      m_surface.SpecularColorSet(DXColor(1.0,1.0,1.0,1.0));
      m_surface.TextureSet(m_canvas.DXDispatcher(),"checker.bmp");
      //--- add the surface to the scene
      m_canvas.ObjectAdd(&m_surface);
      //--- succeed
      return(true);
      }

سطح در یک جعبه با پایه 4×4 و ارتفاع 1 رسم می­شود. ابعاد texture،  0.25×0.25 است.

  • SF_TWO_SIDED نشان می­دهد که در صورت حرکت دوربین در زیر سطح، سطح، بالا و پایین آن ترسیم می­شود.
  • SF_USE_NORMALS نشان می­دهد که از محاسبات نرمال برای محاسبه بازتاب از سطح ناشی از منبع نور جهت دار استفاده خواهد شد.
  • CS_COLD_TO_HOT رنگ­آمیزی حرارت سطح را از آبی به قرمز با عبور از سبز و زرد تنظیم می­کند.

برای تحریک سطح، زمان را زیر علامت سینوس اضافه کنید و آن را با تایمر به روز کنید.

   void              OnTimer(void)
     {
      static ulong last_time=0;
      static float time=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the elapsed time value
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- calculate surface values taking into account time changes
      for(int i=0; i<m_data_width; i++)
        {
         double x=2.0*i/m_data_width-1;
         int offset=m_data_height*i;
         for(int j=0; j<m_data_height; j++)
           {
            double y=2.0*j/m_data_height-1;
            m_data[offset+j]=MathSin(2.0*M_PI*sqrt(x*x+y*y)-2*time);
            }
         }
      //--- update data to draw the surface
      if(m_surface.Update(m_data,m_data_width,m_data_height,2.0f,
                          DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                          CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         //--- recalculate the 3D scene and draw it in the canvas
         Redraw();
         }
      }

کد منبع در Surface.mq5 سه بعدی موجود است، مثال برنامه در فیلم نشان داده شده است.

در این مقاله، ما توانایی­ های DirectX را در ایجاد اشکال هندسی ساده و گرافیک سه بعدی متحرک برای تجزیه و تحلیل داده ­های بصری در نظر گرفته ایم. نمونه ­های پیچیده­ تری را می­توان در فهرست نصب ترمینال گرافیک های سه بعدی MetaTrader 5 یافت: مشاوران خبره ” Correlation Matrix 3D ” و  ” Math 3D Morpher “و هم­چنین اسکریپت  Remnant 3D

MQL5 شما را قادر می­سازد بدون استفاده از بسته ­های شخص ثالث، کارهای مهم تجاری الگوریتمی را حل کنید:

  • استراتژی­ های پیچیده تجارت را که حاوی پارامترهای ورودی بسیاری هستند بهینه کنید
  • نتایج بهینه سازی را به دست آورید
  • داده ها را در راحت­ ترین فروشگاه گرافیک های سه بعدی تجسم کنید

از قابلیت­ های پیشرفته برای تجسم داده­ های سهام و توسعه استراتژی­ های معاملاتی در MetaTrader 5 – اکنون با گرافیک های سه بعدی استفاده کنید!

Attached files |

3D_Surface.mq5 (24.48 KB)
MQL5.zip (199.48 KB)

این مقاله ترجمه شده توسط تیم آکادمی ایران ام کیو ال می باشد. 

صفحه اصلی مقاله

سایر مقالات مرتبط

متا تریدر چیست؟
متاتریدر

متا تریدر چیست؟

متا تریدر چیست؟ اولین سوالی که هر فرد وقتی می خواهد آموزش های متاتریدر مانند آموزش صفر تا صد mql5،

کامل ترین و بهترین آموزش متاتریدر 4
mql4

کامل ترین آموزش متاتریدر 4

بهترین آموزش متاتریدر 4 متاتریدر4 یک پلتفرم معاملاتی محسوب می‌ شود که دارای رابط کاربری ساده است و همین یادگیری

پاسخ‌ها

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *