Меш может иметь любую форму, и в большинстве случаев нет хорошей формулы, которая бы аппроксимировала его форму. Следовательно, мы аппроксимируем локальную кривизну неизбежно грубым способом (если нужна дополнительная информация и немного тяжелой математики, смотрите http://en.wikipedia.org/wiki/Mean_curvature), вычисляя среднюю рёберную кривизну всех связанных с вершиной рёбер для каждой вершины в меше. Здесь мы определяем рёберную кривизну как скалярное произведение нормализованной вершинной нормали и вектора ребра (то есть, вектор, формируемый от вершины к её соседке). Это произведение будет отрицательным, если ребро изгибается вниз относительно нормали, и положительным, если оно изгибается вверх. Мы обратим этот знак, так как нам более привычно понятие положительной кривизны для пиков, а не для впадин. По-другому можно посмотреть на это так: в областях положительной кривизны угол между вершинной нормалью и ребром, начинающемся в той же вершине, больше 90°.
Следующий рисунок иллюстрирует концепцию - он изображает серию вершин, связанных рёбрами. У каждой вершины показана связанная с ней вершинная нормаль (стрелками). Вершины, обозначенные как a, имеют положительную кривизну, те, что обозначены b - отрицательную кривизну. Две из показанных вершин помечены буквой c, они находятся в области нулевой кривизны - в этих местах поверхность плоская, и вершинная нормаль перпендикулярна рёбрам.
Функцию, которая вычисляет локальную кривизну для каждой вершины в меше, и возвращает список нормализованных весов, можно осуществить следующим образом:
from collections import defaultdict
def localcurvature(me,positive=False):
end=defaultdict(list)
for e in me.edges:
end[e.v1.index].append(e.v2)
end[e.v2.index].append(e.v1)
weights=[]
for v1 in me.verts:
dvdn = []
for v2 in end[v1.index]:
dv = v1.co-v2.co
dvdn.append(dv.dot(v1.no.normalize()))
weights.append((v1.index,sum(dvdn)/max(len(dvdn),
1.0)))
if positive:
weights = [(v,max(0.0,w)) for v,w in weights]
minimum = min(w for v,w in weights)
maximum = max(w for v,w in weights)
span = maximum - minimum
if span > 1e-9:
return [(v,(w-minimum)/span) for v,w in weights]
return weights
Функция
Сложная работа выполняется на выделенных строках. Здесь мы проходим циклом над всеми вершинами, и затем, во внутреннем цикле, проверяем каждое связанное с текущей вершиной ребро, чтобы извлечь вершину на другом конце из предварительно рассчитанного словаря. Затем мы вычисляем
weights.append((v1.index,sum(dvdn)/max(len(dvdn),1.0)))
Предшествующая строка может выглядеть странно, но она добавляет кортеж, состоящий из индекса вершины и средней кривизны, где среднее число получается вычислением суммы всех величин кривизны по каждому ребру из списка, и деления её на количество величин в списке. Поскольку список может быть пустым (это случается, когда меш содержит не связанные вершины), мы предохраняемся от ошибки деления на 0, деля её на длину списка или на единицу, в зависимости от того, что больше. Таким образом, мы сохраняем наш код более удобочитаемый, избегая оператора
С функцией
if __name__ == "__main__":
try:
choice = Blender.Draw.PupMenu("Normalization%t|Only
positive|Full range")
if choice>0:
ob = Blender.Scene.GetCurrent().objects.active
me = ob.getData(mesh=True)
try:
me.removeVertGroup('Curvature')
except AttributeError:
pass
me.addVertGroup('Curvature')
for v,w in localcurvature(me,
positive=(choice==1)):