Торговые инструменты на MQL5 (Часть 29): Пошаговая анимация кривой-бабочки на Canvas
Введение
У вас уже есть подробная и реалистичная кривая-бабочка, аккуратно отрисованная на холсте MetaTrader 5. Рисунок включает в себя многослойную заливку крыльев, линии прожилок, чешуйчатую текстуру и полное анатомическое изображение тела. Однако в моменте он выглядит как статичное изображение, лишенное ощущения жизни или движения. Эта статья предназначена для разработчиков MQL5 и креативных программистов, желающих выйти за рамки статического рендеринга на холсте. Вы научитесь анимировать кривую-бабочку на протяжении всего ее жизненного цикла, начиная с первого контурного штриха и заканчивая непрерывным полетом.
В предыдущей статье (Часть 28) мы улучшили программу отрисовки кривой-бабочки на холсте с помощью трехслойной градиентной заливки крыльев, расходящихся линий прожилок, густо расположенные точки текстуры чешуек и полностью детализированное тело (брюшко, грудь, голова, сложные глаза и усики). Все элементы были отрисованы с помощью конвейера суперсэмплирования для получения чистого сглаженного изображения. В этой статье мы представляем четырехэтапную систему анимации. Она постепенно рисует контур кривой, плавно добавляет заливку крыльев, раскрывает детали поверхности и тела, а затем переходит в непрерывный полет. Во время полета мы добавляем взмахи крыльев, вертикальное покачивание, горизонтальное колебание, колебания наклона, эффект неонового свечения и циклическую смену цветов на основе оттенков. Мы рассмотрим следующие темы:
- Понимание фаз анимации кривой-бабочки и механики полета
- Реализация средствами MQL5
- Визуализация
- Заключение
В результате вы получите анимированную бабочку: сначала она сама прорисовывает контур, затем заполняется цветом, обретает детали и начинает летать по холсту MetaTrader 5. Давайте погрузимся в процесс!
Понимание фаз анимации кривой-бабочки и механики полета
Анимация разворачивается в четыре последовательных этапа: контур кривой постепенно формируется путем изменения параметра t от нуля до 12π; затем происходит плавное проявление заливки крыльев от прозрачности до полной непрозрачности; далее плавно появляются детали поверхности и тело; и, наконец, бабочка переходит в непрерывный полет. Каждый этап управляется миллисекундным таймером, который на каждом тике увеличивает соответствующую переменную состояния и передает управление следующему этапу после завершения.
Система полета одновременно использует четыре независимых осциллятора. Взмахи крыльев сжимают горизонтальное распространение каждой точки кривой к центру тела, используя абсолютное значение синусоидальной волны, создавая движение открытия-закрытия, характерное для настоящих крыльев. Вертикальное покачивание и горизонтальное колебание применяют синусоидальные смещения по осям Y и X соответственно, заставляя бабочку подниматься, опускаться и смещаться. Наклон добавляет небольшое сдвиговое преобразование, которое слегка наклоняет бабочку при покачивании, придавая движению естественное трехмерное качество.
Во время полета мы создаем неоновое свечение, рисуя несколько полупрозрачных смещенных линий вокруг каждого контура крыла. Мы также циклически меняем цвет крыльев в каждом фрейме, преобразуя цвета в модели HSV, изменяя оттенок и преобразуя обратно. Это создаёт эффект пульсации крыльев во время полёта бабочки. Мы реализуем все это поверх существующего конвейера рендеринга с суперсэмплированием, полностью управляемого обработчиком событий таймера. Наглядное представление результата приведено ниже.

Реализация средствами MQL5
Добавление перечисления анимации, входных параметров и переменных состояния
Для поддержки системы полной анимации мы вводим новое перечисление, три новые группы входных данных и полный набор глобальных переменных состояния анимации, которые вместе определяют и отслеживают каждый аспект жизненного цикла бабочки.
//+------------------------------------------------------------------+ //| Canvas Drawing PART 2.1 - Butterfly Curve.mq5 | //| Copyright 2026, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict //--- enum AnimationPhaseEnum { ANIM_DRAWING_CURVE, // Phase 1: progressively draw the outline curve ANIM_FILLING_WINGS, // Phase 2: fade wing fill layers in ANIM_ADDING_DETAILS, // Phase 3: fade veins, scales, and body in ANIM_FLYING // Phase 4: continuous flying oscillation }; input group "=== ANIMATION SETTINGS ===" input bool enableAnimation = true; // Enable Drawing Animation input int animationTimerMs = 30; // Animation Timer (ms) input double curveDrawSpeed = 0.30; // Curve Draw Speed (radians/frame) input double fillFadeSpeed = 0.08; // Fill Fade-In Speed (per frame) input double detailsFadeSpeed = 0.12; // Details Fade-In Speed (per frame) input group "=== FLYING ANIMATION SETTINGS ===" input bool enableFlyingAnimation = true; // Enable Flying After Draw input double wingFlapSpeed = 0.24; // Wing Flap Speed (radians/frame) input double wingFlapAmplitude = 0.25; // Wing Flap Depth (0=none, 0.5=extreme) input double verticalBobSpeed = 0.08; // Vertical Bob Speed (radians/frame) input double verticalBobAmplitude = 0.15; // Vertical Bob Amount (world units) input double horizontalSwaySpeed = 0.05; // Horizontal Sway Speed (radians/frame) input double horizontalSwayAmplitude = 0.08; // Horizontal Sway Amount (world units) input double tiltSwaySpeed = 0.06; // Tilt Oscillation Speed input double tiltSwayAmplitude = 0.06; // Tilt Amount (shear factor) input group "=== BUTTERFLY ENHANCEMENT SETTINGS ===" input bool enableCurveGlow = true; // Enable Neon Glow On Curves input int curveGlowLayers = 3; // Glow Bloom Layers (1-5) input double curveGlowIntensity = 0.7; // Glow Intensity (0-1) input bool enableColorCycling = true; // Enable Wing Color Cycling (Flying) input double colorCycleSpeed = 0.02; // Color Cycle Speed //+------------------------------------------------------------------+ //| Global Variables - Animation State | //+------------------------------------------------------------------+ AnimationPhaseEnum animationPhase = ANIM_DRAWING_CURVE; // Current animation phase double animationCurveT = 0.0; // Current T value reached by the drawing animation double animationFillAlpha = 0.0; // Current fill opacity during the fill-in phase double animationDetailAlpha = 0.0; // Current detail opacity during the details phase double flyWingPhase = 0.0; // Wing flap phase accumulator double flyBobPhase = 0.0; // Vertical bob phase accumulator double flySwayPhase = 0.0; // Horizontal sway phase accumulator double flyTiltPhase = 0.0; // Tilt phase accumulator double currentWingFlap = 0.0; // Current wing X-scale factor (0=fully open) double currentBobOffset = 0.0; // Current vertical offset in world coordinates double currentSwayOffset = 0.0; // Current horizontal offset in world coordinates double currentTiltShear = 0.0; // Current shear factor for slight tilt double globalTime = 0.0; // Master time accumulator for all frame-based effects double colorCyclePhase = 0.0; // Hue shift accumulator for color cycling
Перечисление "AnimationPhaseEnum" определяет четыре последовательных состояния анимации — рисование кривой, плавное появление заливки крыльев, плавное проявление деталей и непрерывный полет — что дает нам удобный способ отслеживать и переключаться между фазами на протяжении всей программы.
Группа настроек анимации добавляет главный переключатель, интервал таймера в миллисекундах, который управляет частотой срабатывания каждого фрейма, и три значения скорости, которые определяют, как быстро отрисовывается кривая, как быстро появляется заливка и как быстро отображается слой с деталями. Далее группа анимации полета предоставляет независимые элементы управления скоростью и амплитудой для каждого из четырех осцилляторов полета — взмахов крыльев, вертикального покачивания, горизонтального колебания и наклона — обеспечивая полный контроль настроек характера движений полета. Группа дополнительных эффектов завершает формирование новых вводных параметров с переключателями и параметров для создания эффекта неонового свечения и системы циклической смены цветов.
На стороне глобального состояния мы объявляем текущую фазу анимации инициализированной значением "ANIM_DRAWING_CURVE", аккумулятор кривой T, который отслеживает, насколько далеко был нарисован контур, и два аккумулятора непрозрачности для фаз заливки и появления детализации. Для системы полета кривой-бабочки мы задаем четыре фазовых аккумулятора — по одному на каждый осциллятор — которые увеличиваются на каждом кадре, и четыре соответствующие выходные переменные, которые хранят текущие вычисленные значения преобразования: масштаб взмахов крыльев, вертикальное смещение колебаний (эффект качания), горизонтальное смещение колебаний и коэффициент сдвига наклона. Наконец, главный временной аккумулятор и аккумулятор сдвига оттенка отслеживают общее количество фреймов и текущую позицию цветового цикла соответственно. Далее мы расширяем вспомогательные функции, чтобы включить новые вспомогательные функции, которые обеспечат плавный переход к анимированным фреймам. Начнем со вспомогательной функции для циклического изменения цвета кривой крыла.
Изменяющиеся цвета крыльев в спектре оттенков
Для управления эффектом циклического изменения цвета во время полета мы определяем функцию "ShiftHue", которая принимает любой цвет и изменяет его оттенок на заданную величину, не влияя на его яркость или насыщенность, создавая плавно смещенный вариант исходного цвета.
//+------------------------------------------------------------------+ //| Shift a color hue by rotating through HSV space | //+------------------------------------------------------------------+ color ShiftHue(color c, double shift) { //--- Normalize red channel to 0-1 range double r = ((c >> 16) & 0xFF) / 255.0; //--- Normalize green channel to 0-1 range double g = ((c >> 8) & 0xFF) / 255.0; //--- Normalize blue channel to 0-1 range double b = ( c & 0xFF) / 255.0; //--- Compute HSV value as the maximum of R, G, B double maxC = MathMax(r, MathMax(g, b)); //--- Compute the minimum channel for saturation calculation double minC = MathMin(r, MathMin(g, b)); //--- Initialize hue and saturation to zero double h = 0, s = 0; //--- Set value to the maximum channel double v = maxC; //--- Compute the channel spread double d = maxC - minC; //--- Compute saturation; avoid division by zero for achromatic colors s = (maxC == 0) ? 0 : d / maxC; //--- Compute hue only for chromatic colors if(d > 0) { //--- Hue from red sector if(maxC == r) h = (g - b) / d + (g < b ? 6 : 0); //--- Hue from green sector else if(maxC == g) h = (b - r) / d + 2; //--- Hue from blue sector else h = (r - g) / d + 4; //--- Normalize hue to 0-1 range h /= 6.0; } //--- Apply the requested hue shift h += shift; //--- Wrap hue above 1.0 back into range while(h > 1.0) h -= 1.0; //--- Wrap hue below 0.0 back into range while(h < 0.0) h += 1.0; //--- Compute the HSV sector index int hi = (int)(h * 6); //--- Compute the fractional part within the sector double f2 = h * 6 - hi; //--- Precompute HSV intermediate values double p = v * (1 - s), q = v * (1 - f2 * s), t2 = v * (1 - (1 - f2) * s); //--- Initialize output channels to the value double ro = v, go = v, bo = v; //--- Map HSV sector to RGB output channels switch(hi % 6) { case 0: ro = v; go = t2; bo = p; break; case 1: ro = q; go = v; bo = p; break; case 2: ro = p; go = v; bo = t2; break; case 3: ro = p; go = q; bo = v; break; case 4: ro = t2; go = p; bo = v; break; case 5: ro = v; go = p; bo = q; break; } //--- Recompose and return the hue-shifted color return ((uchar)(ro * 255) << 16) | ((uchar)(go * 255) << 8) | (uchar)(bo * 255); }
Начнем с извлечения красного, зеленого и синего каналов с помощью побитовых сдвигов и нормализации каждого из них до диапазона 0–1. На основе этих данных мы вычисляем представление цвета в виде оттенка, насыщенности и значения. Значение — это просто максимум из трех каналов, насыщенность — это разброс каналов, деленный на значение (с нулевым порогом для ахроматических цветов), а оттенок выводится путем определения доминирующего канала и вычисления его углового положения в пределах 60-градусного сектора цветового круга. После этого результат нормализуется в диапазоне 0–1, охватывающем все 360 градусов.
После извлечения оттенка мы добавляем к нему значение сдвига и возвращаем результат обратно в диапазон 0–1 с помощью простого цикла. Обеспечиваем, что оттенок всегда остается в допустимых пределах независимо от того, сколько полных оборотов накопил циклический аккумулятор за все время.
Для обратного преобразования в красный, зеленый и синий необходимо сопоставить сдвинутый оттенок с одним из шести секторов цветового круга. Мы вычисляем индекс сектора и долю его положения внутри него, затем получаем три промежуточных значения — p, q и t2 — из насыщенности и значения, которые представляют уровни канала на границах сектора. Далее оператор switch назначает эти промежуточные значения выходным каналам красного, зеленого и синего цветов в зависимости от того, в какой сектор попадает оттенок, охватывая все шесть переходов по кругу. Итоговые каналы масштабируются до диапазона 0–255 и объединяются в одно цветовое значение. Эта функция, вызываемая один раз для каждого сегмента крыла за фрейм с медленно продвигающейся фазой цветового цикла, обеспечивает постоянно меняющиеся цвета крыльев летающей бабочки. Далее мы определяем вспомогательные функции для рендеринга свечения и полета.
Рендеринг свечения и преобразование полета
Эти две функции обрабатывают визуальный эффект свечения и основное геометрическое преобразование, которое управляет всем движением полета: одна рисует мягкие радиальные ореолы на холсте, а другая перемещает каждую точку на бабочке в каждом фрейме.
//+------------------------------------------------------------------+ //| Draw a soft radial glow circle with quadratic falloff | //+------------------------------------------------------------------+ void DrawGlowCircle(CCanvas &canvas, int cx, int cy, int radius, color clr, uchar maxAlpha) { //--- Iterate over every row within the bounding box of the glow circle for(int dy = -radius; dy <= radius; dy++) { //--- Iterate over every column within the bounding box for(int dx = -radius; dx <= radius; dx++) { //--- Compute the exact Euclidean distance from the center double dist = MathSqrt((double)(dx * dx + dy * dy)); //--- Process only pixels within the glow radius if(dist <= radius) { //--- Compute linear falloff factor (1 at center, 0 at edge) double f = 1.0 - dist / (double)radius; //--- Apply quadratic falloff for a soft bloom appearance f = f * f; //--- Scale max alpha by the falloff factor uchar a = (uchar)(maxAlpha * f); //--- Skip fully transparent pixels for performance if(a > 0) { //--- Compute the absolute pixel X coordinate int px = cx + dx; //--- Compute the absolute pixel Y coordinate int py = cy + dy; //--- Write the pixel only if it lies within canvas bounds if(px >= 0 && px < canvas.Width() && py >= 0 && py < canvas.Height()) canvas.PixelSet(px, py, ColorToARGB(clr, a)); } } } } } //+------------------------------------------------------------------+ //| Apply flying transform to a world-space point | //+------------------------------------------------------------------+ void ApplyFlyingTransform(double &x, double &y) { //--- Compute wing flap scale: 1.0 = fully open, reduces by flap amplitude double flapScale = 1.0 - currentWingFlap; //--- Scale X distance from the body center axis to simulate wing flapping x = x * flapScale; //--- Apply vertical shear proportional to X distance for organic tilt y = y + x * currentTiltShear; //--- Translate horizontally by the sway offset x += currentSwayOffset; //--- Translate vertically by the bob offset y += currentBobOffset; }
Функция "DrawGlowCircle" перебирает каждый пиксель внутри ограничивающего прямоугольника заданного радиуса, вычисляет точное евклидово расстояние от центра с помощью функции MathSqrt. Для пикселей внутри радиуса мы вычисляем линейный коэффициент затухания от 1,0 в центре до 0,0 на краю, а затем возводим его в квадрат, чтобы применить квадратичное затухание. Именно это возведение в квадрат придает свечению мягкое, постепенное затухание, а не резкое линейное падение. Полученный коэффициент масштабирует максимальное значение альфа-канала для создания прозрачности для каждого пикселя, и любой пиксель с ненулевым значением альфа-канала записывается на холст с помощью метода PixelSet. Пиксели, которые становятся полностью прозрачными, полностью пропускаются для повышения производительности. Эта функция поддерживает более широкую инфраструктуру свечения, используемую вместе с линейными проходами эффекта свечения в цикле рисования кривых.
Функция "ApplyFlyingTransform" является единственной точкой, через которую проходит все движение полета. Она принимает пару координат в мировом пространстве по ссылке и изменяет ее на месте с помощью четырех последовательных операций. Во-первых, координата X масштабируется в сторону нуля с помощью текущего коэффициента взмаха крыла — поскольку тело находится в начале координат, масштабирование по оси X симметрично сжимает все точки крыла внутрь, имитируя смыкание крыльев. Затем к оси Y добавляется небольшое смещение, пропорциональное оси X, с использованием текущего сдвига наклона, что вводит наклон, из-за которого точки, расположенные дальше от оси корпуса, смещаются более вертикально — придавая наклону органичный, перспективный характер.
Наконец, к осям X и Y добавляются горизонтальное колебание и вертикальное смещение соответственно, перемещая всю бабочку в пространстве. Каждая точка на кривой, каждая конечная точка жилки, каждая точка чешуйки и центр тела проходят через эту функцию во время фазы полета, обеспечивая движение всей бабочки как единой целостной формы. Далее мы собираем точки бабочки, которые будут служить полным источником данных кривой.
Централизация сбора точек с помощью дополнительного преобразования полета
Вместо того чтобы, как мы делали раньше, повторять логику оценки кривых и сопоставления координат в нескольких местах, теперь мы объединяем все в функцию "CollectButterflyPoints", которая служит единым источником данных о кривых для всего конвейера рендеринга.
//+------------------------------------------------------------------+ //| Collect full butterfly curve points with optional flying transform| //+------------------------------------------------------------------+ int CollectButterflyPoints(double tEnd, double &xWorld[], double &yWorld[], double &xPx[], double &yPx[], double rangeX, double rangeY, int plotW, int plotH, bool applyFlying, double &outMaxDist, double &outCX, double &outCY) { //--- Estimate the maximum number of points to pre-allocate arrays int est = (int)((tEnd - butterflyTStart) / butterflyTStep) + 2; //--- Ensure at least one slot is allocated if(est < 1) est = 1; //--- Pre-allocate world-space and pixel-space coordinate arrays ArrayResize(xWorld, est); ArrayResize(yWorld, est); ArrayResize(xPx, est); ArrayResize(yPx, est); //--- Initialize point counter int count = 0; //--- Initialize the maximum radial distance from center outMaxDist = 0; //--- Compute the wing center at the world-space origin double cx = 0, cy = 0; //--- Apply flying transform to the center if in flying phase if(applyFlying) ApplyFlyingTransform(cx, cy); //--- Map the transformed center X to a pixel column outCX = (cx - butterflyMinX) / rangeX * plotW; //--- Map the transformed center Y to a pixel row (inverted axis) outCY = (butterflyMaxY - cy) / rangeY * plotH; //--- Traverse the parametric domain and collect valid curve points for(double t = butterflyTStart; t <= tEnd; t += butterflyTStep) { //--- Evaluate the butterfly radial term at this T double term = MathExp(MathCos(t)) - 2 * MathCos(4 * t) - MathPow(MathSin(t / 12), 5); //--- Compute X world coordinate double x = MathSin(t) * term; //--- Compute Y world coordinate double y = MathCos(t) * term; //--- Skip invalid coordinate pairs if(!MathIsValidNumber(x) || !MathIsValidNumber(y)) continue; //--- Store the raw world-space coordinates xWorld[count] = x; yWorld[count] = y; //--- Copy world coordinates for optional transform double wx = x, wy = y; //--- Apply flying transform to pixel-mapped coordinates if required if(applyFlying) ApplyFlyingTransform(wx, wy); //--- Map the (possibly transformed) X to a pixel column xPx[count] = (wx - butterflyMinX) / rangeX * plotW; //--- Map the (possibly transformed) Y to a pixel row (inverted axis) yPx[count] = (butterflyMaxY - wy) / rangeY * plotH; //--- Compute radial distance of this pixel point from the wing center double dist = MathSqrt(MathPow(xPx[count] - outCX, 2) + MathPow(yPx[count] - outCY, 2)); //--- Update the maximum distance for gradient normalization outMaxDist = MathMax(outMaxDist, dist); //--- Increment the valid point count count++; } //--- Trim all arrays to the exact number of valid points collected ArrayResize(xWorld, count); ArrayResize(yWorld, count); ArrayResize(xPx, count); ArrayResize(yPx, count); //--- Return the total number of valid points collected return count; }
Здесь мы начинаем с оценки максимального количества точек на основе диапазона T и размера шага и предварительного выделения памяти для всех четырех массивов координат — X и Y в мировом пространстве и X и Y в пиксельном пространстве — с помощью функции ArrayResize во избежание повторных перераспределений памяти во время цикла сбора данных. Центр крыла вычисляется в начале координат в мировом пространстве и, при необходимости, передается через функцию "ApplyFlyingTransform" перед отображением в пиксельные координаты. Этим гарантируется, что центральная точка, используемая для нормализации радиального градиента и расчета расстояния между точками масштабирования, всегда соответствует преобразованному положению бабочки, а не статическому началу координат.
Цикл проходит по параметрической области от начала до указанного значения "tEnd". На этапе построения кривой "tEnd" может охватывать только часть диапазона. На каждом шаге вычисляется уравнение бабочки, пропускаются недопустимые точки, а исходные мировые координаты сохраняются в мировых массивах. Затем копия этих координат при необходимости преобразуется с помощью функции "ApplyFlyingTransform", после чего отображается в пиксельное пространство и сохраняется в массивах пикселей.
Радиальное расстояние от преобразованного центра вычисляется для каждой пиксельной точки с помощью MathSqrt, а максимальное расстояние отслеживается с помощью MathMax для использования в качестве эталона нормализации радиального градиента. После завершения цикла все четыре массива обрезаются до точного допустимого значения, и функция возвращает это значение. Каждый последующий этап рендеринга — заливки, контуры, прожилки, чешуйки и тело — использует массивы, созданные этой функцией, обеспечивая согласованность данных по всему фрейму. На этом всё о новых вспомогательных функциях. Теперь мы расширим или переработаем существующие вспомогательные функции для поддержки новой системы анимации. Начнём с вспомогательной функции для получения цвета крыльев. При необходимости будем выделять важные места для наглядности.
Расширение разрешения цвета крыльев с помощью циклического изменения оттенков
Это небольшое, но важное обновление существующей функции "GetWingColorForT". Сравнение границ сегментов и логика выбора цвета остались прежними, но теперь перед возвратом результата мы добавляем ещё один шаг.
//+------------------------------------------------------------------+ //| Resolve wing curve color for a given parametric T value | //+------------------------------------------------------------------+ color GetWingColorForT(double tParameter) { //--- Select base color by T segment position color base; if (tParameter <= 3.0 * M_PI) base = blueCurveColor; // Segment 1: blue else if(tParameter <= 6.0 * M_PI) base = redCurveColor; // Segment 2: red else if(tParameter <= 9.0 * M_PI) base = orangeCurveColor; // Segment 3: orange else base = greenCurveColor; // Segment 4: green //--- Apply hue shift during flying phase if color cycling is enabled if(enableColorCycling && animationPhase == ANIM_FLYING) base = ShiftHue(base, colorCyclePhase); //--- Return the resolved wing segment color return base; }
Ранее эта функция возвращала цвет сегмента напрямую. Теперь, после выбора базового цвета, мы проверяем, включена ли циклическая смена цветов и является ли текущая фаза анимации "ANIM_FLYING" — и только если оба условия верны, мы передаем базовый цвет через функцию "ShiftHue" с накопителем текущей фазы цикла перед его возвратом. Это означает, что циклическая смена цветов строго ограничена фазой полета и не влияет на фазы рисования, заливки или детализации, сохраняя визуальную чистоту и согласованность этих более ранних этапов. Поскольку "GetWingColorForT" вызывается для каждой имитирующей чешуйку точки, расположенной вдоль границы крыльев, этого единственного дополнения достаточно, чтобы вся текстура чешуек меняла цвет синхронно с контурными кривыми во время полета. После этого наша следующая цель — вспомогательная функция для жилок, чтобы они двигались синхронно с преобразованием полета.
Обновление жилок крыльев и чешуек в целях поддержки функции анимации
Функции "DrawWingVeins" и "DrawWingScales" сохраняют свою основную логику рисования из предыдущей части без изменений, но каждая из них получает два целевых дополнения для интеграции в систему анимации.
//+------------------------------------------------------------------+ //| Draw anti-aliased wing vein lines radiating from the body center | //+------------------------------------------------------------------+ void DrawWingVeins(CCanvas &canvas, double &xPts[], double &yPts[], int ptCount, double rangeX, double rangeY, int plotW, int plotH, double opMul = 1.0) { //--- Skip drawing if wing veins have been disabled by the user if(!showButterflyWingVeins) return; //--- Initialize body center in world space at the origin double bcx = 0, bcy = 0; //--- Apply flying transform to the body center if in flying phase if(animationPhase == ANIM_FLYING) ApplyFlyingTransform(bcx, bcy); //--- Map the transformed body center X to a pixel column int cxPx = (int)((bcx - butterflyMinX) / rangeX * plotW); //--- Map the transformed body center Y to a pixel row (inverted axis) int cyPx = (int)((butterflyMaxY - bcy) / rangeY * plotH); //--- Set vein color as a darkened body color scaled by opacity multiplier uint argbVein = ColorToARGB(DarkenColor(butterflyBodyColor, 0.2), (uchar)(150 * butterflyWingOpacity * opMul)); //--- Sample wing edge points at regular intervals to define vein endpoints for(int i = 0; i < ptCount; i += 50) { //--- Get the world-space X coordinate of this wing edge sample double wx = xPts[i]; //--- Get the world-space Y coordinate of this wing edge sample double wy = yPts[i]; //--- Apply flying transform to the wing edge point if in flying phase if(animationPhase == ANIM_FLYING) ApplyFlyingTransform(wx, wy); //--- Map the transformed wing point X to a pixel column int px = (int)((wx - butterflyMinX) / rangeX * plotW); //--- Map the transformed wing point Y to a pixel row (inverted axis) int py = (int)((butterflyMaxY - wy) / rangeY * plotH); //--- Draw an anti-aliased vein line from the body center to the wing edge canvas.LineAA(cxPx, cyPx, px, py, argbVein); } } //+------------------------------------------------------------------+ //| Draw wing scale texture dots along and inside the wing boundary | //+------------------------------------------------------------------+ void DrawWingScales(CCanvas &canvas, double &xCoords[], double &yCoords[], int ptCount, double rangeX, double rangeY, int plotW, int plotH, double ctrX, double ctrY, double maxDist, double opMul = 1.0) { //--- Skip drawing if wing scales have been disabled by the user if(!showButterflyWingScales) return; //--- Sample wing edge points at a dense interval for scale placement for(int i = 0; i < ptCount; i += 4) { //--- Get the world-space X coordinate of this scale sample double wx = xCoords[i]; //--- Get the world-space Y coordinate of this scale sample double wy = yCoords[i]; //--- Apply flying transform to the scale point if in flying phase if(animationPhase == ANIM_FLYING) ApplyFlyingTransform(wx, wy); //--- Map the transformed scale point X to a pixel column double pixelX = (wx - butterflyMinX) / rangeX * plotW; //--- Map the transformed scale point Y to a pixel row (inverted axis) double pixelY = (butterflyMaxY - wy) / rangeY * plotH; //--- Reconstruct the approximate T parameter for this point index double tP = butterflyTStart + (double)i * butterflyTStep; //--- Resolve the wing segment color for this T value (with cycling support) color baseColor = GetWingColorForT(tP); //--- Compute the radial distance of this scale from the wing center double dist = MathSqrt(MathPow(pixelX - ctrX, 2) + MathPow(pixelY - ctrY, 2)); //--- Normalize distance to a 0-1 blend factor double factor = (maxDist > 0) ? dist / maxDist : 0; //--- Blend the base color slightly toward white for a shimmering edge effect color scaleColor = InterpolateColors(baseColor, LightenColor(baseColor, 0.2), factor); //--- Convert scale color to ARGB with wing opacity and multiplier applied uint argbScale = ColorToARGB(scaleColor, (uchar)(180 * butterflyWingOpacity * opMul)); //--- Vary scale dot radius slightly based on point index for organic texture int radius = 2 + (i % 3); //--- Draw the primary scale dot on the wing boundary DrawFilledCircle(canvas, (int)pixelX, (int)pixelY, radius, argbScale); //--- Compute a vector from the wing center to this scale point double dx = pixelX - ctrX, dy = pixelY - ctrY; //--- Compute the vector magnitude for normalization double norm = MathSqrt(dx * dx + dy * dy); //--- Add a second inward-offset scale dot only if the vector is non-degenerate if(norm > 0) { //--- Normalize the X component of the inward direction dx /= norm; //--- Normalize the Y component of the inward direction dy /= norm; //--- Draw a secondary inward scale dot slightly smaller for depth DrawFilledCircle(canvas, (int)(pixelX - dx * radius * 2), (int)(pixelY - dy * radius * 2), radius - 1, argbScale); } } }
Первое изменение в обеих функциях — добавление параметра "opMul", значение которого по умолчанию равно 1.0. Этот множитель непрозрачности напрямую включен в расчет альфа-канала для цвета жилок и цвета точек, имитирующих чешуйки, соответственно — он умножается на существующие значения непрозрачности, так что во время фазы плавного появления деталей оба слоя постепенно переходят от прозрачного к полностью видимому состоянию, а не резко появляются с максимальной интенсивностью.
Второе изменение — интеграция преобразования полета. В функции "DrawWingVeins" центр тела инициализируется в начале координат мирового пространства, а затем передается через функцию "ApplyFlyingTransform", если текущая фаза — "ANIM_FLYING", что гарантирует перемещение корня жилок вместе с бабочкой, а не их фиксацию в центре холста. Каждая точка края крыла, выбранная в качестве образца, аналогичным образом преобразуется перед отображением в пиксельное пространство, так что линии жилок во время полета простираются от корректного преобразованного центра тела до корректной преобразованной границы крыльев. Функция "DrawWingScales" применяет тот же принцип: каждая точка выборки чешуек копируется, преобразуется (если объект находится в полете), а затем отображается в пиксельном пространстве перед размещением точки чешуйки, благодаря чему вся текстура чешуйки остается привязанной к движущейся поверхности крыльев на протяжении всей анимации. Далее мы добавим рендеринг с учетом анимации при рисовании бабочки.
Обновление основного рендерера бабочки для анимации и свечения
Функция "DrawRealisticButterfly" сохраняет всю существующую логику заливки, детализации и рисования контура, но получает два важных дополнения вверху и внутри цикла рисования контура — управление рендерингом с учетом фазы и систему неонового свечения.
//+------------------------------------------------------------------+ //| Render filled, outlined, and detailed butterfly with animation | //+------------------------------------------------------------------+ void DrawRealisticButterfly(CCanvas &canvas, int plotWidth, int plotHeight, double rangeX, double rangeY) { //--- Set defaults: use full curve range and full opacity for both fills and details double effectiveTEnd = butterflyTEnd; double effectiveFillOp = 1.0; double effectiveDetailOp = 1.0; bool drawFills = true; bool drawDetails = true; //--- Determine whether flying transform should be applied this frame bool applyFlying = (animationPhase == ANIM_FLYING); //--- Adjust draw parameters according to the current animation phase switch(animationPhase) { case ANIM_DRAWING_CURVE: //--- Limit curve drawing to the current animation T progress effectiveTEnd = animationCurveT; //--- Suppress fills and details during the curve-drawing phase drawFills = false; drawDetails = false; break; case ANIM_FILLING_WINGS: //--- Scale fill opacity by the current fill fade progress effectiveFillOp = animationFillAlpha; //--- Suppress details until filling is complete drawDetails = false; break; case ANIM_ADDING_DETAILS: //--- Scale detail opacity by the current details fade progress effectiveDetailOp = animationDetailAlpha; break; case ANIM_FLYING: //--- Use full opacity for all elements during flying break; } //--- EXISTING LOGIC //--- Draw each of the four wing outline color segments for(int s = 0; s < 4; s++) { //--- Get the T start value for this segment double sFrom = segBounds[s]; //--- Clamp the T end value to the current animation progress double sTo = MathMin(segBounds[s + 1], effectiveTEnd); //--- Skip segments that have not yet been reached by the animation if(sFrom >= effectiveTEnd) break; //--- Retrieve the base color for this segment color curveClr = segBaseColors[s]; //--- Apply color cycling if in flying phase and cycling is enabled if(enableColorCycling && animationPhase == ANIM_FLYING) curveClr = ShiftHue(curveClr, colorCyclePhase); //--- Convert segment color to fully opaque ARGB uint segARGB = ColorToARGB(curveClr, 255); //--- Initialize previous pixel coordinates for connectivity double prevPx = -1, prevPy = -1; //--- Traverse this segment's T range to draw the outline for(double t = sFrom; t <= sTo; t += butterflyTStep) { //--- Evaluate the butterfly radial term at this T double term = MathExp(MathCos(t)) - 2 * MathCos(4 * t) - MathPow(MathSin(t / 12), 5); //--- Compute X world coordinate double x = MathSin(t) * term; //--- Compute Y world coordinate double y = MathCos(t) * term; //--- Reset connectivity on invalid points if(!MathIsValidNumber(x) || !MathIsValidNumber(y)) { prevPx = -1; prevPy = -1; continue; } //--- Apply flying transform to the curve point if in flying phase if(applyFlying) ApplyFlyingTransform(x, y); //--- Map X to pixel column double cpx = (x - butterflyMinX) / rangeX * plotWidth; //--- Map Y to pixel row (inverted axis) double cpy = (butterflyMaxY - y) / rangeY * plotHeight; //--- Round to nearest integer pixel coordinate int ix = (int)MathRound(cpx), iy = (int)MathRound(cpy); //--- Draw the segment line only if a valid previous point exists if(prevPx >= 0 && prevPy >= 0) { //--- Store rounded previous pixel coordinates int px1 = (int)MathRound(prevPx), py1 = (int)MathRound(prevPy); //--- Draw neon glow bloom layers behind the core line if enabled if(enableCurveGlow) { //--- Iterate from outermost to innermost glow layer for(int g = curveGlowLayers; g >= 1; g--) { //--- Compute alpha for this bloom layer (diminishes with layer) uchar ga = (uchar)(25 * curveGlowIntensity / g); //--- Skip layers that are effectively invisible if(ga < 2) continue; //--- Convert glow color to ARGB with computed bloom alpha uint gc = ColorToARGB(curveClr, ga); //--- Draw offset lines around the core to simulate glow spread for(int off = -g; off <= g; off++) { //--- Horizontal offset glow pass canvas.LineAA(px1 + off, py1, ix + off, iy, gc); //--- Vertical offset glow pass canvas.LineAA(px1, py1 + off, ix, iy + off, gc); } } //--- Compute a brightened center highlight color for the hot core color bright = LightenColor(curveClr, 0.5); //--- Draw the white-hot center highlight over the bloom layers canvas.LineAA(px1, py1, ix, iy, ColorToARGB(bright, (uchar)(150 * curveGlowIntensity))); } //--- Draw the primary anti-aliased outline line canvas.LineAA(px1, py1, ix, iy, segARGB); //--- Draw the offset line for additional visual thickness canvas.LineAA(px1 + 1, py1, ix + 1, iy, segARGB); } //--- Store current pixel X for next iteration prevPx = cpx; //--- Store current pixel Y for next iteration prevPy = cpy; } } //--- EXISTING LOGIC }
В начале функции мы объявляем набор эффективных параметров отрисовки до начала любого процесса рисования. Эффективное конечное значение T, непрозрачность заливки и непрозрачность детализации по умолчанию равны своим полным значениям, а флаги заливки и детализации по умолчанию имеют значение true. Далее оператор switch переопределяет эти значения по умолчанию в зависимости от текущей фазы анимации: во время отрисовки кривой эффективное конечное значение T ограничивается текущим аккумулятором анимации, и заливка, и детализация полностью подавляются; во время заливки крыльев непрозрачность заливки масштабируется в соответствии с текущим альфа-каналом заливки, а детализация остается подавленной.
Во время добавления деталей непрозрачность деталей масштабируется с помощью альфа-накопителя деталей, а во время полета все значения по умолчанию остаются неизменными. Далее эти параметры передаются в последующие вызовы функций заливки и детализации, так что каждый слой "бабочки" корректно реагирует на любую фазу, которая в данный момент активна, без необходимости какой-либо функции рисования напрямую знать об этой фазе.
В цикле рисования сегмента контура отображаются два изменения. Во-первых, цвет сегмента теперь передается через "ShiftHue" в соответствии с текущей фазой цветового цикла, если циклирование включено, а бабочка находится в фазе полета, поэтому контурные линии меняют цвет синхронно с точками чешуек во время полета. Вторым и более существенным изменением является блок неонового свечения, который выполняется перед двумя основными вызовами LineAA для каждого сегмента линии.
Когда функция свечения включена, мы начинаем обратный отсчет от самого внешнего слоя bloom до единицы, вычисляя убывающий альфа—канал для каждого слоя, деля базовую интенсивность свечения на индекс слоя, чтобы внешние слои были более тусклыми, а внутренние - более яркими. Для каждого слоя мы рисуем сетку из смещенных проходов "LineAA" вокруг положения основной линии, отступая как по горизонтали, так и по вертикали на расстояние слоя, чтобы распределить расплывание во всех направлениях. После всех проходов проявления, в самом конце рисуется более яркая линия подсвеченной области, используя осветленную версию цвета кривой. Тем самым создается раскаленное добела ядро, которое располагается поверх ореола свечения.
Затем последними рисуются два сплошных основных контурных штриха. Они аккуратно располагаются поверх всех слоев свечения. После этого мы установим время анимации в обработчике OnTimer, но нам нужно инициализировать таймер с помощью других добавленных нами переменных.
Инициализация системы анимации при запуске
Обработчик OnInit сохраняет всю существующую логику создания и настройки холста, но в конце получает новый блок, который инициализирует состояние анимации перед первым рендерингом.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- EXISTING LOGIC //--- Initialize animation state if drawing animation is enabled if(enableAnimation) { //--- Start from the curve-drawing phase animationPhase = ANIM_DRAWING_CURVE; //--- Begin the T accumulator at the curve start animationCurveT = butterflyTStart; //--- Begin fill opacity at zero (fully transparent) animationFillAlpha = 0.0; //--- Begin detail opacity at zero (fully transparent) animationDetailAlpha = 0.0; } //--- Skip drawing animation but enable flying if only flying is requested else if(enableFlyingAnimation) { animationPhase = ANIM_FLYING; } //--- Both animations disabled: show full static butterfly immediately else { //--- Use flying state as the complete steady state animationPhase = ANIM_FLYING; //--- Zero all flying transform values to lock wings open and centered currentWingFlap = 0; currentBobOffset = 0; currentSwayOffset = 0; currentTiltShear = 0; } //--- Reset all flying oscillation phase accumulators flyWingPhase = 0; flyBobPhase = 0; flySwayPhase = 0; flyTiltPhase = 0; //--- Reset all flying transform values to neutral currentWingFlap = 0; currentBobOffset = 0; currentSwayOffset = 0; currentTiltShear = 0; //--- Start the millisecond timer if any animation is enabled if(enableAnimation || enableFlyingAnimation) EventSetMillisecondTimer(animationTimerMs); //--- Render the full main visualization on startup RenderMainVisualization(); //--- Render the legend panel on startup RenderLegend(); //--- Enable mouse move events for drag and resize interaction ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Force an immediate chart redraw ChartRedraw(); //--- return(INIT_SUCCEEDED); }
Условная конструкция из трёх ветвей определяет начальное состояние на основе входных данных анимации. Если включена полная анимация рисования, фаза устанавливается в значение "ANIM_DRAWING_CURVE", накопитель кривой T сбрасывается до параметрического начального значения, а накопители прозрачности заливки и детализации обнуляются, так что бабочка изначально полностью невидима и строится с нуля. Если включена только анимация полета без последовательности рисования, мы сразу переходим к "ANIM_FLYING", так что бабочка появляется немедленно в своей полной форме и сразу же начинает летать. Если обе анимации отключены, мы также устанавливаем фазу на "ANIM_FLYING", но обнуляем все значения преобразования — взмах крыльев, смещение качания, смещение колебания и сдвиг наклона — фиксируя бабочку в ее полностью открытом, центрированном, статическом положении без движения.
Независимо от того, какая ветвь будет выполнена, все четыре аккумулятора фазы осциллятора и все четыре выходные переменные преобразования будут сброшены до нуля, чтобы обеспечить чистое нейтральное начальное состояние. Наконец, если активен какой-либо из переключателей анимации, вызывается EventSetMillisecondTimer с настроенным интервалом таймера для запуска тактового генератора кадров, который будет управлять обработчиком OnTimer — если оба переключателя выключены, таймер не запускается, и бабочка просто отображается один раз как статическое изображение. Теперь мы используем таймер в обработчике событий таймера.
Управление анимацией с помощью таймера и очистка при выходе
Обработчик "OnTimer" является движком всей системы анимации — он срабатывает на каждом кадре с настроенным интервалом и продвигает ту фазу, которая в данный момент активна, прежде чем запустить перерисовку. Обработчик OnDeinit получает одно небольшое, но важное дополнение.
//+------------------------------------------------------------------+ //| Expert timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Advance the master global time accumulator each frame globalTime += 0.05; //--- Drive the correct animation behavior based on the current phase switch(animationPhase) { case ANIM_DRAWING_CURVE: //--- Advance the curve T by the configured draw speed animationCurveT += curveDrawSpeed; //--- Transition to fill phase once the full curve has been drawn if(animationCurveT >= butterflyTEnd) { //--- Lock T at the exact end value animationCurveT = butterflyTEnd; //--- Advance to the wing-filling phase animationPhase = ANIM_FILLING_WINGS; //--- Reset fill opacity to start the fade-in from transparent animationFillAlpha = 0.0; } break; case ANIM_FILLING_WINGS: //--- Advance fill opacity by the configured fade speed animationFillAlpha += fillFadeSpeed; //--- Transition to details phase once fill is fully opaque if(animationFillAlpha >= 1.0) { //--- Lock fill opacity at full animationFillAlpha = 1.0; //--- Advance to the details-adding phase animationPhase = ANIM_ADDING_DETAILS; //--- Reset detail opacity to start the fade-in from transparent animationDetailAlpha = 0.0; } break; case ANIM_ADDING_DETAILS: //--- Advance detail opacity by the configured fade speed animationDetailAlpha += detailsFadeSpeed; //--- Transition to flying phase once all details are fully visible if(animationDetailAlpha >= 1.0) { //--- Lock detail opacity at full animationDetailAlpha = 1.0; //--- Start the flying animation if it is enabled if(enableFlyingAnimation) { //--- Advance to the flying phase animationPhase = ANIM_FLYING; //--- Reset all oscillation phase accumulators flyWingPhase = 0; flyBobPhase = 0; flySwayPhase = 0; flyTiltPhase = 0; } else { //--- Animation fully complete; transition to static flying state animationPhase = ANIM_FLYING; //--- Zero transform values to lock the butterfly in place currentWingFlap = 0; currentBobOffset = 0; currentSwayOffset = 0; currentTiltShear = 0; //--- Stop the timer; no further animation is needed EventKillTimer(); //--- Perform a final render to show the completed static butterfly RenderMainVisualization(); ChartRedraw(); //--- Exit early; no further processing needed this tick return; } } break; case ANIM_FLYING: //--- Stop the timer if flying animation has been disabled if(!enableFlyingAnimation) { EventKillTimer(); return; } //--- Advance the wing flap oscillation phase flyWingPhase += wingFlapSpeed; //--- Advance the vertical bob oscillation phase flyBobPhase += verticalBobSpeed; //--- Advance the horizontal sway oscillation phase flySwayPhase += horizontalSwaySpeed; //--- Advance the tilt oscillation phase flyTiltPhase += tiltSwaySpeed; //--- Compute current wing flap scale using an abs-sine for smooth open-close currentWingFlap = wingFlapAmplitude * MathAbs(MathSin(flyWingPhase)); //--- Compute current vertical bob offset using a sine wave currentBobOffset = verticalBobAmplitude * MathSin(flyBobPhase); //--- Compute current horizontal sway offset using a sine wave currentSwayOffset = horizontalSwayAmplitude * MathSin(flySwayPhase); //--- Compute current tilt shear factor using a sine wave currentTiltShear = tiltSwayAmplitude * MathSin(flyTiltPhase); //--- Advance the color cycle phase if color cycling is enabled if(enableColorCycling) colorCyclePhase += colorCycleSpeed; break; } //--- Rebuild the main visualization for this animation frame RenderMainVisualization(); //--- Refresh the chart to display the new frame ChartRedraw(); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- //--- Stop the animation timer EventKillTimer(); //--- Destroy the main canvas and release its resources mainCanvas.Destroy(); //--- Destroy the curve canvas and release its resources curveCanvas.Destroy(); //--- Destroy the legend canvas and release its resources legendCanvas.Destroy(); //--- Refresh the chart after all objects have been removed ChartRedraw(); }
На каждом тике мы продвигаем глобальный временной аккумулятор, а затем включаем текущую фазу анимации. В "ANIM_DRAWING_CURVE" аккумулятор кривой T продвигается на скорость отрисовки в каждом фрейме — как только он достигает или превышает максимальное конечное значение, он фиксируется точно на этом значении, фаза переходит в "ANIM_FILLING_WINGS", а аккумулятор прозрачности заливки сбрасывается до нуля, чтобы начать следующее проявление от состояния прозрачности. В "ANIM_FILLING_WINGS" альфа-канал заливки продвигается на скорость появления заливки в каждом фрейме, пока не достигнет 1,0, после чего он фиксируется, и фаза переходит в "ANIM_ADDING_DETAILS", при этом альфа-канал деталей сбрасывается до нуля.
В "ANIM_ADDING_DETAILS" альфа-канал детализации продвигается аналогично — после завершения перехода происходит разветвление в зависимости от того, включен ли полет. Если да, фаза переходит в фазу "ANIM_FLYING", и все аккумуляторы осцилляторов сбрасываются до нуля для чистого начала полета. Если функция полета отключена, значения преобразования обнуляются для фиксации статичности бабочки, таймер останавливается с помощью EventKillTimer, запускается финальный рендеринг, и функция завершает работу раньше, поскольку дальнейшие отметки не требуются.
В функции "ANIM_FLYING" все четыре фазовых аккумулятора осцилляторов увеличиваются на величину своих входных значений скорости в каждом фрейме, и четыре выходные переменные преобразования пересчитываются на их основе — взмах крыльев с использованием абсолютной синусоидальной волны для плавного цикла открытия-закрытия, а также покачивание, колебание и наклон с использованием обычной синусоидальной волны. Фаза цикла цвета увеличивается в каждом фрейме, если циклическое движение включено. После переключения вызываются функции "RenderMainVisualization" и ChartRedraw для вывода обновленного фрейма на экран.
Обработчик OnDeinit добавляет вызов "EventKillTimer" перед тремя вызовами уничтожения холста, гарантируя, что таймер анимации корректно останавливается в момент удаления программы, а не продолжает срабатывать в состоянии уничтоженного холста. Это знаменует собой полную переработку для добавления анимации. В следующем разделе мы протестируем программу.
Визуализация
Мы скомпилировали программу и прикрепили ее к графику MetaTrader 5, чтобы проверить результат всей анимации. Ниже показан итоговый результат в виде GIF-изображения.

Бабочка рисуется мазок за мазком, затем происходит плавное проявление заливки крыльев. Далее добавляются детали поверхности и тело. Затем она переходит в непрерывный полет с видимыми взмахами крыльев, вертикальными покачиваниями, горизонтальными колебаниями и наклонами. Неоновое свечение аккуратно располагается вокруг контурных штрихов, а цвета крыльев постепенно меняются в зависимости от оттенка на протяжении всего полета.
Заключение
В заключение, мы расширили программу создания холста с бабочкой, добавив четырехэтапную систему анимации, которая последовательно прорисовывает контур кривой, плавно добавляет заливку крыльев, раскрывает детали поверхности и тела, а затем переходит в непрерывный полет, управляемый четырьмя независимыми осцилляторами для взмахов крыльев, вертикального покачивания, горизонтального колебания и наклона. Мы также добавили эффект неонового свечения вокруг контуров крыльев и систему циклической смены оттенков, которая изменяет цвета крыльев в цветовом спектре во время полета, и все это управляется миллисекундным таймером. После прочтения статьи вы сможете:
- Создавать многофазную систему анимации на холсте MQL5, используя управляемую таймером машину состояний, которая автоматически упорядочивает фазы рисования, проявления цвета и движения
- Имитировать органичное движение полета, комбинируя независимые синусоидальные осцилляторы для взмахов, покачиваний, колебаний и наклона, применяемые в качестве геометрических преобразований к каждой отображаемой точке
- Добавлять эффект неонового свечения к линейным рисункам на холсте, накладывая несколько проходов полупрозрачных смещенных линий вокруг осветленного основного штриха
В следующей части мы продолжим изучение возможностей рисования на холсте MQL5, обратившись к совершенно другой математической кривой, расширив серию новым параметрическим форматом и собственным уникальным подходом к визуальному рендерингу.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22139
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Рыночные секреты Ларри Уильямса (Часть 12): Торговля разворотами Smash Day на основе контекста
Разработка инструментария для анализа Price Action (Часть 66): Создание сканера паттерна "голова и плечи" с проверкой структуры на MQL5
Сеточный советник на клеточном автомате с онлайн-обучением в MQL5 (Часть III): Живой граф признаков
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: «Торговые инструменты MQL5 (часть 29): пошаговая анимация бабочки на Canvas».
Автор: Аллан Мунене Мутирия