Из курса математики мы знаем, что через любые три точки в пространстве можно провести только одну окружность. Пусть у нас есть три точки в сцене Blender, заданные координатами V0 (x0, y0, z0), V1 (x1, y1, z1) и V2 (x2, y2, z2). Давайте разберемся, как мы можем построить окружность, которая проходит через эти три точки.
Пусть любые три точки в пространстве задаются тремя объектами Ico Sphere, размещенными в сцене.
1 2 3 |
p0 = bpy.data.objects['Icosphere'] p1 = bpy.data.objects['Icosphere.001'] p2 = bpy.data.objects['Icosphere.002'] |
Получим их координаты.
1 2 3 4 5 6 7 8 |
v0 = p0.location # <Vector (3.8803, 0.0357, 2.6335)> v1 = p1.location # <Vector (-1.0290, -6.2008, 1.4619)> v2 = p2.location # <Vector (-2.1548, 3.5917, 2.0728)> |
Для того чтобы построить окружность, которая проходит через эти три точки, нам нужно найти два ее параметра: ее центр и ее радиус.
Центр окружности можно рассматривать как центр сферы, на поверхности которой лежат все три изначальные точки. Также все четыре точки (три изначальные точки и центр сферы) должны лежать в одной плоскости.
Мы можем записать систему уравнений для трех сфер, проходящих через три заданные точки, а так же систему уравнений, описывающих принадлежность точек одной плоскости. Решив эту систему уравнений, мы можем найти нужные нам значения центра сферы и ее радиуса.
Пропустим математические преобразования, и сразу перейдем к алгоритму с кодом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
vv1 = v1 - v0 vv2 = v2 - v0 def _dot(_v1, _v2): return _v1.x * _v2.x + _v1.y * _v2.y + _v1.z * _v2.z v1v1 = _dot(vv1, vv1) v2v2 = _dot(vv2, vv2) v1v2 = _dot(vv1, vv2) base = 0.5 / (v1v1 * v2v2 - v1v2 * v1v2) k1 = base * v2v2 * (v1v1 - v1v2) k2 = base * v1v1 * (v2v2 - v1v2) circle_center = v0 + vv1 * k1 + vv2 * k2 # <Vector (-0.8859, -1.2299, 1.8714)> circle_radius = (circle_center - v0).length # 4.989844552955784 |
Обратите внимание, что для удобства вычисления коэффициентов уравнения мы определили функцию для получения псевдо-произведения одного вектора на другой _dot(). В результате мы получаем число, равное сумме квадратов соответствующих координат перемножаемых векторов.
Рассчитав таким образом координаты центра окружности и ее радиус, мы теперь можем добавить в сцену окружность – объект Circle. Поместим в точку, где должен располагаться центр окружности, 3D-курсор, и вызовем оператор добавления окружности в сцену.
1 2 3 4 5 6 |
bpy.context.scene.cursor.location = circle_center bpy.ops.mesh.primitive_circle_add( radius=circle_radius, location=circle_center ) |
Окружность находится в нужном месте, однако она все еще не проходит через нужные нам три точки.
Это потому, что окружность всегда добавляется в сцену так, что она располагается горизонтально – в плоскости XY.
Для того чтобы окружность легла на исходные три точки, ее нужно повернуть так, чтобы ее нормаль совпадала с нормалью плоскости, образованной тремя исходными точками. Мы можем сделать это рассчитав матрицу поворота вектора нормали окружности до вектора нормали плоскости трех точек.
Нормаль к плоскости, образованной тремя точками
1 2 3 4 |
normal = (v1 - v0).cross(v1 - v2) normal.normalize() # <Vector (-0.1374, -0.0774, 0.9875)> |
И нормаль построенной окружности
1 |
Vector((0.0, 0.0, 1.0)) |
Воспользовавшись одной из функций расчета матрицы поворота из одного вектора в другой, мы получим нужную нам матрицу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def get_mat_rot(src_vector, dest_vector): angle = src_vector.angle(dest_vector) axis = src_vector.cross(dest_vector) axis.normalize() return Matrix.Rotation(angle, 4, axis) m = get_mat_rot( src_vector=Vector((0.0, 0.0, 1.0)), dest_vector=normal ) # <Matrix 4x4 ( 0.9905, -0.0053, -0.1374, 0.0000) # (-0.0053, 0.9970, -0.0774, 0.0000) # ( 0.1374, 0.0774, 0.9875, 0.0000) # ( 0.0000, 0.0000, 0.0000, 1.0000)> |
Теперь повернем построенную окружность при помощи полученной матрицы.
1 |
bpy.context.object.matrix_world @= m |
И теперь построенная окружность проходит через все три изначально заданные точки.