2026-03-15 16:01:30 +04:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| TestStand3D.mqh |
|
|
|
|
|
|
//| 3D-панель визуализации для тест-стенда метаэвристик |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| Порядок #include в скрипте: |
|
|
|
|
|
|
//| 1. TestStandFunctions.mqh — C_TestStand (2D-стенд) |
|
|
|
|
|
|
//| 2. TestFunctions.mqh — C_Function, GenerateDataFixedSize|
|
|
|
|
|
|
//| 3. TestStand3D.mqh — этот файл |
|
|
|
|
|
|
//| 4. алгоритм оптимизации |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
|
|
#include <Canvas\Canvas3D.mqh>
|
|
|
|
|
|
#include <Canvas\DX\DXSurface.mqh>
|
|
|
|
|
|
#include <Math\AOs\TestFunctions.mqh>
|
|
|
|
|
|
|
|
|
|
|
|
//--- Параметры камеры (можно менять)
|
2026-03-20 20:35:26 +04:00
|
|
|
|
#define S3D_DIST_DEF 35.0 // дистанция по умолчанию
|
|
|
|
|
|
#define S3D_DIST_MIN 12.0 // минимальный zoom-in
|
|
|
|
|
|
#define S3D_DIST_MAX 80.0 // максимальный zoom-out
|
|
|
|
|
|
#define S3D_PITCH_DEF 0.45f // наклон камеры по умолчанию (~26°)
|
|
|
|
|
|
#define S3D_FOV 0.5236f // поле зрения = π/6 (30°)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- Авто-вращение (активируется после бездействия мыши)
|
|
|
|
|
|
#define S3D_TIMEOUT 1.0 // секунд бездействия до начала вращения
|
|
|
|
|
|
#define S3D_AUTO_YAW 0.07 // рад/с вращения по оси Y (~4°/с)
|
|
|
|
|
|
#define S3D_RETURN 0.02 // коэффициент плавного возврата наклона к дефолту
|
|
|
|
|
|
|
|
|
|
|
|
//--- Полуразмеры поверхности в мировых координатах
|
|
|
|
|
|
#define S3D_SX 10.0f
|
|
|
|
|
|
#define S3D_SY 5.0f
|
|
|
|
|
|
#define S3D_SZ 10.0f
|
|
|
|
|
|
|
|
|
|
|
|
//--- Фоновый цвет панели
|
|
|
|
|
|
#define S3D_BG 0xFF0D0D1A
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| S3DTrailPt — точка трека лучшего решения (мировые координаты) |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
struct S3DTrailPt
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
float wx, wy, wz;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| S3DAgent — агент популяции (мировые координаты + цвет) |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
struct S3DAgent
|
|
|
|
|
|
{
|
|
|
|
|
|
float wx, wy, wz;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
uint clr; // ARGB
|
|
|
|
|
|
bool is_best; // лучший агент — особая отрисовка
|
2026-03-15 16:01:30 +04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| C_TestStand3D |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| Отображает левую (H×H) панель тест-стенда как интерактивную |
|
|
|
|
|
|
//| 3D-поверхность функции с проекцией агентов и треком лучшего. |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| Управление (EA-контекст, события через OnChartEvent): |
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//| LMB-drag — вращение камеры |
|
|
|
|
|
|
//| Scroll — zoom |
|
|
|
|
|
|
//| Double-click — сброс камеры к позиции по умолчанию |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| В скрипте управление мышью недоступно (синхронный OnStart), |
|
|
|
|
|
|
//| работает только авто-вращение через OnTimer. |
|
2026-03-15 16:01:30 +04:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
class C_TestStand3D
|
|
|
|
|
|
{
|
|
|
|
|
|
//--- DX-объекты
|
2026-03-20 20:35:26 +04:00
|
|
|
|
CCanvas3D m_cv;
|
|
|
|
|
|
CDXSurface m_surf;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- параметры канваса
|
2026-03-20 20:35:26 +04:00
|
|
|
|
string m_name;
|
|
|
|
|
|
int m_x, m_y, m_w, m_h;
|
|
|
|
|
|
bool m_surf_ok;
|
|
|
|
|
|
bool m_vis;
|
|
|
|
|
|
bool m_shutdown_done;
|
|
|
|
|
|
int m_grid;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- параметры камеры
|
2026-03-20 20:35:26 +04:00
|
|
|
|
float m_pitch; // наклон (вокруг X)
|
|
|
|
|
|
float m_yaw; // азимут (вокруг Y)
|
|
|
|
|
|
double m_dist; // расстояние от центра сцены
|
|
|
|
|
|
double m_timeout; // таймер бездействия до авто-вращения
|
|
|
|
|
|
int m_mx, m_my; // предыдущая позиция мыши (локальная)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- матрица вид×проекция для ручного проецирования агентов на экран
|
2026-03-20 20:35:26 +04:00
|
|
|
|
// (CCanvas3D использует свою VP для поверхности; для 2D-примитивов
|
|
|
|
|
|
// агентов строим VP вручную, чтобы они точно совпадали с поверхностью)
|
|
|
|
|
|
DXMatrix m_vp;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- диапазон домена функции (для маппинга агентов в мировые координаты)
|
2026-03-20 20:35:26 +04:00
|
|
|
|
double m_fxMin, m_fxMax, m_fyMin, m_fyMax;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- агенты текущей эпохи
|
2026-03-20 20:35:26 +04:00
|
|
|
|
S3DAgent m_ag [];
|
|
|
|
|
|
int m_nAg;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- трек лучшего решения (кольцевой буфер ёмкостью m_trailMax точек)
|
|
|
|
|
|
S3DTrailPt m_trail [];
|
|
|
|
|
|
int m_trailLen;
|
|
|
|
|
|
int m_trailMax;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
public:
|
2026-03-20 20:35:26 +04:00
|
|
|
|
C_TestStand3D ();
|
|
|
|
|
|
~C_TestStand3D ()
|
|
|
|
|
|
{
|
|
|
|
|
|
Shutdown ();
|
|
|
|
|
|
}
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
//--- жизненный цикл
|
|
|
|
|
|
bool Init (const string name, int x, int y, int w, int h);
|
|
|
|
|
|
void Shutdown ();
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- построение поверхности; grid — вершин по одной оси (10..200)
|
|
|
|
|
|
// вызывать при каждой смене тестовой функции
|
2026-03-15 16:01:30 +04:00
|
|
|
|
bool BuildSurface (C_Function &func, int grid = 80);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
|
|
|
|
|
|
//--- передача позиций агентов; args[] = [x0,y0, x1,y1, …] (count пар)
|
|
|
|
|
|
// best_idx = -1 → ни один агент не выделяется белым кружком
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void SetAgents (double &args [], int count,
|
|
|
|
|
|
C_Function &func, int best_idx = -1);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- один кадр рендера (поверхность + трек + агенты)
|
|
|
|
|
|
void Redraw ();
|
|
|
|
|
|
|
|
|
|
|
|
//--- сброс трека; вызывать в начале каждого нового повторного теста
|
|
|
|
|
|
void ResetTrail ()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_trailLen = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//--- события камеры (маршрутизировать из OnChartEvent в EA-контексте)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void OnMouseMove (int chart_x, int chart_y, uint flags);
|
|
|
|
|
|
void OnMouseWheel (double delta);
|
|
|
|
|
|
void OnDblClick ();
|
2026-03-20 20:35:26 +04:00
|
|
|
|
|
|
|
|
|
|
//--- авто-вращение; dt — виртуальный шаг времени в секундах
|
|
|
|
|
|
// в скрипте передавать фиксированный шаг (например 0.033 = ~30 fps)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void OnTimer (double dt);
|
|
|
|
|
|
|
|
|
|
|
|
//--- видимость и размер
|
|
|
|
|
|
void Show (bool v);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
bool IsVisible () const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_vis;
|
|
|
|
|
|
}
|
|
|
|
|
|
string GetName () const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_name;
|
|
|
|
|
|
}
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void Resize (int new_w, int new_h);
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- пересчёт позиции камеры и VP-матрицы для агентов
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void _CamUpdate ();
|
2026-03-20 20:35:26 +04:00
|
|
|
|
|
|
|
|
|
|
//--- проекция мировой точки → экранные пиксели
|
|
|
|
|
|
// возвращает false если точка вышла за пределы viewport
|
2026-03-15 16:01:30 +04:00
|
|
|
|
bool _Project (float wx, float wy, float wz, int &sx, int &sy);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
|
|
|
|
|
|
//--- отрисовка трека и агентов поверх 3D-сцены
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void _DrawTrail ();
|
|
|
|
|
|
void _DrawAgents ();
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- портативные матричные хелперы (не используют внешние DX-утилиты)
|
|
|
|
|
|
// DXMatrix в MQL5: элементы m[row][col], НЕ _11/_12/...
|
|
|
|
|
|
// DXVector3: передавать только по ссылке (MQL5 не допускает struct by value)
|
|
|
|
|
|
// Конвенция: row-major, left-handed (DirectX)
|
|
|
|
|
|
static void _LookAtLH (DXMatrix &M,
|
|
|
|
|
|
DXVector3 &eye, DXVector3 &tgt, DXVector3 &up);
|
|
|
|
|
|
static void _PerspLH (DXMatrix &M,
|
|
|
|
|
|
float fov, float aspect, float zn, float zf);
|
2026-03-15 16:01:30 +04:00
|
|
|
|
static void _MulMat (DXMatrix &C, DXMatrix &A, DXMatrix &B);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- радужный цвет агента: h_deg ∈ [0,360] → ARGB (S=1, L=0.5, полный спектр)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
static uint _HSL (int h_deg);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
C_TestStand3D::C_TestStand3D ()
|
|
|
|
|
|
: m_surf_ok (false), m_vis (false), m_shutdown_done (false),
|
|
|
|
|
|
m_nAg (0), m_grid (80),
|
|
|
|
|
|
m_pitch (S3D_PITCH_DEF), m_yaw (0.0f), m_dist (S3D_DIST_DEF),
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_timeout (0.0), m_mx (-1), m_my (-1),
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_fxMin (0), m_fxMax (1), m_fyMin (0), m_fyMax (1),
|
|
|
|
|
|
m_trailLen (0), m_trailMax (600)
|
|
|
|
|
|
{
|
|
|
|
|
|
ArrayResize (m_trail, m_trailMax);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool C_TestStand3D::Init (const string name, int x, int y, int w, int h)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_name = name;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_x = x;
|
|
|
|
|
|
m_y = y;
|
|
|
|
|
|
m_w = w;
|
|
|
|
|
|
m_h = h;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
ResetLastError ();
|
|
|
|
|
|
if (!m_cv.CreateBitmapLabel (m_name, m_x, m_y, m_w, m_h,
|
|
|
|
|
|
COLOR_FORMAT_ARGB_NORMALIZE))
|
|
|
|
|
|
{
|
|
|
|
|
|
PrintFormat ("C_TestStand3D::Init – ошибка CreateBitmapLabel: %d",
|
|
|
|
|
|
GetLastError ());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_cv.ProjectionMatrixSet (S3D_FOV, (float)m_w / m_h, 0.1f, 200.0f);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- target и up задаются один раз; в _CamUpdate их не трогаем,
|
|
|
|
|
|
// чтобы ViewPositionSet мог корректно пересчитать view-матрицу
|
2026-03-15 16:01:30 +04:00
|
|
|
|
DXVector3 tgt = DXVector3 (0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
DXVector3 up = DXVector3 (0.0f, 1.0f, 0.0f);
|
|
|
|
|
|
m_cv.ViewTargetSet (tgt);
|
|
|
|
|
|
m_cv.ViewUpDirectionSet (up);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- освещение: тёплый направленный + холодный ambient
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_cv.LightColorSet (DXColor (1.0f, 0.97f, 0.88f, 0.32f));
|
|
|
|
|
|
m_cv.AmbientColorSet (DXColor (0.82f, 0.88f, 1.0f, 0.72f));
|
|
|
|
|
|
|
|
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
|
|
|
|
|
|
ObjectSetInteger (0, m_name, OBJPROP_HIDDEN, true);
|
|
|
|
|
|
ObjectSetInteger (0, m_name, OBJPROP_SELECTABLE, false);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::Shutdown ()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_shutdown_done) return;
|
|
|
|
|
|
m_shutdown_done = true;
|
|
|
|
|
|
m_surf.Shutdown ();
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- m_cv.Destroy() НЕ вызываем: деструктор CCanvas3D делает это сам.
|
|
|
|
|
|
// Двойной вызов Destroy() → Abnormal termination.
|
2026-03-15 16:01:30 +04:00
|
|
|
|
ObjectDelete (0, m_name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool C_TestStand3D::BuildSurface (C_Function &func, int grid)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_grid = (int)MathMax (10.0, MathMin (200.0, (double)grid));
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_fxMin = func.GetMinRangeX ();
|
|
|
|
|
|
m_fxMax = func.GetMaxRangeX ();
|
|
|
|
|
|
m_fyMin = func.GetMinRangeY ();
|
|
|
|
|
|
m_fyMax = func.GetMaxRangeY ();
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
double buf [];
|
|
|
|
|
|
if (!GenerateDataFixedSize (m_grid, m_grid, func, buf))
|
|
|
|
|
|
{
|
|
|
|
|
|
Print ("C_TestStand3D::BuildSurface – GenerateDataFixedSize failed");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- C_Function нормализует значения в [0,1], поэтому VAL_RANGE = 1
|
|
|
|
|
|
const float VAL_RANGE = 1.0f;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
DXVector3 bMin = DXVector3 (-S3D_SX, -S3D_SY, -S3D_SZ);
|
|
|
|
|
|
DXVector3 bMax = DXVector3 (S3D_SX, S3D_SY, S3D_SZ);
|
|
|
|
|
|
DXVector2 uv = DXVector2 (1.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_surf_ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_surf.Create (m_cv.DXDispatcher (), m_cv.InputScene (),
|
|
|
|
|
|
buf, (uint)m_grid, (uint)m_grid,
|
|
|
|
|
|
VAL_RANGE, bMin, bMax, uv,
|
|
|
|
|
|
CDXSurface::SF_TWO_SIDED | CDXSurface::SF_USE_NORMALS))
|
|
|
|
|
|
{
|
|
|
|
|
|
Print ("C_TestStand3D::BuildSurface – CDXSurface::Create failed");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- Create не принимает цветовую схему → сразу Update с CS_COLD_TO_HOT;
|
|
|
|
|
|
// без этого первая функция отображается серой (без градиента)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_surf.Update (buf, (uint)m_grid, (uint)m_grid,
|
|
|
|
|
|
VAL_RANGE, bMin, bMax, uv,
|
|
|
|
|
|
CDXSurface::SF_TWO_SIDED | CDXSurface::SF_USE_NORMALS,
|
|
|
|
|
|
CDXSurface::CS_COLD_TO_HOT);
|
|
|
|
|
|
m_surf.SpecularColorSet (DXColor (1.0f, 1.0f, 1.0f, 0.55f));
|
|
|
|
|
|
m_cv.ObjectAdd (&m_surf);
|
|
|
|
|
|
m_surf_ok = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_surf.Update (buf, (uint)m_grid, (uint)m_grid,
|
|
|
|
|
|
VAL_RANGE, bMin, bMax, uv,
|
|
|
|
|
|
CDXSurface::SF_TWO_SIDED | CDXSurface::SF_USE_NORMALS,
|
|
|
|
|
|
CDXSurface::CS_COLD_TO_HOT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_nAg = 0;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_trailLen = 0; // трек сбрасывается при каждой смене функции
|
2026-03-15 16:01:30 +04:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::SetAgents (double &args [], int count,
|
|
|
|
|
|
C_Function &func, int best_idx)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_nAg = MathMax (0, count);
|
|
|
|
|
|
if (m_nAg == 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
ArrayResize (m_ag, m_nAg);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
double rx = m_fxMax - m_fxMin;
|
|
|
|
|
|
if (rx < 1e-9) rx = 1e-9;
|
|
|
|
|
|
double ry = m_fyMax - m_fyMin;
|
|
|
|
|
|
if (ry < 1e-9) ry = 1e-9;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_nAg; i++)
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- клампинг в допустимый диапазон функции
|
2026-03-15 16:01:30 +04:00
|
|
|
|
double ax = MathMax (m_fxMin, MathMin (m_fxMax, args [i * 2]));
|
|
|
|
|
|
double ay = MathMax (m_fyMin, MathMin (m_fyMax, args [i * 2 + 1]));
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- маппинг: домен функции → мировые координаты поверхности
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float wx = (float)(-S3D_SX + (ax - m_fxMin) / rx * 2.0 * S3D_SX);
|
|
|
|
|
|
float wz = (float)(-S3D_SZ + (ay - m_fyMin) / ry * 2.0 * S3D_SZ);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- высота: нормализованное значение [0,1] → мировая Y + небольшой отступ
|
2026-03-15 16:01:30 +04:00
|
|
|
|
double fv = MathMax (0.0, MathMin (1.0, func.Core (ax, ay)));
|
|
|
|
|
|
float wy = (float)(-S3D_SY + fv * 2.0 * S3D_SY) + 0.22f;
|
|
|
|
|
|
|
|
|
|
|
|
m_ag [i].wx = wx;
|
|
|
|
|
|
m_ag [i].wy = wy;
|
|
|
|
|
|
m_ag [i].wz = wz;
|
|
|
|
|
|
m_ag [i].is_best = (i == best_idx);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- радужный цвет: синий (i=0) → красный (i=last)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_ag [i].clr = _HSL ((int)(270.0 * i / MathMax (1, m_nAg - 1)));
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- добавить позицию лучшего агента в трек
|
2026-03-15 16:01:30 +04:00
|
|
|
|
if (i == best_idx && best_idx >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_trailLen < m_trailMax)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_trail [m_trailLen].wx = wx;
|
|
|
|
|
|
m_trail [m_trailLen].wy = wy;
|
|
|
|
|
|
m_trail [m_trailLen].wz = wz;
|
|
|
|
|
|
m_trailLen++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- буфер полон — сдвиг на 1, старейшая точка теряется
|
2026-03-15 16:01:30 +04:00
|
|
|
|
for (int t = 0; t < m_trailMax - 1; t++) m_trail [t] = m_trail [t + 1];
|
|
|
|
|
|
m_trail [m_trailMax - 1].wx = wx;
|
|
|
|
|
|
m_trail [m_trailMax - 1].wy = wy;
|
|
|
|
|
|
m_trail [m_trailMax - 1].wz = wz;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::Redraw ()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_vis || !m_surf_ok) return;
|
|
|
|
|
|
m_cv.Render (DX_CLEAR_COLOR | DX_CLEAR_DEPTH, S3D_BG);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
_DrawTrail (); // трек рисуется под агентами
|
2026-03-15 16:01:30 +04:00
|
|
|
|
_DrawAgents ();
|
|
|
|
|
|
m_cv.Update ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::_CamUpdate ()
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- вращаем базовую позицию (0,0,-dist) вокруг X (pitch), затем Y (yaw)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
DXVector4 cam = DXVector4 (0.0f, 0.0f, -(float)m_dist, 1.0f);
|
|
|
|
|
|
DXVector4 lgt = DXVector4 (0.25f, -0.45f, 1.0f, 0.0f);
|
|
|
|
|
|
DXMatrix rot;
|
|
|
|
|
|
|
|
|
|
|
|
DXMatrixRotationX (rot, m_pitch);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
DXVec4Transform (cam, cam, rot);
|
|
|
|
|
|
DXVec4Transform (lgt, lgt, rot);
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
DXMatrixRotationY (rot, m_yaw);
|
|
|
|
|
|
DXVec4Transform (cam, cam, rot);
|
|
|
|
|
|
DXVec4Transform (lgt, lgt, rot);
|
|
|
|
|
|
|
|
|
|
|
|
DXVector3 eye = DXVector3 (cam);
|
|
|
|
|
|
DXVector3 ld = DXVector3 (lgt);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- ViewPositionSet внутри CCanvas3D сразу вызывает DXMatrixLookAtLH
|
|
|
|
|
|
// и обновляет m_view_matrix, используя сохранённые target и up из Init()
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_cv.ViewPositionSet (eye);
|
|
|
|
|
|
m_cv.LightDirectionSet (ld);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- строим VP-матрицу для ручного проецирования агентов в _Project()
|
|
|
|
|
|
DXVector3 tgt = DXVector3 (0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
DXVector3 up = DXVector3 (0.0f, 1.0f, 0.0f);
|
|
|
|
|
|
DXMatrix view, proj;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
_LookAtLH (view, eye, tgt, up);
|
|
|
|
|
|
_PerspLH (proj, S3D_FOV, (float)m_w / m_h, 0.1f, 200.0f);
|
|
|
|
|
|
_MulMat (m_vp, view, proj);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool C_TestStand3D::_Project (float wx, float wy, float wz,
|
|
|
|
|
|
int &sx, int &sy)
|
|
|
|
|
|
{
|
|
|
|
|
|
DXVector4 p = DXVector4 (wx, wy, wz, 1.0f);
|
|
|
|
|
|
DXVec4Transform (p, p, m_vp);
|
|
|
|
|
|
if (p.w < 0.01f) return false;
|
|
|
|
|
|
|
|
|
|
|
|
float nx = p.x / p.w;
|
|
|
|
|
|
float ny = p.y / p.w;
|
|
|
|
|
|
if (nx < -1.1f || nx > 1.1f || ny < -1.1f || ny > 1.1f) return false;
|
|
|
|
|
|
|
|
|
|
|
|
sx = (int)((nx + 1.0f) * 0.5f * m_w);
|
|
|
|
|
|
sy = (int)((1.0f - ny) * 0.5f * m_h);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| _DrawTrail |
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//| Золотая линия (#FFC828) по истории позиций лучшего агента. |
|
|
|
|
|
|
//| Градиент прозрачности: старые точки alpha≈0x30, новые — 0xE0. |
|
|
|
|
|
|
//| Двойная линия (смещение +1px по Y) — визуальная толщина. |
|
2026-03-15 16:01:30 +04:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::_DrawTrail ()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_trailLen < 2) return;
|
|
|
|
|
|
|
|
|
|
|
|
int px = 0, py = 0, cx = 0, cy = 0;
|
|
|
|
|
|
bool hasPrev = false;
|
|
|
|
|
|
|
|
|
|
|
|
for (int t = 0; t < m_trailLen; t++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_Project (m_trail [t].wx, m_trail [t].wy, m_trail [t].wz, cx, cy))
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
hasPrev = false; // разрыв при выходе точки за viewport
|
2026-03-15 16:01:30 +04:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!hasPrev)
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
px = cx;
|
|
|
|
|
|
py = cy;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
hasPrev = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double ratio = (double)t / (double)(m_trailLen - 1);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
uint alpha = (uint)(0x30 + ratio * 0xB0); // 0x30 → 0xE0
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
|
|
|
|
|
uint clr = (alpha << 24) | 0x00FFC828;
|
|
|
|
|
|
uint clrT = ((alpha >> 1) << 24) | 0x00FFC828; // тень +1px
|
|
|
|
|
|
|
|
|
|
|
|
m_cv.LineAA (px, py, cx, cy, clr);
|
|
|
|
|
|
m_cv.LineAA (px, py + 1, cx, cy + 1, clrT);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
px = cx;
|
|
|
|
|
|
py = cy;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| _DrawAgents |
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//| Обычный агент: цветной кружок r=4 + чёрная обводка. |
|
|
|
|
|
|
//| Лучший агент (ao.cB): белый кружок r=7 + жирная тень. |
|
2026-03-15 16:01:30 +04:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::_DrawAgents ()
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < m_nAg; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int sx, sy;
|
|
|
|
|
|
if (!_Project (m_ag [i].wx, m_ag [i].wy, m_ag [i].wz, sx, sy)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (m_ag [i].is_best)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cv.FillCircle (sx, sy, 7, 0xFFFFFFFF);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_cv.Circle (sx, sy, 7, 0xFF000000);
|
|
|
|
|
|
m_cv.Circle (sx, sy, 8, 0xFF000000);
|
|
|
|
|
|
m_cv.Circle (sx, sy, 9, 0x88000000);
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_cv.FillCircle (sx, sy, 4, m_ag [i].clr);
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_cv.Circle (sx, sy, 4, 0xFF000000);
|
|
|
|
|
|
m_cv.Circle (sx, sy, 5, 0x55000000);
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::OnMouseMove (int chart_x, int chart_y, uint flags)
|
|
|
|
|
|
{
|
|
|
|
|
|
int lx = chart_x - m_x;
|
|
|
|
|
|
int ly = chart_y - m_y;
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
if ((flags & 1) == 1) // LMB нажата
|
2026-03-15 16:01:30 +04:00
|
|
|
|
{
|
|
|
|
|
|
if (m_mx >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_yaw += (lx - m_mx) / 240.0f;
|
|
|
|
|
|
m_pitch += (ly - m_my) / 240.0f;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- ограничение наклона ±86°
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float pMax = (float)(DX_PI * 0.48);
|
|
|
|
|
|
if (m_pitch < -pMax) m_pitch = -pMax;
|
|
|
|
|
|
if (m_pitch > pMax) m_pitch = pMax;
|
|
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
}
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_mx = lx;
|
|
|
|
|
|
m_my = ly;
|
|
|
|
|
|
m_timeout = S3D_TIMEOUT; // сброс таймера авто-вращения
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-03-20 20:35:26 +04:00
|
|
|
|
{
|
|
|
|
|
|
m_mx = -1;
|
|
|
|
|
|
m_my = -1;
|
|
|
|
|
|
}
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::OnMouseWheel (double delta)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_dist *= (1.0 - delta * 0.0012);
|
|
|
|
|
|
m_dist = MathMax (S3D_DIST_MIN, MathMin (S3D_DIST_MAX, m_dist));
|
|
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::OnDblClick ()
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_pitch = S3D_PITCH_DEF;
|
|
|
|
|
|
m_yaw = 0.0f;
|
|
|
|
|
|
m_dist = S3D_DIST_DEF;
|
|
|
|
|
|
m_timeout = 0.0;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::OnTimer (double dt)
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_timeout -= dt;
|
|
|
|
|
|
if (m_timeout < 0.0)
|
|
|
|
|
|
{
|
|
|
|
|
|
//--- авто-вращение по Y и плавный возврат наклона к дефолту
|
|
|
|
|
|
m_yaw += (float)(dt * S3D_AUTO_YAW);
|
|
|
|
|
|
m_pitch = (float)((1.0 - S3D_RETURN) * m_pitch + S3D_RETURN * S3D_PITCH_DEF);
|
|
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
}
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::Show (bool v)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_vis = v;
|
|
|
|
|
|
ObjectSetInteger (0, m_name, OBJPROP_HIDDEN, !v);
|
|
|
|
|
|
ChartRedraw ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void C_TestStand3D::Resize (int new_w, int new_h)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (new_w == m_w && new_h == m_h) return;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
m_w = new_w;
|
|
|
|
|
|
m_h = new_h;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
m_cv.Resize (m_w, m_h);
|
|
|
|
|
|
DXContextSetSize (m_cv.DXContext (), m_w, m_h);
|
|
|
|
|
|
m_cv.ProjectionMatrixSet (S3D_FOV, (float)m_w / m_h, 0.1f, 200.0f);
|
|
|
|
|
|
_CamUpdate ();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//════════════════════════════════════════════════════════════════════
|
2026-03-15 16:01:30 +04:00
|
|
|
|
// Матричные хелперы
|
|
|
|
|
|
//
|
|
|
|
|
|
// DXMatrix в MQL5 хранит элементы как m[row][col], НЕ как _11/_12
|
2026-03-20 20:35:26 +04:00
|
|
|
|
// DXVector3: передавать только по ссылке (MQL5, struct by value недопустим)
|
|
|
|
|
|
// Конвенция: row-major, left-handed (DirectX)
|
|
|
|
|
|
//════════════════════════════════════════════════════════════════════
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- LookAt LH — эквивалент D3DXMatrixLookAtLH
|
|
|
|
|
|
void C_TestStand3D::_LookAtLH (DXMatrix &M,
|
|
|
|
|
|
DXVector3 &eye, DXVector3 &tgt, DXVector3 &up)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- z = normalize(tgt - eye) — ось «вперёд»
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float zx = tgt.x - eye.x, zy = tgt.y - eye.y, zz = tgt.z - eye.z;
|
|
|
|
|
|
float zl = (float)MathSqrt (zx * zx + zy * zy + zz * zz);
|
|
|
|
|
|
if (zl < 1e-7f) zl = 1e-7f;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
zx /= zl;
|
|
|
|
|
|
zy /= zl;
|
|
|
|
|
|
zz /= zl;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- x = normalize(cross(up, z)) — ось «вправо»
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float xx = up.y * zz - up.z * zy;
|
|
|
|
|
|
float xy = up.z * zx - up.x * zz;
|
|
|
|
|
|
float xz = up.x * zy - up.y * zx;
|
|
|
|
|
|
float xl = (float)MathSqrt (xx * xx + xy * xy + xz * xz);
|
|
|
|
|
|
if (xl < 1e-7f) xl = 1e-7f;
|
2026-03-20 20:35:26 +04:00
|
|
|
|
xx /= xl;
|
|
|
|
|
|
xy /= xl;
|
|
|
|
|
|
xz /= xl;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- y = cross(z, x) — ортогонализованная ось «вверх»
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float yx = zy * xz - zz * xy;
|
|
|
|
|
|
float yy = zz * xx - zx * xz;
|
|
|
|
|
|
float yz = zx * xy - zy * xx;
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- трансляционная часть
|
2026-03-15 16:01:30 +04:00
|
|
|
|
float dx = -(xx * eye.x + xy * eye.y + xz * eye.z);
|
|
|
|
|
|
float dy = -(yx * eye.x + yy * eye.y + yz * eye.z);
|
|
|
|
|
|
float dz = -(zx * eye.x + zy * eye.y + zz * eye.z);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
M.m [0] [0] = xx;
|
|
|
|
|
|
M.m [0] [1] = yx;
|
|
|
|
|
|
M.m [0] [2] = zx;
|
|
|
|
|
|
M.m [0] [3] = 0.0f;
|
|
|
|
|
|
M.m [1] [0] = xy;
|
|
|
|
|
|
M.m [1] [1] = yy;
|
|
|
|
|
|
M.m [1] [2] = zy;
|
|
|
|
|
|
M.m [1] [3] = 0.0f;
|
|
|
|
|
|
M.m [2] [0] = xz;
|
|
|
|
|
|
M.m [2] [1] = yz;
|
|
|
|
|
|
M.m [2] [2] = zz;
|
|
|
|
|
|
M.m [2] [3] = 0.0f;
|
|
|
|
|
|
M.m [3] [0] = dx;
|
|
|
|
|
|
M.m [3] [1] = dy;
|
|
|
|
|
|
M.m [3] [2] = dz;
|
|
|
|
|
|
M.m [3] [3] = 1.0f;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- Perspective LH — эквивалент D3DXMatrixPerspectiveFovLH
|
2026-03-15 16:01:30 +04:00
|
|
|
|
void C_TestStand3D::_PerspLH (DXMatrix &M,
|
|
|
|
|
|
float fov, float aspect, float zn, float zf)
|
|
|
|
|
|
{
|
|
|
|
|
|
float ys = 1.0f / (float)MathTan (fov * 0.5);
|
|
|
|
|
|
float xs = ys / aspect;
|
|
|
|
|
|
float zr = zf / (zf - zn);
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
M.m [0] [0] = xs;
|
|
|
|
|
|
M.m [0] [1] = 0.0f;
|
|
|
|
|
|
M.m [0] [2] = 0.0f;
|
|
|
|
|
|
M.m [0] [3] = 0.0f;
|
|
|
|
|
|
M.m [1] [0] = 0.0f;
|
|
|
|
|
|
M.m [1] [1] = ys;
|
|
|
|
|
|
M.m [1] [2] = 0.0f;
|
|
|
|
|
|
M.m [1] [3] = 0.0f;
|
|
|
|
|
|
M.m [2] [0] = 0.0f;
|
|
|
|
|
|
M.m [2] [1] = 0.0f;
|
|
|
|
|
|
M.m [2] [2] = zr;
|
|
|
|
|
|
M.m [2] [3] = 1.0f;
|
|
|
|
|
|
M.m [3] [0] = 0.0f;
|
|
|
|
|
|
M.m [3] [1] = 0.0f;
|
|
|
|
|
|
M.m [3] [2] = -zn * zr;
|
|
|
|
|
|
M.m [3] [3] = 0.0f;
|
2026-03-15 16:01:30 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//--- C = A × B (row-major)
|
|
|
|
|
|
void C_TestStand3D::_MulMat (DXMatrix &C, DXMatrix &A, DXMatrix &B)
|
|
|
|
|
|
{
|
|
|
|
|
|
C.m [0] [0] = A.m [0] [0] * B.m [0] [0] + A.m [0] [1] * B.m [1] [0] + A.m [0] [2] * B.m [2] [0] + A.m [0] [3] * B.m [3] [0];
|
|
|
|
|
|
C.m [0] [1] = A.m [0] [0] * B.m [0] [1] + A.m [0] [1] * B.m [1] [1] + A.m [0] [2] * B.m [2] [1] + A.m [0] [3] * B.m [3] [1];
|
|
|
|
|
|
C.m [0] [2] = A.m [0] [0] * B.m [0] [2] + A.m [0] [1] * B.m [1] [2] + A.m [0] [2] * B.m [2] [2] + A.m [0] [3] * B.m [3] [2];
|
|
|
|
|
|
C.m [0] [3] = A.m [0] [0] * B.m [0] [3] + A.m [0] [1] * B.m [1] [3] + A.m [0] [2] * B.m [2] [3] + A.m [0] [3] * B.m [3] [3];
|
|
|
|
|
|
C.m [1] [0] = A.m [1] [0] * B.m [0] [0] + A.m [1] [1] * B.m [1] [0] + A.m [1] [2] * B.m [2] [0] + A.m [1] [3] * B.m [3] [0];
|
|
|
|
|
|
C.m [1] [1] = A.m [1] [0] * B.m [0] [1] + A.m [1] [1] * B.m [1] [1] + A.m [1] [2] * B.m [2] [1] + A.m [1] [3] * B.m [3] [1];
|
|
|
|
|
|
C.m [1] [2] = A.m [1] [0] * B.m [0] [2] + A.m [1] [1] * B.m [1] [2] + A.m [1] [2] * B.m [2] [2] + A.m [1] [3] * B.m [3] [2];
|
|
|
|
|
|
C.m [1] [3] = A.m [1] [0] * B.m [0] [3] + A.m [1] [1] * B.m [1] [3] + A.m [1] [2] * B.m [2] [3] + A.m [1] [3] * B.m [3] [3];
|
|
|
|
|
|
C.m [2] [0] = A.m [2] [0] * B.m [0] [0] + A.m [2] [1] * B.m [1] [0] + A.m [2] [2] * B.m [2] [0] + A.m [2] [3] * B.m [3] [0];
|
|
|
|
|
|
C.m [2] [1] = A.m [2] [0] * B.m [0] [1] + A.m [2] [1] * B.m [1] [1] + A.m [2] [2] * B.m [2] [1] + A.m [2] [3] * B.m [3] [1];
|
|
|
|
|
|
C.m [2] [2] = A.m [2] [0] * B.m [0] [2] + A.m [2] [1] * B.m [1] [2] + A.m [2] [2] * B.m [2] [2] + A.m [2] [3] * B.m [3] [2];
|
|
|
|
|
|
C.m [2] [3] = A.m [2] [0] * B.m [0] [3] + A.m [2] [1] * B.m [1] [3] + A.m [2] [2] * B.m [2] [3] + A.m [2] [3] * B.m [3] [3];
|
|
|
|
|
|
C.m [3] [0] = A.m [3] [0] * B.m [0] [0] + A.m [3] [1] * B.m [1] [0] + A.m [3] [2] * B.m [2] [0] + A.m [3] [3] * B.m [3] [0];
|
|
|
|
|
|
C.m [3] [1] = A.m [3] [0] * B.m [0] [1] + A.m [3] [1] * B.m [1] [1] + A.m [3] [2] * B.m [2] [1] + A.m [3] [3] * B.m [3] [1];
|
|
|
|
|
|
C.m [3] [2] = A.m [3] [0] * B.m [0] [2] + A.m [3] [1] * B.m [1] [2] + A.m [3] [2] * B.m [2] [2] + A.m [3] [3] * B.m [3] [2];
|
|
|
|
|
|
C.m [3] [3] = A.m [3] [0] * B.m [0] [3] + A.m [3] [1] * B.m [1] [3] + A.m [3] [2] * B.m [2] [3] + A.m [3] [3] * B.m [3] [3];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//--- HSL → ARGB при S=1, L=0.5 (чистый спектр)
|
2026-03-15 16:01:30 +04:00
|
|
|
|
uint C_TestStand3D::_HSL (int h_deg)
|
|
|
|
|
|
{
|
2026-03-20 20:35:26 +04:00
|
|
|
|
double hue = h_deg / 360.0;
|
|
|
|
|
|
double off [3] = {1.0 / 3.0, 0.0, -1.0 / 3.0};
|
2026-03-15 16:01:30 +04:00
|
|
|
|
uint ch [3];
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
double vh = hue + off [i];
|
|
|
|
|
|
if (vh < 0.0) vh += 1.0;
|
|
|
|
|
|
if (vh > 1.0) vh -= 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
double v;
|
|
|
|
|
|
if (6.0 * vh < 1.0) v = 6.0 * vh;
|
|
|
|
|
|
else
|
|
|
|
|
|
if (2.0 * vh < 1.0) v = 1.0;
|
|
|
|
|
|
else
|
|
|
|
|
|
if (3.0 * vh < 2.0) v = (2.0 / 3.0 - vh) * 6.0;
|
|
|
|
|
|
else v = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
ch [i] = (uint)(v * 255.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0xFF000000 | (ch [0] << 16) | (ch [1] << 8) | ch [2];
|
|
|
|
|
|
}
|
2026-03-20 20:35:26 +04:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
�
|