Алгоритм трехточечного градиента

в 8:44, , рубрики: Action Script, actionscript 3.0, flash, Блог компании Mail.Ru Group, градиент, Программирование, треугольник, шейдер, метки: , , , ,

Приветствую, друзья.

В данной статье я расскажу вам о том, как рисовать треугольный градиент. Наверняка некоторые языки программирования имеют встроенные средства для выполнения этой задачи. Мы же с вами попробуем реализовать такой градиент используя стандартные геометрический формулы. Вот что в итоге необходимо получить:
Алгоритм трехточечного градиента

Что имеем:

Рассмотрим треугольник с вершинами в точках A,B и C. Точки пересечения перпендикуляров, опущенных из вершин ABC, с противоположными сторонами треугольника назовем A", B" и C":
Алгоритм трехточечного градиента

Задача:

Необходимо в каждой точке внутри треугольника(назовем такую точку O), определить проценты близости этой точки к каждой вершине(A,B,C) треугольника. Проценты в сумме должны дать значение 100.
Алгоритм трехточечного градиента

Решение:

Все что нам понадобится, это формулы пересечения прямых и уравнение перпендикуляра, опущенного из точки на прямую на плоскости. Определить точку пересечения прямых можно по формуле:

// прямые, заданные по двум точкам
l1 = {p1,p2};
l2 = {p1,p2};
// 
d = (l2.p2.y-l2.p1.y) * (l1.p2.x-l1.p1._x) - (l2.p2.x-l2.p1.x) * (l1.p2.y-l1.p1.y);
a = (l2.p2.x-l2.p1.x) * (l1.p1.y-l2.p1._y) - (l2.p2.y-l2.p1.y) * (l1.p1.x-l2.p1.x);
// точка пересечения
x0 = l1.p1.x + a * (l1.p2.x-l1.p1.x)/d;
y0 = l1.p1.y + a * (l1.p2.y-l1.p1.y)/d;

Получить уравнение перпендикуляра опущенного из точки A на прямую line можно так:

// точка задана по двум координатам
A = {x,y};
// линия задана по двум точкам
line = {p1,p2};
//
// определяем вектор направления линии line
dir = {0,0};
dir.x = line.p2.x - line.p1.x;
dir.y = line.p2.y - line.p1.y;
//
// очевидно, что первая точка искомой линии(перпендикуляра) будет точка A
// найдем вторую точку
point2 = {0,0};
if (dir.y != 0) {
	temp = dir.x*A.x + dir.y*A.y;
	point2.x = A.x != 1 ? 1 : 2;
	point2.y = (-dir.x * point2.x + temp) / dir.y;
} else {
	point2.x = A.x;
	point2.y = line.p1.y;
}
//
// искомая линия, перпендикуляр опущенный из точки A на прямую line,
// будет прямая проходящая через точки A и point2
// Для решения поставленной в статье задачи,
// нам будет необходимо найти длину высоты,
// опущенной из вершины треугольника на противоположную сторону.
// Эту длину можно определить следующим образом.
// Находим точку пересечения прямой A-point2 и противоположной точке А прямой в треугольнике,
// и находим расстояние между найденной точкой и заданной A.

Опустим перепендикуляр из заданной точки O на перпендикуляр опущенный из точки A на противоположную сторону треугольника. Точку пересечения полученной линии с перпендикуляром, опущенным из точки A, назовем Ah. Аналогично находим точки Bh и Ch:
Алгоритм трехточечного градиента

Процент близости от заданной точки О к вершине A можно определить отношением AAh/AA", где AAh — длина отрезка A->Ah, а AA" — длина высоты опущенной из вершины A на противоположную сторону (т.е. отрезок A->A"):

p1 = AAh/AA";
p2 = BBh/BB";
p3 = CCh/CC";

Определив проценты близости точки, можем определить цвет этой точки по формуле:

red = color1.red*p1 + color2.red*p2 + color3.red*p3;
green = color1.green*p1 + color2.green*p2 + color3.green*p3;
blue = color1.blue*p1 + color2.blue*p2 + color3.blue*p3;

Где red, green и blue — это каналы в цветовой схеме RGB. Получив значения каналов RGB для заданной точки, мы можем получить значение цвета в шеснадцатеричном формате:

color = (red << 16) + (green << 8) + blue;

Обратное преобразование из шестандцатеричного кода в RGB можно сделать с помощью формул:

color = 0xffff00;
// красный канал
red = (color >>> 16) & 0xff
// зеленый канал
green = (color >>> 8) & 0xff
// синий канал
blue = color & 0xff

Более подробно про работу с каналами RGB можно почитать здесь.

Пример на языке ActionScript 3.0

Посмотреть результат вы можете во флешке.

Исходный код флешки
package
{
	import flash.display.Sprite;
	import flash.filters.GlowFilter;
	
	import ru.flashpress.callback.ICallback;
	import ru.flashpress.geom.line.FPGLine2d;
	import ru.flashpress.geom.line.math.FPGLineToPoint2dMath;
	import ru.flashpress.geom.point.FPGPoint2d;
	import ru.flashpress.geom.point.FPGPoint2dMath;
	import ru.flashpress.geom.triangles.FPGTriangle2d;
	import ru.flashpress.geom.view.core.FillData;
	import ru.flashpress.geom.view.line.LineView;
	import ru.flashpress.geom.view.point.PointView;
	import ru.flashpress.geom.view.triangle.TriangleView;
	
	public class testPointG3 extends Sprite implements ICallback
	{
		// геометрия треугольника
		private var triangle:FPGTriangle2d;
		// отображение треугольника
		private var triangleView:TriangleView;
		// геометория проверяемой точки
		private var targetPoint:FPGPoint2d;
		// отображение проверяемой точки
		private var targetPointView:PointView;
		//
		// проекции трех вершин на противповоложные стороны
		private var pHeight1:PointView;
		private var pHeight2:PointView;
		private var pHeight3:PointView;
		//
		// проекции текущей точки на высоты
		// опущенные из вершин треугольника
		private var crossHeight1:PointView;
		private var crossHeight2:PointView;
		private var crossHeight3:PointView;
		//
		// пукнтирные линии соединяющие текущую точку
		// с проекциями crossHeight(1/2/3)
		private var lineCross1:LineView;
		private var lineCross2:LineView;
		private var lineCross3:LineView;
		// 
		public function testPointG3()
		{
			var glow:GlowFilter = new GlowFilter(0xffffff, 1, 2, 2, 10, 3);
			//
			var size:Number = Math.min(stage.stageWidth, stage.stageHeight)*0.8;
			var p1:FPGPoint2d = new FPGPoint2d(size/2, 0);
			var p2:FPGPoint2d = new FPGPoint2d(size, size*0.8);
			var p3:FPGPoint2d = new FPGPoint2d(0, size*0.8);
			triangle = new FPGTriangle2d(p1, p2, p3);
			triangle.translate((stage.stageWidth-size)*0.5, (stage.stageHeight-size*0.8)*0.35);
			//
			triangleView = new TriangleView(triangle, new FillData(0x666666, 3));
			triangleView.visibleHeights = true;
			triangleView.heightLines.fill = new FillData(0x9900, 2);
			//
			triangleView.point1.filters = [glow];
			triangleView.point2.filters = [glow];
			triangleView.point3.filters = [glow];
			triangleView.point1.fill = color1.fill(2);
			triangleView.point2.fill = color2.fill(2);
			triangleView.point3.fill = color3.fill(2);
			triangleView.point1.name = 'A';
			triangleView.point2.name = 'B';
			triangleView.point3.name = 'C';
			triangleView.point1.scale = 1.5;
			triangleView.point2.scale = 1.5;
			triangleView.point3.scale = 1.5;
			//
			triangleView.heightLines.line1.fill = color1.fill(2);
			triangleView.heightLines.line2.fill = color2.fill(2);
			triangleView.heightLines.line3.fill = color3.fill(2);
			//
			//
			pHeight1 = new PointView(null, color1.fill());
			pHeight1.enabled = false;
			pHeight1.filters = [glow];
			pHeight1.name = 'A"';
			//
			pHeight2 = new PointView(null, color2.fill());
			pHeight2.enabled = false;
			pHeight2.filters = [glow];
			pHeight2.name = 'B"';
			//
			pHeight3 = new PointView(null, color3.fill());
			pHeight3.enabled = false;
			pHeight3.filters = [glow];
			pHeight3.name = 'C"';
			//
			crossHeight1 = new PointView(null, color1.fill());
			crossHeight1.enabled = false;
			crossHeight1.filters = [glow];
			crossHeight1.name = 'Ah';
			//
			crossHeight2 = new PointView(null, color2.fill());
			crossHeight2.enabled = false;
			crossHeight2.filters = [glow];
			crossHeight2.name = 'Bh';
			//
			crossHeight3 = new PointView(null, color3.fill());
			crossHeight3.enabled = false;
			crossHeight3.filters = [glow];
			crossHeight3.name = 'Ch';
			//
			lineCross1 = new LineView(new FPGLine2d(new FPGPoint2d(0, 0), new FPGPoint2d(100, 100)), 0, 0, color1.fill(1, true));
			lineCross2 = new LineView(new FPGLine2d(new FPGPoint2d(0, 0), new FPGPoint2d(100, 100)), 0, 0, color2.fill(1, true));
			lineCross3 = new LineView(new FPGLine2d(new FPGPoint2d(0, 0), new FPGPoint2d(100, 100)), 0, 0, color3.fill(1, true));
			//
			var xp:Number = p3.x + (triangle.p2.x-p3.x)*0.40;
			var yp:Number = p1.y + (triangle.p2.y-p1.y)*0.92;
			targetPoint = new FPGPoint2d(xp, yp);
			targetPointView = new PointView(targetPoint, new FillData(0x0, 1));
			targetPointView.filters = [glow]
			targetPointView.scale = 1.8;
			//
			this.addChild(triangleView);
			this.addChild(pHeight1);
			this.addChild(pHeight2);
			this.addChild(pHeight3);
			this.addChild(crossHeight1);
			this.addChild(crossHeight2);
			this.addChild(crossHeight3);
			this.addChild(lineCross1);
			this.addChild(lineCross2);
			this.addChild(lineCross3);
			this.addChild(targetPointView);
			//
			targetPoint.addCallback(this);
			triangle.addCallback(this);
			callbackEvent(null, null);
		}
		
		/**
		 * Были изменены положения вершин треугольника,
		 * или текущей точки targetPoint
		 */
		public function callbackEvent(target:Object, data:Object):void
		{
			pHeight1.data = triangle.heightA.p2;
			pHeight2.data = triangle.heightB.p2;
			pHeight3.data = triangle.heightC.p2;
			//
			var color:Number = getColor(targetPoint.x, targetPoint.y);
			targetPointView.fill = new FillData(color);
		}
			
		
		private var color1:RGB = new RGB(255, 0, 0);
		private var color2:RGB = new RGB(0, 200, 0);
		private var color3:RGB = new RGB(0, 0, 255);
		private function getColor(_x:Number, _y:Number):uint
		{
			var point:FPGPoint2d = new FPGPoint2d(_x, _y);
			//
			// находим уравнение высоты опущенной из текущей точки(point)
			// на высоту опущенную из вершины A(triangle.heightA)
			var h2mA:FPGLine2d = FPGLineToPoint2dMath.lineHeight(point, triangle.heightA);
			// аналогично находим высоты для других точек B и C
			var h2mB:FPGLine2d = FPGLineToPoint2dMath.lineHeight(point, triangle.heightB);
			var h2mC:FPGLine2d = FPGLineToPoint2dMath.lineHeight(point, triangle.heightC);
			if (!h2mA || !h2mB|| !h2mC) return 0xffffff;
			//
			// h2mA.p2 - это точка пересечения прямой h2mA с
			// высотой опущенной из вершины A
			crossHeight1.data = h2mA.p2;
			crossHeight2.data = h2mB.p2;
			crossHeight3.data = h2mC.p2;
			//
			//
			// находим расстояние от текущей вершины треугольника
			// до точки пересечения прямой h2mA c высотой, опущенной из вершины A
			var h2Alen:Number = FPGPoint2dMath.length(triangle.p1, h2mA.p2);
			// аналогично находим расстояния для других точек
			var h2Blen:Number = FPGPoint2dMath.length(triangle.p2, h2mB.p2);
			var h2Clen:Number = FPGPoint2dMath.length(triangle.p3, h2mC.p2);
			//
			// рисуем пунктриную линию 
			lineCross1.data = h2mA;
			lineCross2.data = h2mB;
			lineCross3.data = h2mC;
			//
			// находим проценты близости
			var p1:Number = 1-h2Alen/(triangle.heightA.length);
			var p2:Number = 1-h2Blen/(triangle.heightB.length);
			var p3:Number = 1-h2Clen/(triangle.heightC.length);
			//
			// задаем нижнюю границу
			if (p1 < 0) p1 = 0;
			if (p2 < 0) p2 = 0;
			if (p3 < 0) p3 = 0;
			//
			//
			// вычисляем значения цветовых каналов red, green и blue
			var r:Number = color1.r*p1 + color2.r*p2 + color3.r*p3;
			var g:Number = color1.g*p1 + color2.g*p2 + color3.g*p3;
			var b:Number = color1.b*p1 + color2.b*p2 + color3.b*p3;
			// задаем верхнюю границу
			if (r > 255) r = 255;
			if (g > 255) g = 255;
			if (b > 255) b = 255;
			// вычисляем значение цвета
			var color:int = (r << 16) + (g << 8) + b;
			return color;
		}
	}
}
import ru.flashpress.geom.view.core.FillData;



class RGB
{
	public var r:uint;
	public var g:uint;
	public var b:uint;
	public var code:uint;
	public function RGB(r:uint, g:uint, b:uint)
	{
		this.r = r;
		this.g = g;
		this.b = b;
		this.code = (r << 16) + (g << 8) + b;
	}
	
	public function fill(stroke:uint=1, dotline:Boolean=false):FillData
	{
		return new FillData(this.code, stroke, dotline);
	}
}

Для геометрических вычислений используется библиотека FPGeometry2d.swc.

Пример ActionScript3.0 с использованием шейдера

Результат закраски треугольника с использованием шейдера во флешке.

Главный класс флешки testShaderG3.as

package
{
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	import ru.flashpress.filters.G3Shader;

	public class testShaderG3 extends Sprite
	{
		// шейдер, рисующий треугольный градиент
		private var shader:G3Shader;
		public function testShaderG3()
		{
			start();
		}
		
		private var p1:Sprite;
		private var p2:Sprite;
		private var p3:Sprite;
		private function start():void
		{
			shader = new G3Shader();
			//
			// создаем три вершины треугольника
			p1 = createPoint(200, 50);
			p2 = createPoint(350, 350);
			p3 = createPoint(50, 350);
			//
			shader.initPoints(	new Point(p1.x, p1.y),
								new Point(p2.x, p2.y),
								new Point(p3.x, p3.y));
			//
			redraw();
		}
		private function createPoint(_x:Number, _y:Number):Sprite
		{
			var point:Sprite = new Sprite();
			point.graphics.beginFill(0x0, 0.5);
			point.graphics.drawCircle(0, 0, 10);
			this.addChild(point);
			point.x = _x;
			point.y = _y;
			point.buttonMode = true;
			point.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
			return point;
		}
		private var currentPoint:Sprite;
		private function downHandler(event:MouseEvent):void
		{
			currentPoint = event.target as Sprite;
			currentPoint.startDrag();
			this.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
			this.stage.addEventListener(MouseEvent.MOUSE_UP, upHandler);
		}
		private function moveHandler(event:MouseEvent):void
		{
			event.updateAfterEvent();
			switch (currentPoint) {
				//  задаем шейдеру значения вершин треугольника
				case p1:
					shader.point1 = new Point(p1.x, p1.y);
					break;
				case p2:
					shader.point2 = new Point(p2.x, p2.y);
					break;
				case p3:
					shader.point3 = new Point(p3.x, p3.y);
					break;
			}
			redraw();
		}
		private function upHandler(event:MouseEvent):void
		{
			currentPoint.stopDrag();
			this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
			this.stage.removeEventListener(MouseEvent.MOUSE_UP, upHandler);
		}
		
		private function redraw():void
		{
			this.graphics.clear();
			this.graphics.beginFill(0xff0000, 1);
			this.graphics.beginShaderFill(shader);
			this.graphics.moveTo(p1.x, p1.y);
			this.graphics.lineTo(p2.x, p2.y);
			this.graphics.lineTo(p3.x, p3.y);
		}
	}
}

Класс G3Shader.as

package ru.flashpress.filters
{
	import flash.display.Shader;
	import flash.geom.Point;
	
	/**
	 * Шейдер, рисующий треугольный градиент, по заданным точкам и цветам к ним
	 * @author Serious Sam
	 */
	public class G3Shader extends Shader
	{
		private static var dir:Point = new Point();
		private static var point2:Point = new Point();
		private static var temp:Number;
		private static var k1:Number;
		private static var k2:Number;
		private static function getHeight(o:Point, p1:Point, p2:Point):void
		{
			dir.x = p2.x-p1.x;
			dir.y = p2.y-p1.y;
			point2.x = 0;
			point2.y = 0;
			if (dir.y != 0) {
				temp = dir.x*o.x + dir.y*o.y;
				point2.x = o.x != 1 ? 1 : 2;
				point2.y = (-dir.x * point2.x + temp) / dir.y;
			} else {
				point2.x = o.x;
				point2.y = p1.y;
			}
			//
			k1 = (point2.y-o.y)*(p2.x-p1.x) - (point2.x-o.x)*(p2.y-p1.y);
			k2 = (point2.x-o.x)*(p1.y-o.y) - (point2.y-o.y)*(p1.x-o.x);
			pointH.x = p1.x + k2*(p2.x-p1.x)/k1;
			pointH.y = p1.y + k2*(p2.y-p1.y)/k1;
		}
		//
		//
		//
		[Embed(source="g3.pbj", mimeType="application/octet-stream")]
		private var BytecodesClass:Class;
		//
		/**
		 * @private
		 */
		public function G3Shader()
		{
			super(new BytecodesClass());
		}
		
		private static var pointH:Point = new Point();
		/**
		 * В этом методе необходимо сообщить шейдеру значения координат вершин (a, b и c)
		 * а так же координаты проекций этих вершин на противоположные стороны (ah, bh, ch)
		 */		
		private function reinit():void
		{
			// вершина A треугольника
			this.data.a.value = [_point1.x, _point1.y];
			getHeight(_point1, _point2, _point3);
			// проекция вершины A на сторону BC
			this.data.ah.value = [pointH.x, pointH.y];
			//
			this.data.b.value = [_point2.x, _point2.y];
			getHeight(_point2, _point3, _point1);
			this.data.bh.value = [pointH.x, pointH.y];
			//
			this.data.c.value = [_point3.x, _point3.y];
			getHeight(_point3, _point1, _point2);
			this.data.ch.value = [pointH.x, pointH.y];
		}
		
		/**
		 * Инициализировать координаты вершин трегольника
		 */
		public function initPoints(p1:Point, p2:Point, p3:Point):void
		{
			this._point1.x = p1.x;
			this._point1.y = p1.y;
			//
			this._point2.x = p2.x;
			this._point2.y = p2.y;
			//
			this._point3.x = p3.x;
			this._point3.y = p3.y;
			//
			reinit();
		}
		
		private var _point1:Point = new Point(100, 0);
		/**
		 * Координаты первой вершины треугольника
		 */
		public function get point1():Point {return this._point1.clone();}
		public function set point1(value:Point):void
		{
			this._point1.x = value.x;
			this._point1.y = value.y;
			//
			this.reinit();
		}
		
		private var _point2:Point = new Point(200, 200);
		/**
		 * Координаты второй вершины треугольника
		 */
		public function get point2():Point {return this._point2.clone();}
		public function set point2(value:Point):void
		{
			this._point2.x = value.x;
			this._point2.y = value.y;
			//
			this.reinit();
		}
		
		private var _point3:Point = new Point(0, 200);
		/**
		 * Координаты третьей вершины треугольника
		 */
		public function get point3():Point {return this._point3.clone();}
		public function set point3(value:Point):void
		{
			this._point3.x = value.x;
			this._point3.y = value.y;
			//
			this.reinit();
		}
		
		/**
		 * Инициализировать цвета вершин треугольника
		 */
		public function initColors(color1:uint, color2:uint, color3:uint):void
		{
			this.color1 = color1;
			this.color2 = color2;
			this.color3 = color3;
		}
		
		private var _color1:Number = 0;
		/**
		 * Цвет первой вершины треугольника
		 */
		public function get color1():uint {return this._color1;}
		public function set color1(value:uint):void
		{
			this._color1 = value;
			this.data.color1.value = [	((value >>> 16) & 0xff)/255,
										((value >>>  8) & 0xff)/255,
										(value & 0xff)/255];
		}
		
		private var _color2:Number = 0;
		/**
		 * Цвет второй вершины треугольника
		 */
		public function get color2():uint {return this._color2;}
		public function set color2(value:uint):void
		{
			this._color2 = value;
			this.data.color2.value = [	((value >>> 16) & 0xff)/255,
										((value >>>  8) & 0xff)/255,
										(value & 0xff)/255];
		}
		
		private var _color3:Number = 0;
		/**
		 * Цвет третьей вершины треугольника
		 */
		public function get color3():uint {return this._color3;}
		public function set color3(value:uint):void
		{
			this._color3 = value;
			this.data.color3.value = [	((value >>> 16) & 0xff)/255,
										((value >>>  8) & 0xff)/255,
										(value & 0xff)/255];
		}
	}
}

Шейдер g3.pbk

<languageVersion: 1.0;>
 
kernel TriangleGradient
<   namespace : "flashpress.ru";
    vendor : "FlashPress.ru";
    version : 1;
    description : "Triangle Gradient"; >
{
    
    parameter float2 a
    <
        defaultValue:float2(50.0, 50.0);
    >;
    parameter float2 ah
    <
        defaultValue:float2(50.0, 200.0);
    >;
    parameter float2 b
    <
        defaultValue:float2(50.0, 200.0);
    >;
    parameter float2 bh
    <
        defaultValue:float2(125.0, 125.0);
    >;
    parameter float2 c
    <
        defaultValue:float2(200.0, 200.0);
    >;
    parameter float2 ch
    <
        defaultValue:float2(50.0, 200.0);
    >;
    parameter float3 color1
    <
        minValue:float3(0.0, 0.0, 0.0);
        maxValue:float3(1.0, 1.0, 1.0);
        defaultValue:float3(1.0, 0.0, 0.0);
    >;
    parameter float3 color2
    <
        minValue:float3(0.0, 0.0, 0.0);
        maxValue:float3(1.0, 1.0, 1.0);
        defaultValue:float3(0.0, 1.0, 0.0);
    >;
    parameter float3 color3
    <
        minValue:float3(0.0, 0.0, 0.0);
        maxValue:float3(1.0, 1.0, 1.0);
        defaultValue:float3(0.0, 0.0, 1.0);
    >;

    input image4 src;
    output float4 dst;

    void
    evaluatePixel()
{
    //
    //
    //
    //
    float2 direction;
    float lineA;
    float lineB;
    float lineC;
    float checkd;
    float checka;
    float2 point;
    float2 o = outCoord();
    // 
    // уравнение перпендикуляра опущенного из точки O на прямую a-ah
    direction = ah-a;
    lineA = -direction.y;
    lineB = -direction.x;
    lineC = direction.x*o.x + direction.y*o.y;
    if (lineA != 0.0) {
        point = float2(o.x + 1.0, 0);
        point.y = -(lineB*point.x+lineC)/lineA;
    } else {
        point = float2(o.x, a.y);
    }
    // пересечение p1-point и b-c
    checkd = (point.y-o.y)*(ah.x-a.x) - (point.x-o.x)*(ah.y-a.y);
    checka = (point.x-o.x)*(a.y-o.y) - (point.y-o.y)*(a.x-o.x);
    // точка пересечения перпендикуляра опущенного из A и прямой BC
    float2 aho = float2(0.0, 0.0);
    aho.x = a.x + checka*(ah.x-a.x)/checkd;
    aho.y = a.y + checka*(ah.y-a.y)/checkd;
    //
    //
    //
    // уравнение перпендикуляра опущенного из точки O на прямую b-bh
    direction = bh-b;
    lineA = -direction.y;
    lineB = -direction.x;
    lineC = direction.x*o.x + direction.y*o.y;
    if (lineA != 0.0) {
        point = float2(o.x+1.0, 0.0);
        point.y = -(lineB*point.x+lineC)/lineA;
    } else {
        point = float2(o.x, b.y);
    }
    // пересечение p1-point и b-c
    checkd = (point.y-o.y)*(bh.x-b.x) - (point.x-o.x)*(bh.y-b.y);
    checka = (point.x-o.x)*(b.y-o.y) - (point.y-o.y)*(b.x-o.x);
    // точка пересечения перпендикуляра опущенного из A и прямой BC
    float2 bho = float2(0.0, 0.0);
    bho.x = b.x + checka*(bh.x-b.x)/checkd;
    bho.y = b.y + checka*(bh.y-b.y)/checkd;
    //
    //
    //
    // уравнение перпендикуляра опущенного из точки O на прямую c-ch
    direction = ch-c;
    lineA = -direction.y;
    lineB = -direction.x;
    lineC = direction.x*o.x + direction.y*o.y;
    if (lineA != 0.0) {
        point = float2(o.x+1.0, 0.0);
        point.y = -(lineB*point.x+lineC)/lineA;
    } else {
        point = float2(o.x, c.y);
    }
    // пересечение p1-point и b-c
    checkd = (point.y-o.y)*(ch.x-c.x) - (point.x-o.x)*(ch.y-c.y);
    checka = (point.x-o.x)*(c.y-o.y) - (point.y-o.y)*(c.x-o.x);
    // точка пересечения перпендикуляра опущенного из A и прямой BC
    float2 cho = float2(0.0, 0.0);
    cho.x = c.x + checka*(ch.x-c.x)/checkd;
    cho.y = c.y + checka*(ch.y-c.y)/checkd;
    //
    //
    //
    float pp1 = 1.0-((a.x-aho.x)*(a.x-aho.x)+(a.y-aho.y)*(a.y-aho.y))/((a.x-ah.x)*(a.x-ah.x)+(a.y-ah.y)*(a.y-ah.y));
    float pp2 = 1.0-((b.x-bho.x)*(b.x-bho.x)+(b.y-bho.y)*(b.y-bho.y))/((b.x-bh.x)*(b.x-bh.x)+(b.y-bh.y)*(b.y-bh.y));
    float pp3 = 1.0-((c.x-cho.x)*(c.x-cho.x)+(c.y-cho.y)*(c.y-cho.y))/((c.x-ch.x)*(c.x-ch.x)+(c.y-ch.y)*(c.y-ch.y));
    //
    dst = float4(0.0, 0.0, 0.0, 1.0);
    dst.r = color1.r*pp1 + color2.r*pp2 + color3.r*pp3;
    dst.g = color1.g*pp1 + color2.g*pp2 + color3.g*pp3;
    dst.b = color1.b*pp1 + color2.b*pp2 + color3.b*pp3;
}
}

Автор: FlashPress

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js