Перейдем к рассмотрению библиотеки WebGeometry используемой для рачета координат вершин 3D моделей.
Oписание всех входящих в нее функций можно найти здесь.
Библиотека включает в себя Javascript функциии для работы с 2D и 3D объектами.
В основном для построения моделей используются трехмерные объекты библиотеки - 3D-точки, 3D-вектора,
прямые в пространстве и плоскости. Но иногда требуются и чисто двумерные объекты - 2D-точки, 2D-вектора,
прямые на плоскости и окружности.
В библиотеке 2D и 3D объекты четко разделены. Отдельно присутствут функции для построений
на плоскости и отдельно для построений в пространстве. Кроме того в библиотеке большую роль играют
функции для работы с матрицами. Для 2D и 3D матриц также присутствуют функции
для обеих размерностей по отдельности. Но функции с матрицами нам практически никогда
не понадобятся, хотя они играют очень важную роль в библиотеке WebGeometry. На их основе
построены функции работающие с точками, прямыми и плоскостями, которые нам требуются в дальнейшем
для построения многогранников.
Начнем с рассмотрения функций библиотеки предназначенных
для работы с 2D объектами. В данном разделе мы рассмотрим на примерах как можно использовать
функции библиотеки WebGeometry применительно к построениям на плоскости.
В показанной ниже программе показано как можно расположить 2D-точку на холсте. При помощи контроллера dat.GUI точку можно перемещать по холсту.
Основная часть исходного текста программы с подробными комментариями объясняющими ее работу:
var elem1 = document.getElementById('canvas_01'); var ctx1 = elem1.getContext("2d"); elem1.style.position = "relative"; elem1.style.border = "1px solid"; /* // Функции сопряжения координат холста с координатами используемыми в WebGeometry, WebGL и three.js // Используются внутри функций csp, axes, txt_c (и других ...) для пересчета координат используемых // в WebGeometry, WebGL и three.js в координаты используемые при построениях на холсте (HTML5 canvas). function fx(val) { var res = val * SCALE + xC; return res; } function fy(val) { var res = - val * SCALE + yC; return res; } */ SCALE = 100; // задает масштаб xC = elem2.width / 2; // задает положение начала координат по оси OX yC = elem2.height / 2; // задает положение начала координат по оси OY // начало координат var pt00 = new Point2D(0, 0); // рисуем точку в начале координат коричневым цветом csp(ctx1, pt00, 2, "Brown"); // обозначаем начало координат символом "O" коричневым цветом text1(ctx1, "O", pt00, "rt", "up", "Brown"); // рисуем оси коричневым цветом с размахом линий по OX и OY равным 1.8 axes(ctx1, 1.8, 1.8, 0.5, "Brown"); // canvas ctx1 // точка с координатами ( 1.2, -1.2) var pt0 = new Point2D( 1.2, -1.2); csp(ctx1, pt0, 10, "B"); // рисуем точку pt0 синим цветом // формируем строку с координатами точки pt0 var t1 = (roundNumber(pt0[0], 2)).toString(); // преобразование значения в строку var t2 = (roundNumber(pt0[1], 2)).toString(); // преобразование значения в строку var t = t1 + ", " + t2; // отображаем сформированную строку синим цветом // рядом с pt0 - чуть правее и чуть выше самой точки text1(ctx1, t, pt0, "rt", "up", "B"); // переменная controller1 используется dat.GUI var controller1 = new function() { this.X = pt0[0]; this.Y = pt0[1]; }(); // Создаем новый объект dat.GUI. var gui = new dat.GUI({ autoPlace: false }); gui.domElement.id = 'gui'; gui_container1.appendChild(gui.domElement); var f1_1 = gui.addFolder('Point position'); // используется для изменения координаты X точки pt0 f1_1.add(controller1, 'X', -2.0, 2.0).onChange( function() { pt0[0] = (controller1.X); // задаем новое значение координаты X // так как новое значение координаты X то требуется // очистить экран ctx1.clearRect(0, 0, elem1.width, elem1.height); // перерисовываем экран с новым положением точки pt0 axes(ctx1, 1.8, 1.8, 0.5, "Brown"); csp(ctx1, pt0, 10, "B"); var t1 = (roundNumber(pt0[0], 2)).toString(); var t2 = (roundNumber(pt0[1], 2)).toString(); var t = t1 + ", " + t2; text1(ctx1, t, pt0, "rt", "up", "B"); csp(ctx1, pt00, 2, "Brown"); text1(ctx1, "O", pt00, "rt", "up", "Brown"); }); // используется для изменения координаты Y точки pt0 f1_1.add(controller1, 'Y', -2.0, 2.0).onChange( function() { pt0[1] = (controller1.Y); // задаем новое значение координаты Y // так как новое значение координаты Y то требуется // очистить экран ctx1.clearRect(0, 0, elem1.width, elem1.height) // перерисовываем экран с новым положением точки pt0 axes(ctx1, 1.8, 1.8, 0.5, "Brown"); csp(ctx1, pt0, 10, "B"); var t1 = (roundNumber(pt0[0], 2)).toString(); var t2 = (roundNumber(pt0[1], 2)).toString(); var t = t1 + ", " + t2; text1(ctx1, t, pt0, "rt", "up", "B"); csp(ctx1, pt00, 2, "Brown"); text1(ctx1, "O", pt00, "rt", "up", "Brown"); });
Кроме библиотеки WebGeometry в дальнейшем при выводе на холст мы будем использовать некоторое количество вспомогательных функций, исходный текст которых находится в файле canvas2D.js. Они представляют собой небольшую обертку вокруг стандартного API HTML5 Canvas и позволяют вывести на экран точки, текст, прямые и окружности при помощи сокращенной записи. Эти функции в качестве параметров получают координаты не привязанные к холсту (как в HTML5 Canvas), а непосредственно значения получаемые в процессе расчетов с помощью функций входящих в WebGeomrtry. Также большинство этих функций позволяют задать цвет точек, текста, прямых и окружностей, размер точек и толщину линий.
В приведенном выше исходном тексте использовалась только одна функция создания 2D-точки из библиотеки WebGeometry.
Она создает точку с координатами (1.2, -1.2)
:
var pt0 = new Point2D(1.2, -1.2);
Создадим две прямые и отобразим их на холсте. Первая прямая задается двумя точками (точки обозначенные на холсте как 1 и 2):
var pt1 = new Point2D ( -1.1, -0.8); // черная точка 1 var pt2 = new Point2D ( 1.1, 0.8); // черная точка 2 var line1 = new Line2D(pt1, pt2); // создаваемая прямаяВторая прямая задается своим направлением при помощи вектора, и точкой принадлежащей создаваемой прямой. При помощи функции
IntersectionTwoLines
определяем точку пересечения двух прямых:
var vec = new Vector2D(0.95, -0.2); // вектор синего цвета var pt3 = new Point2D ( -1.0, 0.7); // точка point синего цвета var line2 = new Line2D(); // объявление прямой line2 // назначение прямой line2 требуемых параметров line2.CreateLineVectorPoint(vec, pt3); // определяем координаты точки пересечения прямых line1 и line2 var point = line1.IntersectionTwoLines(line2); // точка имеющая красный цвет
При помощи dat.GUI
можно задавать координаты точек 1
и 2
через которые проходит первая прямая и кординаты вектора и точки для второй прямой.
Возможно представляет интерес посмотреть как это все рисуется на холсте. Далее приведен текст функции cross выполняющий вычисления и отрисовку на холсте с подробными комментариями:
function cross(pt1, pt2, pt3, vec) { // pt1 и pt2 задают line1 // pt3 и vec задают line2 ctx2.clearRect(0, 0, elem2.width, elem2.height); ctx2.lineWidth = 1.0; ctx2.font = "12px Arial"; axes(ctx2, 1.8); // Создаем вспомогательную прямую лежащую на оси OX var pt00 = new Point2D(0, 0); // начало координат var OX = new Line2D(pt00, new Point2D(1, 0)); csp(ctx2, pt1, 6); // черная точка 1 csp(ctx2, pt2, 6); // черная точка 2 var line1 = new Line2D(pt1, pt2); // создаем line1 line(ctx2, pt2, pt1); // отображаем line1 на холсте // Для отрисовки вектора на экране // создаем сначала прямую лежащую на векторе // Переменная vec объявлена вне данной функции и изменяется в dat.GUI vec.Normer(); var line_vec = new Line2D(pt00, vec); // Отображаем вектор на холсте. // Предположим, что он исходит из начала координат и идет в точку с координатами vec. // В WebGeometry предполагается, что векторы являются свободными и задают только направление. // Поэтому можем условно привязать вектор к любой точке - в том числе и к началу координат. vector(ctx2, pt00, vec, "B"); txt_c(ctx2, "vector", vec, "rt", "up", "B"); // отображаем слово "vector" // При помощи функции Angle библиотеки WebGeometry определяем угол наклона стрелки на холсте var ang = (line_vec.Angle(OX) + 90*DEGREE); // Затем рисуем стрелку на конце вектора arrow(ctx2, vec, ang, 0.4, "B"); csp(ctx2, pt3, 6, "B"); // отображаем на холсте точку pt3 синим цветом txt_c(ctx2, "point", pt3, "lt", "up", "B"); // отображаем на холсте слово point синим цветом // Создаем прямую line2 var line2 = new Line2D(); // объявление прямой line2 // назначение прямой line2 требуемых параметров line2.CreateLineVectorPoint(vec, pt3); // находим вспомогательную точку pt_temp для изображения на холсте line2 var pt_temp = line2.IntersectionTwoLines(OX); // прямая line2 проходит через точки pt3 и pt_temp // отображаем line2 при помощи функции line (смотри в canvas2D.js) line(ctx2, pt3, pt_temp, "B"); txt_c(ctx2, "1", pt1, "lt", "up"); // отображаем слово "1" на холсте txt_c(ctx2, "2", pt2, "lt", "up"); // отображаем слово "2" на холсте var point = line1.IntersectionTwoLines(line2); csp(ctx2, point, 8, "R"); var t1 = (roundNumber(point[0], 2)).toString(); var t2 = (roundNumber(point[1], 2)).toString(); var t = t1 + ", " + t2; txt_c(ctx2, t, point, "rt", "up", "R"); // отображаем координаты точки пересечения // прямых на холсте красным цветом csp(ctx2, pt00, 8, "B"); // отображаем точку лежащую в начале координат (8 - размер точки в px) }
В приведенном выше листинге использовались следующие функции библиотеки WebGeometry:
Vector2D
(Vector2D) Normer
Line2D
(Line2D) CreateLineVectorPoint
(Line2D) Angle
(Line2D) IntersectionTwoLines
В качестве типичного примера создадим окружность и прямую и затем найдем точки их пересечения. На холст окружность и прямую отображает следующая программа:
По тексту программы можно посмотреть как используются некоторые функции библиотеки WebGeometry
для отображения на холст прямых, окружностей и т.д. В следующем листинге
в качестве функции определяющей точки пересечения окружности и прямой используется
функция
Intersection_LineCircle
.
Эта функция возвращает массив из двух элементов типа Point2D
. Эти два элемента массива представляют собой
две точки пересечения прямой с плоскостью.
function draw_line_circle(ctx, O, pt1, pt2, R) { axes(ctx, 1.8, 1.8, 0.5, "Brown"); // оси координат csp(ctx, O, 5, "B"); // центр окружности var t1 = (roundNumber(O[0], 2)).toString(); var t2 = (roundNumber(O[1], 2)).toString(); var t = t1 + ", " + t2; text1(ctx, t, O, "rt", "up", "B"); circle(ctx, O, R, 1, "B"); // радиус - прямая со стрелкой на одном конце var line_radius = new Line2D(O, new Point2D(O[0] + 6, O[1] + 3)); var cir = new Circle2D(O, R); // Окружность с центром O и радиусом R // определяем место где нарисуем стрелку радиуса var points = cir.Intersection_LineCircle(line_radius); if (points == null) { return null; } var x, y; // в качестве места для стрелки выбираем где координата Y // точки пересесечения имеет меньшее значение if (points[0][1] > points[1][1]) { x = points[1][0]; y = points[1][1]; } else { x = points[0][0]; y = points[0][1]; } // точка для стрелки var point_radius = new Point2D(x, y); // радиус segment_arrow(ctx, O, point_radius, 1, 0.2, "Black") text1(ctx, "R", point_radius, "lt", "dn", "B"); // прямая OX задается для определения угла используемого // для проведения отрезка со стрелкой на конце примыкающем к окружности var O = new Point2D(0, 0); // начало координат var OX = new Line2D(O, new Point2D(1, 0)); // определяем угол var ang = (line_radius.Angle(OX) - 90*DEGREE); // рисуем стрелку на конце отрезка arrow(ctx, point_radius, ang, 0.2, "B"); // задаем прямую line_var пересекающуюся с окружностью csp(ctx, pt1, 6); csp(ctx, pt2, 6); text1(ctx, "1", pt1, "rt", "up"); text1(ctx, "2", pt2, "rt", "up"); var line_var = new Line2D(pt1, pt2); // прямая задается двумя точками line(ctx, pt2, pt1, -3, 3, 1, "Black"); // отображаем line_var // находим две точки пересечения points = cir.Intersection_LineCircle(line_var); if (points == null) { return null; } csp(ctx, points[0], 6, "R"); // первая точка пересечения csp(ctx, points[1], 6, "R"); // вторая точка пересечения // координаты первой точки var t1 = (roundNumber(points[0][0], 2)).toString(); var t2 = (roundNumber(points[0][1], 2)).toString(); var t = t1 + ", " + t2; text1(ctx, t, points[0], "rt", "up", "R"); // координаты второй точки t1 = (roundNumber(points[1][0], 2)).toString(); t2 = (roundNumber(points[1][1], 2)).toString(); t = t1 + ", " + t2; text1(ctx, t, points[1], "rt", "up", "R"); }
Создадим две окружности у которых можно менять координаты их центров и радиусы.
Найдем точки пересечения окружностей друг с другом. Они определяются с помощью
функции
Intersection_TwoCircles
var cir1 = new Circle2D(O1, R1); // первая окружность var cir2 = new Circle2D(O2, R2); // вторая окружность var points = cir1.Intersection_TwoCircles(cir2); if (points == null) { return null; // нет пересечения окружностей } // пересечение есть ................... ...................
Иногда возникает задача плавного соединения двух окружностей.
Рисунок 1 иллюстрирует это построение. Фактически оно сводится
к сопряжению окружностей cir1
и cir2
дугой третьей окружности cir3
.
Окружность cir3
имеет внутреннее касание к окружностям cir1
и cir2
в точках F и G. Термин “внутреннее касание” означает, что центры дуг окружностей
cir1
, cir2
и cir3
находятся по одну сторону от точек их касания.
Будем считать, что заданы радиусы R1
и R2
основных окружностей
cir1
и cir2
, а также радиус сопрягающей окружности R3
.
Предположим также, что известно расположение центров O1 и O2 основных окружностей.
Требуется найти положение центра O3 сопрягающей окружности и координаты точек касания F и G.
Для нахождения центра O3 окружности сопряжения cir3
построим две вспомогательные окружности с радиусами
равными значениям R1 – R3
и R2 – R3
. Центры этих вспомогательных окружностей поместим в центры основных
окружностей O1 и O2 соответственно. Тогда центр O3 окружности cir3
можно найти как точку
пересечения вспомогательных окружностей. Для нахождения точек пересечения окружности
cir3
с окружностями cir1
и cir2
создается окружность с центром O3 и радиусом,
отличающимся в большую сторону от R3 на очень малую величину epsilon. Следовательно радиус этой окружности равен
(R3 + epsilon). Введение этой малой величины необходимо для того, чтобы в процессе проведения вычислений заведомо
обеспечить пересечение соответствующих окружностей. В предельном случае, когда значение epsilon стремится к 0,
две точки пересечения сливаются и превращаются в одну точку касания окружностей.
Пересечение окружности cir3
с радиусом равным (R3 + epsilon) с окружностями
cir1
и cir2
даст точки касания F и G.
Точки пересечения окружностей определяются с помощью
функции
Intersection_TwoCircles
Функция отрисовки сопряжения двух окружностей третьей окружностью приведена ниже.
function draw_three_circles(ctx, O1, O2, O3, R1, R2, R3) { axes(ctx, 1.8, 1.8, 0.5, "Black"); // рисуем оси черным цветом // Необходимо провести сопряжение следующих двух окружностей. var cir1 = new Circle2D(O1, R1); var cir2 = new Circle2D(O2, R2); // Рисуем и обозначаем центр O1 коричневым цветом csp(ctx, O1, 5, "Brown"); text1(ctx, "O1", O1, "rt", "up", "Brown"); // Рисуем дугу с центром O1 и радиусом R1 коричневым цветом arc(ctx, O1, R1, 1, "Brown", 5, 120); // Рисуем и обозначаем центр O2 синим цветом csp(ctx, O2, 5, "B"); text1(ctx, "O2", O2, "rt", "up", "B"); // Рисуем дугу с центром O2 и радиусом R2 синим цветом arc(ctx, O2, R2, 1, "B", -60, 60); // Радиусы вспомогательных окружностей должны быть > 0 if (R2 - R3 <= 0.0) { return null; } if (R1 - R3 <= 0.0) { return null; } // Создаем две вспомогательные окружности. var R2_R3 = new Circle2D(O2, R2 - R3); var R1_R3 = new Circle2D(O1, R1 - R3); // Две дуги вспомогательных окружностей рисуем черным цветом и толщиной равной 0.3 arc(ctx, O1, R1 - R3, 0.3, "Black", 5, 120); // рисуем дугу с центром O1 и радиусом R1 - R3 arc(ctx, O2, R2 - R3, 0.3, "Black", -60, 60); // рисуем дугу с центром O2 и радиусом R2 - R3 // Находим точки пересечения двух окружностей между собой // Переменная points представляет собой массив из двух точек типа Point2D. var points = R2_R3.Intersection_TwoCircles(R1_R3); if (points == null) { return null; } // Центр сопрягающей окружности O3 if (points[0][0] > points[1][0]) { O3[0] = points[0][0]; O3[1] = points[0][1] } else { O3[0] = points[1][0]; O3[1] = points[1][1] } // Создаем сопрягающую окружность чуть большего // радиуса чем R3 (для проверки) var cir3 = new Circle2D(O3, R3 + 0.00001); // R3 + EPSILON); csp(ctx, O3, 5, "R"); // рисуем точку красным цветом text1(ctx, "O3", O3, "rt", "up", "R"); // обозначаем точку как "O3" красным цветом circle(ctx, O3, R3, 1, "R"); // рисуем окружность красным цветом // с центром в точке O3 и радиусом равным R3 // Проверяем пересекаются или нет окружность // cir2 с сопрягающей окружностью cir3 // Координаты двух точек пересечения полученные в результате // работы функции Intersection_TwoCircles должны // отличаться совершенно незначительно по своим значениям // и в пределе переходить в одну точку касания окружностей "G". points = cir2.Intersection_TwoCircles(cir3); if (points == null) { return null; } // Первая точка "G" сопряжения/касания var G = new Point2D(); // объявление точки G[0] = points[0][0]; G[1] = points[0][1]; csp(ctx, G, 3, "R"); // рисуем точку красным цветом text1(ctx, "G", G, "lt", "dn", "R"); // обозначаем точку как "G" // Проверяем пересекаются или нет окружность // cir1 с сопрягающей окружностью cir3 // Координаты двух точек пересечения полученные в результате // работы функции Intersection_TwoCircles должны // отличаться совершенно незначительно по своим значениям // и в пределе переходить в одну точку касания окружностей "F". points = cir1.Intersection_TwoCircles(cir3); if (points == null) { return null; } // Вторая точка "F" сопряжения/касания var F = new Point2D(); // объявление точки F[0] = points[0][0]; F[1] = points[0][1]; csp(ctx, F, 3, "R"); // рисуем точку красным цветом text1(ctx, "F", F, "lt", "dn", "R"); // обозначаем точку как "F" // Предварительные расчеты требующиеся для рисования дуги сопряжения. // Вычисляем углы начала и конца дуги сопряжения. var line_O3_F = new Line2D(O3, F); var line_O3_G = new Line2D(O3, G); var line_hor = new Line2D(O3, new Point2D(O3[0] + 1, O3[1])); var ang_F_degree = (180 / Math.PI) * line_O3_F.Angle(line_hor); var ang_G_degree = (180 / Math.PI) * line_O3_G.Angle(line_hor); // Рисуем "жирную" дугу сопряжения окружностей красным цветом arc(ctx, O3, R3, 4, "R", -ang_G_degree, -ang_F_degree); }
Полный текст программы сопряжения окружностей с отрисовкой на холсте находится в файле three_circles.js.
Рассмотрим построение правильных многоугольников на холсте. Простейший способ таких построений состоит в делении окружности на соответствующее количество частей при помощи транспортира и соединении между собой получившихся точек.
Однако эти построения можно провести только при помощи циркуля и линейки. В книге Джона Аллена "Базовые геометрические формы для дизайнеров и архитекторов" (издательство Питер 2016г.) подробно рассматриваются такие построения. Воспользуемся этой книгой и построим пятиугольник (pentagon), шестиугольник (hexagon) и семиугольник (heptagon). Только в качестве циркуля (как это делается в книге) будем использовать функцию создания окружности, а в качестве линейки (она используется в книге) - функцию создания прямой из библиотеки WebGeometry. Начнем с пятиугольника.
Начертим окружность с центром в точке O. Проведем горизонтальный диаметр окружности и получим точки
пересечения A и B. В книге эти точки просто отмечаются карандашом, а мы используем для этого
функцию Intersection_LineCircle
из библиотеки WebGeometry. Раствором циркуля равным радиусу исходной окружности из точки B
проведем дугу (вместо дуг при вычислениях используем функции Circle2D
)
пересекающую окружность в точках C и D. Соединим точки C и D
чтобы найти точку E, делящую отрезок OB на две равные части.
Затем проводим вертикальную ось через точку O и пересекающую окружность в точке F.
Установив ножку циркуля в точке E, раствором циркуля, равным EF, проведем дугу, пересекающую
горизонтальную ось в точке G. Установив ножку циркуля в точке F, раствором циркуля, равным FG,
проведем дугу пересекающую окружность в точках H и J. Из точек H и J проведем дуги, используя
тот же раствор циркуля, чтобы найти точки K и L. FJKLH - искомый пятиугольник.
Точки пересечения дуг окружностей определяются с помощью функции
Intersection_TwoCircles
Исходный текст программы построения пятиугольника приведен в файле pentagon.js.
Перейдем к построению правильного шестиугольника.
Начертим произвольную окружность с центром в точке O и радиусом OA равным значению R.
В качестве точки A может быть выбрана произвольная точка лежащая на окружности.
Установив ножку циркуля в точке A и используя тот же самый радиус проведем через центр O
дугу BC (от одной стороны окружности до другой). Из точки B проведем дугу через точки A,
O и новую точку D на окружности. Таким же образом из точки D проведем еще одну
дугу от точки B, чтобы найти точку E.
Далее продолжаем рисовать дуги из точек E, F и C,
пока не получится шестиугольник ABDEFC.
Исходный текст программы построения шестиугольника приведен в файле hexagon.js. При расчетах его
вершин координаты точек пересечения дуг между собой мы опредеделяем при помощи
функции Intersection_TwoCircles
входящей в состав библиотеки WebGeometry, а рисуем дуги на холсте
используя функцию arc
из файла canvas2D.js. Эта функция сделана на основе одноименной
функции arc
входящей в HTML5 Canvas.
Построим почти правильный семиугольник. Как пишет (в упомянутой выше книге) Джон Аллен это построение точно на 99.9%.
Начертим произвольный круг с центром в точке O и радиусом OC равным значению R.
Построим квадрат EFGH вокруг этого круга. Из точек E и F проведем две дуги, имеющие радиус,
равный отрезку EF, и пересекающиеся в точке J внутри квадрата. EFJ - равносторонний треугольник,
пересекающий изначальную окружность в точках K и L. CK и CL можно считать сторонами правильного
семиугольника с точностью 99.9%.
Установив раствор циркуля равным CK и двигаясь по окружности начиная из точки K,
отметим остальные четыре точки семиугольника. В результате получится семиугольник CKMNPQL.
Исходный текст программы построения семиугольника приведен в файле heptagon.js.