//+------------------------------------------------------------------+ //| TestStand3D.mqh | //| 3D-панель визуализации для тест-стенда метаэвристик | //| | //| Порядок #include в скрипте: | //| 1. TestStandFunctions.mqh — C_TestStand (2D-стенд) | //| 2. TestFunctions.mqh — C_Function, GenerateDataFixedSize| //| 3. TestStand3D.mqh — этот файл | //| 4. алгоритм оптимизации | //+------------------------------------------------------------------+ #include #include #include //--- Параметры камеры (можно менять) #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°) //--- Авто-вращение (активируется после бездействия мыши) #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 //+------------------------------------------------------------------+ //| S3DTrailPt — точка трека лучшего решения (мировые координаты) | //+------------------------------------------------------------------+ struct S3DTrailPt { float wx, wy, wz; }; //+------------------------------------------------------------------+ //| S3DDot — цветная точка проекции функции (мировые координаты) | //+------------------------------------------------------------------+ struct S3DDot { float wx, wy, wz; uint clr; // ARGB }; //+------------------------------------------------------------------+ //| S3DCentroid — центроид агента (мировые координаты + тип) | //+------------------------------------------------------------------+ struct S3DCentroid { float wx, wy, wz; bool is_best; // true = ao.cB (глобально лучший) }; //+------------------------------------------------------------------+ //| C_TestStand3D | //| | //| Отображает левую (H×H) панель тест-стенда как интерактивную | //| 3D-поверхность функции с проекцией агентов и треком лучшего. | //| | //| Управление (EA-контекст, события через OnChartEvent): | //| LMB-drag — вращение камеры | //| Scroll — zoom | //| Double-click — сброс камеры к позиции по умолчанию | //| | //| В скрипте управление мышью недоступно (синхронный OnStart), | //| работает только авто-вращение через OnTimer. | //+------------------------------------------------------------------+ class C_TestStand3D { //--- DX-объекты CCanvas3D m_cv; CDXSurface m_surf; //--- параметры канваса 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; //--- параметры камеры float m_pitch; // наклон (вокруг X) float m_yaw; // азимут (вокруг Y) double m_dist; // расстояние от центра сцены double m_timeout; // таймер бездействия до авто-вращения int m_mx, m_my; // предыдущая позиция мыши (локальная) //--- матрица вид×проекция для ручного проецирования агентов на экран DXMatrix m_vp; //--- диапазон домена функции (для маппинга агентов в мировые координаты) double m_fxMin, m_fxMax, m_fyMin, m_fyMax; //--- цветные точки проекций (agentCount * funcCount штук) S3DDot m_dots []; int m_nDots; //--- центроиды агентов (agentCount обычных + 1 лучший) S3DCentroid m_centroids []; int m_nCentroids; //--- трек лучшего решения (кольцевой буфер ёмкостью m_trailMax точек) S3DTrailPt m_trail []; int m_trailLen; int m_trailHead; // индекс записи в кольцевом буфере int m_trailMax; public: C_TestStand3D(); ~C_TestStand3D() { Shutdown(); } //--- жизненный цикл bool Init(const string name, int x, int y, int w, int h); void Shutdown(); //--- построение поверхности; grid — вершин по одной оси (10..200) // вызывать при каждой смене тестовой функции bool BuildSurface(C_Function &func, int grid = 80); //--- передача позиций агентов // args[] = [a0_f0_x, a0_f0_y, a0_f1_x, a0_f1_y, …] — agentCount * funcCount пар // bestArgs[] = ao.cB — funcCount пар координат глобально лучшего решения void SetAgents(double &args [], int agentCount, int funcCount, C_Function &func, double &bestArgs []); //--- один кадр рендера (поверхность + трек + агенты) void Redraw(); //--- сброс трека; вызывать в начале каждого нового повторного теста void ResetTrail() { m_trailLen = 0; m_trailHead = 0; } //--- события камеры (маршрутизировать из OnChartEvent в EA-контексте) void OnMouseMove(int chart_x, int chart_y, uint flags); void OnMouseWheel(double delta); void OnDblClick(); //--- авто-вращение; dt — виртуальный шаг времени в секундах // в скрипте передавать фиксированный шаг (например 0.033 = ~30 fps) void OnTimer(double dt); //--- видимость и размер void Show(bool v); bool IsVisible() const { return m_vis; } string GetName() const { return m_name; } void Resize(int new_w, int new_h); private: //--- пересчёт позиции камеры и VP-матрицы для агентов void _CamUpdate(); //--- проекция мировой точки → экранные пиксели // возвращает false если точка вышла за пределы viewport bool _Project(float wx, float wy, float wz, int &sx, int &sy); //--- маппинг одной координатной пары домена → мировые координаты void _MapToWorld(double ax, double ay, C_Function &func, double rx, double ry, float &wx, float &wy, float &wz); //--- отрисовка трека и агентов поверх 3D-сцены void _DrawTrail(); void _DrawAgents(); //--- портативные матричные хелперы static void _LookAtLH(DXMatrix &M, DXVector3 &eye, DXVector3 &tgt, DXVector3 &up); static void _PerspLH(DXMatrix &M, float fov, float aspect, float zn, float zf); static void _MulMat(DXMatrix &C, DXMatrix &A, DXMatrix &B); //--- радужный цвет: h_deg ∈ [0,360] → ARGB (S=1, L=0.5, полный спектр) static uint _HSL(int h_deg); }; //+------------------------------------------------------------------+ C_TestStand3D::C_TestStand3D() : m_surf_ok(false), m_vis(false), m_shutdown_done(false), m_nDots(0), m_nCentroids(0), m_grid(80), m_pitch(S3D_PITCH_DEF), m_yaw(0.0f), m_dist(S3D_DIST_DEF), m_timeout(0.0), m_mx(-1), m_my(-1), m_fxMin(0), m_fxMax(1), m_fyMin(0), m_fyMax(1), m_trailLen(0), m_trailHead(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; m_x = x; m_y = y; m_w = w; m_h = h; 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); 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); //--- освещение: тёплый направленный + холодный ambient 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(); ObjectDelete(0, m_name); } //+------------------------------------------------------------------+ bool C_TestStand3D::BuildSurface(C_Function &func, int grid) { m_grid = (int)MathMax(10.0, MathMin(200.0, (double)grid)); m_fxMin = func.GetMinRangeX(); m_fxMax = func.GetMaxRangeX(); m_fyMin = func.GetMinRangeY(); m_fyMax = func.GetMaxRangeY(); double buf []; if(!GenerateDataFixedSize(m_grid, m_grid, func, buf)) { Print("C_TestStand3D::BuildSurface – GenerateDataFixedSize failed"); return false; } const float VAL_RANGE = 1.0f; 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; } 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_nDots = 0; m_nCentroids = 0; m_trailLen = 0; m_trailHead = 0; return true; } //+------------------------------------------------------------------+ //| _MapToWorld — маппинг (ax,ay) из домена функции в мировые коорд. | //| rx, ry — предвычисленные (fxMax-fxMin), (fyMax-fyMin) | //+------------------------------------------------------------------+ void C_TestStand3D::_MapToWorld(double ax, double ay, C_Function &func, double rx, double ry, float &wx, float &wy, float &wz) { ax = MathMax(m_fxMin, MathMin(m_fxMax, ax)); ay = MathMax(m_fyMin, MathMin(m_fyMax, ay)); wx = (float)(-S3D_SX + (ax - m_fxMin) / rx * 2.0 * S3D_SX); wz = (float)(-S3D_SZ + (ay - m_fyMin) / ry * 2.0 * S3D_SZ); double fv = MathMax(0.0, MathMin(1.0, func.Core(ax, ay))); wy = (float)(-S3D_SY + fv * 2.0 * S3D_SY) + 0.22f; } //+------------------------------------------------------------------+ //| SetAgents | //| args[] — agentCount * funcCount пар (x,y), layout: | //| [a0_f0_x, a0_f0_y, a0_f1_x, a0_f1_y, …, | //| a1_f0_x, a1_f0_y, …] | //| bestArgs[] — ao.cB, funcCount пар (x,y) глобально лучшего | //+------------------------------------------------------------------+ void C_TestStand3D::SetAgents(double &args [], int agentCount, int funcCount, C_Function &func, double &bestArgs []) { if(agentCount <= 0 || funcCount <= 0) return; if(ArraySize(args) < agentCount * funcCount * 2) return; if(ArraySize(bestArgs) < funcCount * 2) return; 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; //=== 1. Цветные точки проекций (agentCount × funcCount) === // Цвет определяется индексом функции j — как в 2D (DoubleToColor(i, 0, count-1, 0, 270)) m_nDots = agentCount * funcCount; ArrayResize(m_dots, m_nDots); for(int i = 0; i < agentCount; i++) for(int j = 0; j < funcCount; j++) { int idx = i * funcCount + j; float wx, wy, wz; _MapToWorld(args [idx * 2], args [idx * 2 + 1], func, rx, ry, wx, wy, wz); m_dots [idx].wx = wx; m_dots [idx].wy = wy; m_dots [idx].wz = wz; m_dots [idx].clr = _HSL((int)(270.0 * j / MathMax(1, funcCount - 1))); } //=== 2. Центроиды агентов (agentCount обычных + 1 лучший) === m_nCentroids = agentCount + 1; ArrayResize(m_centroids, m_nCentroids); //--- центроид каждого обычного агента (среднее его funcCount проекций) for(int i = 0; i < agentCount; i++) { double sx = 0.0, sy = 0.0, sz = 0.0; for(int j = 0; j < funcCount; j++) { int idx = i * funcCount + j; sx += m_dots [idx].wx; sy += m_dots [idx].wy; sz += m_dots [idx].wz; } m_centroids [i].wx = (float)(sx / funcCount); m_centroids [i].wy = (float)(sy / funcCount); m_centroids [i].wz = (float)(sz / funcCount); m_centroids [i].is_best = false; } //--- центроид лучшего решения из ao.cB (как st.PointDr(ao.cB, …, true)) { double bsx = 0.0, bsy = 0.0, bsz = 0.0; for(int j = 0; j < funcCount; j++) { float wx, wy, wz; _MapToWorld(bestArgs [j * 2], bestArgs [j * 2 + 1], func, rx, ry, wx, wy, wz); bsx += wx; bsy += wy; bsz += wz; } int bi = agentCount; m_centroids [bi].wx = (float)(bsx / funcCount); m_centroids [bi].wy = (float)(bsy / funcCount); m_centroids [bi].wz = (float)(bsz / funcCount); m_centroids [bi].is_best = true; //--- трек отслеживает центроид ao.cB (кольцевой буфер) m_trail [m_trailHead].wx = m_centroids [bi].wx; m_trail [m_trailHead].wy = m_centroids [bi].wy; m_trail [m_trailHead].wz = m_centroids [bi].wz; m_trailHead = (m_trailHead + 1) % m_trailMax; if(m_trailLen < m_trailMax) m_trailLen++; } } //+------------------------------------------------------------------+ void C_TestStand3D::Redraw() { if(!m_vis || !m_surf_ok) return; m_cv.Render(DX_CLEAR_COLOR | DX_CLEAR_DEPTH, S3D_BG); _DrawTrail(); _DrawAgents(); m_cv.Update(); } //+------------------------------------------------------------------+ void C_TestStand3D::_CamUpdate() { 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); DXVec4Transform(cam, cam, rot); DXVec4Transform(lgt, lgt, rot); DXMatrixRotationY(rot, m_yaw); DXVec4Transform(cam, cam, rot); DXVec4Transform(lgt, lgt, rot); DXVector3 eye = DXVector3(cam); DXVector3 ld = DXVector3(lgt); m_cv.ViewPositionSet(eye); m_cv.LightDirectionSet(ld); DXVector3 tgt = DXVector3(0.0f, 0.0f, 0.0f); DXVector3 up = DXVector3(0.0f, 1.0f, 0.0f); DXMatrix view, proj; _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 | //| Золотая линия (#FFC828) по истории позиций лучшего агента. | //| Градиент прозрачности: старые точки alpha≈0x30, новые — 0xE0. | //| Двойная линия (смещение +1px по Y) — визуальная толщина. | //| Обход кольцевого буфера: от oldest к newest по логическому k. | //+------------------------------------------------------------------+ void C_TestStand3D::_DrawTrail() { if(m_trailLen < 2) return; int px = 0, py = 0, cx = 0, cy = 0; bool hasPrev = false; //--- индекс самой старой точки в кольцевом буфере int start = (m_trailHead - m_trailLen + m_trailMax) % m_trailMax; for(int k = 0; k < m_trailLen; k++) { int t = (start + k) % m_trailMax; if(!_Project(m_trail [t].wx, m_trail [t].wy, m_trail [t].wz, cx, cy)) { hasPrev = false; continue; } if(!hasPrev) { px = cx; py = cy; hasPrev = true; continue; } double ratio = (double)k / (double)(m_trailLen - 1); uint alpha = (uint)(0x30 + ratio * 0xB0); uint clr = (alpha << 24) | 0x00FFC828; uint clrT = ((alpha >> 1) << 24) | 0x00FFC828; m_cv.LineAA(px, py, cx, cy, clr); m_cv.LineAA(px, py + 1, cx, cy + 1, clrT); px = cx; py = cy; } } //+------------------------------------------------------------------+ //| _DrawAgents | //| | //| Полное соответствие 2D PointDr: | //| — цветные точки r=2 для каждой проекции функции | //| (цвет по индексу функции j, hue [0,270]) | //| — центроид обычного агента: FillCircle r=4 black + r=3 white | //| — центроид лучшего (ao.cB): Circle r=6 + r=7 black (кольцо) | //| (радиусы в 3D на 1 больше, чем в 2D, для видимости) | //+------------------------------------------------------------------+ void C_TestStand3D::_DrawAgents() { //--- 1. Цветные точки (аналог FillCircle r=1 в 2D, в 3D r=2 для видимости) for(int i = 0; i < m_nDots; i++) { int sx, sy; if(!_Project(m_dots [i].wx, m_dots [i].wy, m_dots [i].wz, sx, sy)) continue; m_cv.FillCircle(sx, sy, 2, m_dots [i].clr); } //--- 2. Центроиды агентов + центроид лучшего for(int i = 0; i < m_nCentroids; i++) { int sx, sy; if(!_Project(m_centroids [i].wx, m_centroids [i].wy, m_centroids [i].wz, sx, sy)) continue; if(m_centroids [i].is_best) { //--- аналог 2D: Canvas.Circle(…, 5, clrBlack); Canvas.Circle(…, 6, clrBlack); m_cv.Circle(sx, sy, 6, 0xFF000000); m_cv.Circle(sx, sy, 7, 0xFF000000); } else { //--- аналог 2D: FillCircle(…, 3, clrBlack); FillCircle(…, 2, clrWhite); m_cv.FillCircle(sx, sy, 4, 0xFF000000); m_cv.FillCircle(sx, sy, 3, 0xFFFFFFFF); } } } //+------------------------------------------------------------------+ void C_TestStand3D::OnMouseMove(int chart_x, int chart_y, uint flags) { int lx = chart_x - m_x; int ly = chart_y - m_y; if((flags & 1) == 1) { if(m_mx >= 0) { m_yaw += (lx - m_mx) / 240.0f; m_pitch += (ly - m_my) / 240.0f; float pMax = (float)(DX_PI * 0.48); if(m_pitch < -pMax) m_pitch = -pMax; if(m_pitch > pMax) m_pitch = pMax; _CamUpdate(); } m_mx = lx; m_my = ly; m_timeout = S3D_TIMEOUT; } else { m_mx = -1; m_my = -1; } } //+------------------------------------------------------------------+ 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() { m_pitch = S3D_PITCH_DEF; m_yaw = 0.0f; m_dist = S3D_DIST_DEF; m_timeout = 0.0; _CamUpdate(); } //+------------------------------------------------------------------+ void C_TestStand3D::OnTimer(double dt) { m_timeout -= dt; if(m_timeout < 0.0) { m_yaw += (float)(dt * S3D_AUTO_YAW); m_pitch = (float)((1.0 - S3D_RETURN) * m_pitch + S3D_RETURN * S3D_PITCH_DEF); _CamUpdate(); } } //+------------------------------------------------------------------+ 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; m_w = new_w; m_h = new_h; 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(); } //════════════════════════════════════════════════════════════════════ // Матричные хелперы //════════════════════════════════════════════════════════════════════ void C_TestStand3D::_LookAtLH(DXMatrix &M, DXVector3 &eye, DXVector3 &tgt, DXVector3 &up) { 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; zx /= zl; zy /= zl; zz /= zl; 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; xx /= xl; xy /= xl; xz /= xl; float yx = zy * xz - zz * xy; float yy = zz * xx - zx * xz; float yz = zx * xy - zy * xx; 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); 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; } //+------------------------------------------------------------------+ 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); 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; } //+------------------------------------------------------------------+ 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]; } //+------------------------------------------------------------------+ uint C_TestStand3D::_HSL(int h_deg) { double hue = h_deg / 360.0; double off [3] = {1.0 / 3.0, 0.0, -1.0 / 3.0}; 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]; } //+------------------------------------------------------------------+