Разработать 3d-модель головы персонажа и выполнить анимацию движения губ и челюсти, применяя различные инструменты.
Использовать 3ds Max и язык программирования MaxScript.
Для решения данной задачи воспользуемся полигональной моделью половины головы, генерируемую приведенной в приложении MAXScript-программой.
Исходная модель содержит 200 полигонов (рис. 1).
Рис. 1. Полигональная модель головы
Модель представлена на рис. 1. в четырех проекциях (Top, Front, Left и Perspective).
Так как модель - это единое целое, то при попытке сдвинуть полигоны/вершины/ребра сдвигаемые компоненты будут тянуть за собой другие. Поэтому для раздельного перемещения губ между ними нужно создать разрез.
Преобразуем модель в EditablePoly, перейдем на подуровень Polygon.
Выделим полигоны нижней губы и применим к ним трижды метод Tessellate, выполняющий дробление полигонов (рис. 2).
Рис. 2. Нижняя губа после применения Tessellate
Далее выделим и удалим верхний ряд полигонов (рис. 3).
Рис. 3. Между губами есть разрез
Tessellate-дробление полигонов имеет и другое назначение: моделирование мимики губ будет более точным при большом числе полигонов.
Возвратимся на нулевой подуровень EditablePoly и употребим модификатор Symmetry, чтобы получить модель всей головы (рис. 4).
Рис. 4. После применения модификатора Symmetry
Код, выполняющий описанные выше действия:
-- Выбираем модель половины головы, созданную приведенным в приложении кодом
-- Здесь и далее hd - это идентификатор полигональной модели головы
select hd
subobjectLevel = 4
-- Выделяем полигоны нижней губы
hd.SetSelection #Face #{53..54, 57}
for k = 1 to 3 do hd.tessellate #Face
-- Формируем разрез между губами, удаляя выбранные полигоны
hd.SetSelection #Face #{198, 201, 210..211, 230..231, 234, 237, 246, 249, 258..259, 294..295, 298, 301, 310, 313, 322..323, 358..359, 362, 365}
hd.delete #Face
subobjectLevel = 0
-- Добавляем в модель вторую половину головы
sym = Symmetry()
addModifier hd sym
convertToPoly hd
Создаем кости шеи, головы и челюсти (рис. 5), применив следующий код:
vN = 3
viewport.ActiveViewport = vN
viewport.SetType #view_left
viewport.SetGridVisibility vN false
-- Кость шеи
b1 = boneSys.createBone [0, 42.3, -131.6] [0, 25, -75] [0, 0, 1]
-- Кости головы
b2 = boneSys.createBone [0, 25, -75] [0,8,120] [0, 0, 1]
b3 = boneSys.createBone [0, 8, 120] [0, 8, 124] [0, 0, 1]
b3.parent = b2
-- Кости челюсти
b4 = boneSys.createBone [0, 16.5, -67] [0 ,-91.5, -105.9] [0, 0, 1]
b5 = boneSys.createBone [0, -91.5, -105.9] [0, -94, -107] [0, 0, 1]
b5.parent = b4
#(b1, b2, b3, b4, b5).wireColor = yellow
Рис. 5. Кости внутри головы
Сустав между шейной и головной костью должен находиться примерно в месте последнего шейного позвонка. Сустав челюсти расположен чуть выше шейного позвонка.
Кости головы и шеи непосредственно в анимации не участвуют, но нужны для вертикальной фиксации модели головы при движении кости челюсти.
Употребим модификатор Skin, поместив в него три кости: шейную (b1), головную (b2) и челюстную (b4). Перейдем на подуровень Envelope и активизируем все фильтры:
max modify mode
select hd
skn = Skin()
addModifier hd skn
skinOps.addBone skn b1 1
skinOps.addBone skn b2 1
skinOps.addBone skn b4 1
-- Переходим на подуровень Envelope
subobjectLevel = 1
-- Активизируем фильтры вершин, оболочки и сечений
skn.filter_vertices = on
skn.filter_envelopes = on
skn.filter_cross_sections = on
Выбираем кость головы и выделяем все вершины модели, устанавливая их вес равным 1:
-- Кость головы
skinOps.SelectBone skn 2
-- hd.verts.count вернет 784
skinOps.SelectVertices skn#{1..hd.verts.count}
skinOps.setWeight skn 1
На рис. 6 показаны вершины, ассоциированные с костью головы (кость b2).
Рис. 6. Оболочка кости b2
Выбираем далее кость челюсти (b4) и выделяем вершины, которые будут связаны с костью b4 (сюда включены вершины подбородка и низа щек); устанавливаем вес вершин равным 1 (рис. 7):
-- Кость челюсти (кость b4). Она третья в списке костей
skinOps.SelectBone skn 3
skinOps.SelectVertices skn #{5, 6, 11, 41, 59..65, 113..115, 116, 117, 118, 119, 127..132, 142..143, 150..151, 155, 158, 171..175, 201..202, 204..205, 207..211, 213..214, 215..219, 222..229, 232..239, 241..251, 253..255, 256..262, 264..274, 276, 277, 279..281, 283..292, 294..311, 313, 314, 316..317, 319..327, 329..345, 347..348, 350..359, 360..375, 376..395, 408..411,416, 418..419, 486, 509..537, 580..592, 604..605, 609, 612, 615..616, 634..784}
skinOps.setWeight skn 1
Рис. 7. Оболочка кости b4
Теперь вершины кости b4 при перемещении кости будут следовать ее движениям.
Для вращения кости b4 относительно ее основания можно выбрать и перемещать кость b5 (рис. 8), которая является дочерним объектом b4.
Рис. 8. Сдвиг вниз кости b5 (b5.parent = b4)
Управление движением челюстью можно так же выполнить, применив рассмотренный в следующем разд. механизм.
Управляющий объект (УО) заимствован из [1]. УО - это окружность, которая не может быть перемещена за пределы прямоугольника, в котором она находится. Окружность иерархически подчинена прямоугольнику:
cir.parent = rec
Она используется как рукоятка контролера.
Перемещение окружности ограничено сторонами прямоугольника, что обеспечивает скрипт-контроллер на позицию центра окружности:
function mkCntrllr txt mv = (
local cir
--viewport.ActiveViewport = 2
--viewport.SetType #view_front
rec = rectangle length:50 width:50 wireColor:blue displayRenderMesh:true
cir = circle radius:2 wireColor:red
cir.parent = rec
-- Нельзя вращать объект cir
setTransformLockFlags cir #{3..9}
-- Вводим для наглядности текст Control
conName = text text:txt size:8 wireColor:[0, 150, 0] pos:[0, 27.5, 0] alignment:2 displayRenderMesh:false
conName.parent = rec
setTransformLockFlags conName #{1..9}
rec.rotation.x_rotation = 90
-- Перемещаем прямоугольник в зону видимости
select rec
move rec mv
-- Контроллер position_script(), не позволяющий окружности покинуть прямоугольник,
-- помещаем в список position_list()
listCon = cir.pos.controller = position_list()
scCon = listCon.available.controller = position_script()
-- Формируем атрибуты контроллера
def = attributes clamp (
parameters clampP (
L type:#float
w type:#float
xc type:#float
yc type:#float
)
)
custAttributes.add scCon def
-- Во всех случаях употребляем контроллер bezier_float()
scCon.L.controller = rec.length.controller = bezier_float()
scCon.L = 40
scCon.w.controller = rec.width.controller = bezier_float()
scCon.w = 40
scCon.xc.controller = listCon[1].x_position.controller = bezier_float()
scCon.yc.controller = listCon[1].y_position.controller = bezier_float()
script = "x = 0
y = 0
w = 0.5 * this.w
L = 0.5 * this.L
if this.xc > w do (x = -this.xc + w; this.xc = w)
if this.xc < -w do (x = -this.xc - w; this.xc = -w)
if this.yc > L do (y = -this.yc + L; this.yc = L)
if this.yc < -L do (y = -this.yc - L; this.yc = -L)
[x, y, 0]"
scCon.script = script
cir
)
-- Создаем первый УО
cir = mkCntrllr "Control 1" [-125, 0, -56]
select cir
Введенный УО показан на рис. 9.
Рис. 9. Окружность не может покинуть прямоугольник
Привязка контроллера осуществляется при помощи объекта paramWire.
Перед тем как делать привязку, кость нужно сначала подготовить, так как при привязке кость может получить существенное смещение. Чтобы при привязке смещения не происходило, нужно применить List контроллер. List контроллер позволяет добавить в список несколько контроллеров, и каждый последующий контроллер в списке будет потомком по отношению к предыдущему.
-- Привязка кости челюсти к УО
-- Кость челюсти
select b4
--actionMan.executeAction 1 "440"
b4.rotation.controller = rotation_list()
RC = b4.rotation.controller
-- Добавим в список контроллер EulerXYZ
--actionMan.executeAction 1 "440"
RC.Available.controller = Euler_XYZ ()
-- Теперь в списке два контроллера Euler XYZ
-- Переименуем только что добавленный
RC.SetName 2 "abc"
-- Выполняем привязку кости к контроллеру
select cir
PC = cir.pos.controller
-- Перемещение окружности по X будет вызывать поворот кости относительно оси Z
paramWire.connect PC.Position_XYZ.controller[#X_Position] RC.abc.controller[#Z_Rotation] "X_Position * 0.01"
-- Перемещение окружности по Y будет вызывать поворот кости относительно оси Y
paramWire.connect PC.Position_XYZ.controller[#Y_Position] RC.abc.controller[#Y_Rotation] "-Y_Position * 0.01"
Таким образом, сдвиг окружности на:
Положительный угол задает поворот против часовой.
Соответствие между движениями окружности (контроллера) и поворотами кости иллюстрирует на рис. 10.
Рис. 10. Сдвигая окружность, вращаем кость b4
Движение верхней губы осуществим средствами морфинга. Для этого создаем копию головы и удаляем в копии все модификаторы. Далее выделяем полигоны верхней губы и передвигаем их вверх, как показано на рис. 11.
Рис. 11. Цель морфинга - перемещены полигоны верхней губы
Это обеспечит следующий код:
-- Морф-цель для верхней губы
hdCopy =copy(hd)
move hdCopy [350, 0, 0]
select hdCopy
deleteModifier hdCopy 1
subobjectLevel = 4
hdCopy.EditablePoly.SetSelection #Face #{34..35, 39..44, 383..384, 388..393}
move hdCopy.selectedFaces [0, 0, 3]
Выбираем исходный объект и добавляем к нему модификатор Morpher, а в качестве морф-цели указываем копию головы:
select hd
mrph = Morpher()
addModifier hd mrph
-- Морф-цель
WM3_MC_BuildFromNode mrph 1 hdCopy
Создаем второй УО (по аналогии с вышерассмотренным):
cir2 = mkCntrllr "FaceControl" [125, 0, -56] -- (рис. 12)
Рис. 12. Создан второй контроллер для управления верхней губой
Выбираем второй УО и привязываем его, используя paramWire, к морф-цели:
select cir2
PC2 = cir2.pos.controller
paramWire.connect PC2.Position_XYZ.controller[#Y_Position] mrph[1] "Y_Position*10"
Теперь при перемещении окружности второго контроллера на dy верхняя губа будет пытаться переместится на 10 * dy.
Первый контроллер по-прежнему действует на челюсть.
Имея 2 контроллера, установим 4 ключа анимации на позиции контроллеров:
animate on (
at time 25 (
move cir [0, 0, -10]
move cir2 [0, 0, 25]
)
at time 50 (
move cir [0, 0, 10]
move cir2 [0, 0, -25]
)
at time 75 (
move cir [0, 0, -10]
move cir2 [0, 0, 25]
)
at time 100 (
move cir [0, 0, 10]
move cir2 [0, 0, -25]
)
)
playAnimation()
Рис. 13. Работают оба УО: кадры 0 и 25
Анимацию выбранных полигонов, например верхней губы, можно выполнить, применив модификатор Linked_XForm. Продемонстрируем его действие на копии модели головы:
select hdCopy
subobjectLevel = 4
-- Выбираем полигоны верхней губы
hdCopy.EditablePoly.SetSelection #Face #{34..35, 39..44, 383..384, 388..393}
LXF = Linked_XForm()
modPanel.addModToSelection LXF
-- Управляющий объект модификатора Linked_XForm
dm = dummy pos: [500, 0, -56]
LXF.control = dm
animate on (
at time 35 (
move dm [0, 0, -10]
)
at time 70 (
move cir [0, 0, 20]
)
at time 100 (
move cir [0, 0, -10]
)
)
playAnimation()
Верхняя губа перемещается вслед за dummy.
В случае исходной модели перед употреблением Linked_XForm следует в качестве базового объекта выбрать полигональную модель головы:
select hd
modPanel.setCurrentObject hd
subobjectLevel = 4
…
Рассмотрены варианты анимации движения челюсти и губ модели головы персонажа. Анимация движения головы, мимики лица и речи потребует усложнения модели и, возможно, поиск других решений.
На текущем этапе очевидно следующее:
Сочетая эти технологии, можно решать задачу воспроизведения речи и соответствующих эмоций.
Половина головы строится следующим кодом:
fn cnnctDg hd arrDg s sld pnch = (
hd.EditablePoly.SetSelection #Edge arrDg
hd.ConnectEdgeSegments = s
hd.ConnectEdgeSlide =sld
hd.ConnectEdgePinch = pnch
hd.EditablePoly.ConnectEdges()
return "Connected"
)
fn trtTwLvls nV arrL arrLL fs hd = (
nL = arrL.Count - 1 - fs
ks = 1 + fs
for k = ks to nL do (
p = arrL[k]
p2 = arrL[k + 1]
p3 = arrLL[k + 1]
p4 = arrLL[k]
polyop.CreateVert hd p
polyop.CreateVert hd p2
polyop.CreateVert hd p3
polyop.CreateVert hd p4
polyop.CreatePolygon hd #(nV + 1, nV + 2, nV + 3, nV + 4)
nV += 4
)
update hd
return nV
)
delete $*
viewport.ActiveViewport = 2
viewport.SetType #view_front
viewport.SetGridVisibility 2 false
max tool zoomExtents
if hd != udefined and not isDeleted hd do delete hd
-- Border
arrB = #(
[0, 88, -63],
[0, 94, -50],
[0, 100, -37],
[0, 114, 41],
[0, 89, 98],
[0, 0, 118],
[0, -74, 83],
[0, -96, 19],
[0, -114, -37],
[0, -91, -117],
[0, -36, -117]
)
-- Neck bottom (level 1)
arrL1 = #(
[0, -19, -139],
[17, -15, -133],
[39, 22, -126],
[26, 82, -117],
[0, 94, -114]
)
-- Neck top (level 2)
arrL2 = #(
[0, -36, -117],
[34, -30, -103],
[49, 7, -83],
[29, 74, -65],
[0, 88, -63]
)
-- Level 3
arrL3 = #(
[0, -91, -117],
[26, -84, -108],
[65, 16, -64],
[60, 84, -48],
[0, 94, -50]
)
-- Level 4
arrL4 = #(
[0, -114, -37],
[42, -97, -37],
[71, 19, -45],
[70, 93, -32],
[0, 100, -37]
)
-- Level 5
arrL5 = #(
[0, -96, 19],
[50, -83, 19],
[86, 51, 21],
[75, 106, 41],
[0, 114, 41]
)
-- Level 6
arrL6 = #(
[0, -74, 83],
[42, -64, 78],
[56, 0, 112],
[77, 82, 93],
[0, 89, 98]
)
-- Level 7
arrL7 = #(
[0, 0, 0],
[0, -74, 83],
[0, 0, 118],
[0, 89, 98],
[0, 0, 0]
)
-- Создаем пустую mesh
hd = editable_mesh wireColor:gray
convertToPoly hd
nV = 0
nV = trtTwLvls nV arrL1 arrL2 0 hd
nV = trtTwLvls nV arrL2 arrL3 0 hd
nV = trtTwLvls nV arrL3 arrL4 0 hd
nV = trtTwLvls nV arrL4 arrL5 0 hd
nV = trtTwLvls nV arrL5 arrL6 0 hd
nV = trtTwLvls nV arrL6 arrL7 1 hd
hd.WeldThreshold = 0.1
polyop.WeldVertsByThreshold hd #{1..nV}
--
select hd
-- Elbow
cnnctDg hd #{23, 37, 51} 2 0 0
ps = hd.Verts[28].Pos; hd.EditablePoly.WeldVerts 37 28 ps
hd.Verts[33].Pos = [6, -95, 19]
hd.Verts[34].Pos = [21, -91, 19]
--
-- Nose top
cnnctDg hd #{32, 58..59} 1 -45 0
hd.Verts[38].Pos = [9, -85, 2]
hd.Verts[39].Pos = [5, -89, 2]
hd.Verts[37].Pos = [0, -91, 2]
-- Nose bottom
hd.Verts[32].Pos = [5, -112, -35]
cnnctDg hd #{58, 62, 64} 1 -80 0
cnnctDg hd #{62, 64, 67} 1 80 0
ps = hd.Verts[40].Pos; hd.EditablePoly.WeldVerts 31 40 ps
ps = hd.Verts[31].Pos; hd.EditablePoly.WeldVerts 31 44 ps
--
-- 42, 43, 46, 50
zNP = -36
hd.Verts[42].Pos = [0, -116, zNP]
hd.Verts[43].Pos = [8, -114, zNP]
--
-- 40, 41, 45, 48
zNM = -44
hd.Verts[40].Pos = [0, -115, zNM]
hd.Verts[41].Pos = [7, -114, zNM]
--
-- 18, 32, 44, 47
zNL = -48
hd.Verts[18].Pos = [0, -110, zNL]
hd.Verts[32].Pos = [14, -108, zNL]
--
cnnctDg hd #{53, 64, 68, 74} 1 40 0
ps = hd.Verts[38].Pos; hd.EditablePoly.WeldVerts 38 45 ps
cnnctDg hd #{53, 67, 71, 73} 1 0 0
--
polyop.CreateEdge hd 46 49
--
hd.Verts[46].Pos = [15, -109, zNP]
hd.Verts[50].Pos = [20, -91, zNP]
--
hd.Verts[45].Pos = [15, -108, zNM]
x48 = 18
hd.Verts[48].Pos = [x48, -90, zNM]
--
hd.Verts[15].Pos = [42, -67, -42]
hd.Verts[31].Pos = [x48, -81, zNM + 2]
--
hd.Verts[18].Pos = [0, -85, zNL]
hd.Verts[32].Pos = [6, -84, zNL]
hd.Verts[44].Pos = [8, -83, zNL]
hd.Verts[47].Pos = [15, -82, zNL + 2]
--
hd.Verts[49].Pos = [19, -90, zNp + 4]
--
-- Lips: upper part
cnnctDg hd #{22, 24} 1 0 0
hd.Verts[51].Pos = [36, -76, -71]
hd.Verts[52].Pos = [0, -91, -70]
for k = 1 to 2 do hd.InsertVertexInEdge 91 0.5
for k = 1 to 3 do hd.InsertVertexInEdge 24 0.5
polyop.CreateEdge hd 53 55
polyop.CreateEdge hd 53 56
polyop.CreateEdge hd 54 57
polyop.CreateEdge hd 31 54
hd.Verts[54].Pos = [33, -64, -71]
hd.Verts[53].Pos = [26, -69, -70]
hd.Verts[55].Pos = [0, -98, -64]
hd.Verts[56].Pos = [0, -98, -60]
hd.Verts[57].Pos = [0, -96, -54]
--
cnnctDg hd #{92, 97, 98, 99} 3 0 0
--
ps = hd.Verts[52].Pos; hd.EditablePoly.WeldVerts 52 60 ps
hd.Verts[58].Pos = [11, -82, -70]
hd.Verts[57].Pos = [20, -74, -70]
--
hd.Verts[68].Pos = [6, -94, -54]
hd.Verts[67].Pos = [15, -87, -54]
hd.Verts[66].Pos = [24, -75, -59]
--
hd.Verts[65].Pos = [7, -96, -59]
hd.Verts[64].Pos = [13, -90, -60]
hd.Verts[63].Pos = [20, -80, -65]
--
hd.Verts[62].Pos = [7, -96, -63]
hd.Verts[61].Pos = [12, -91, -64]
hd.Verts[60].Pos = [20, -80, -66]
--
hd.InsertVertexInEdge 100 0.5
hd.Verts[69].Pos = [29, -72, -55]
polyop.CreateEdge hd 66 69
polyop.CreateEdge hd 31 67
polyop.CreateEdge hd 32 68
--
-- Lips: low part
--
for k = 1 to 3 do hd.InsertVertexInEdge 90 0.5
v0 = 59
z0 = hd.Verts[v0].Pos[3]
p = hd.Verts[56].Pos; dZ = p[3] - z0; hd.Verts[70].Pos = p - [0, 0, 2 * dZ]
p = hd.Verts[55].Pos; dZ = p[3] - z0; hd.Verts[71].Pos = p - [0, 0, 2 * dZ]
p = hd.Verts[54].Pos; dZ = p[3] - z0; hd.Verts[72].Pos = p - [0, 0, 2 * dZ]
--
polyop.CreateEdge hd 53 70
polyop.CreateEdge hd 52 71
polyop.CreateEdge hd 52 72
--
for k = 1 to 2 do hd.InsertVertexInEdge 128 0.5
for k = 1 to 2 do hd.InsertVertexInEdge 129 0.5
for k = 1 to 2 do hd.InsertVertexInEdge 130 0.5
--
-- row 1
hd.Verts[72].Pos = [0, -94, -79]
hd.Verts[77].Pos = [8, -91, -79]
hd.Verts[78].Pos = [20, -78, -75]
-- row 2
hd.Verts[71].Pos = [0, -90, -83]
hd.Verts[75].Pos = [8, -88, -82]
hd.Verts[76].Pos = [20, -77, -76]
-- row 3
hd.Verts[70].Pos = [0, -87, -89]
hd.Verts[73].Pos = [11, -83, -87]
hd.Verts[74].Pos = [25, -73, -80]
--
polyop.CreateEdge hd 57 78
polyop.CreateEdge hd 76 78
polyop.CreateEdge hd 74 76
polyop.CreateEdge hd 58 77
polyop.CreateEdge hd 75 77
polyop.CreateEdge hd 73 75
--
-- Eyes
cnnctDg hd #{32, 58, 62} 1 0 0
-- 37, 39, 38: [0, -91, 2], [5, -89, 2], [9, -85, 2]
-- 79, 80, 81
hd.Verts[79].Pos = [0, -95, 16]
hd.Verts[80].Pos = [6, -94, 16]
hd.Verts[81].Pos = [12, -88, 13]
--19, 33, 34
hd.Verts[19].Pos = [0, -96, 26]
hd.Verts[33].Pos = [6, -94, 26]
hd.Verts[34].Pos = [17, -87, 24]
--
hd.Verts[22].Pos = [49, -65, 28]
for k = 1 to 2 do hd.InsertVertexInEdge 31 0.5
hd.Verts[83].Pos = [57, -52, -3]
hd.Verts[82].Pos = [58, -53, 14]
hd.Verts[15].Pos = [48, -64, -43]
--
polyop.CreateEdge hd 38 83
for k = 1 to 2 do hd.InsertVertexInEdge 150 0.5
hd.Verts[85].Pos = [22, -79, -16]
hd.Verts[84].Pos = [47, -66, -16]
--
polyop.BevelFaces hd #(23) -1 -3
-- 86, 87, 88, 89, 90, 91, 92, 93
hd.Verts[86].Pos = [47, -71, 19]
hd.Verts[87].Pos = [23, -85, 18]
hd.Verts[88].Pos = [16, -83, 11]
hd.Verts[89].Pos = [12, -80, 2]
hd.Verts[90].Pos = [25, -74, -7]
hd.Verts[91].Pos = [46, -66, -7]
hd.Verts[92].Pos = [56, -58, 0]
hd.Verts[93].Pos = [57, -60, 12]
--
polyop.BevelFaces hd #(23) -1 -3
hd.Verts[94].Pos = [45, -73, 12]
hd.Verts[95].Pos = [28, -79, 12]
hd.Verts[96].Pos = [21, -77, 7]
hd.Verts[97].Pos = [17, -74, 2]
hd.Verts[98].Pos = [30, -74, -3]
hd.Verts[99].Pos = [45, -70, -3]
hd.Verts[100].Pos = [53, -65, 4]
hd.Verts[101].Pos = [52, -65, 6]
--
polyop.BevelFaces hd #(23) 1 -3
hd.Verts[102].Pos = [44, -74, 10]
hd.Verts[103].Pos = [29, -78, 10]
hd.Verts[104].Pos = [24, -76, 7]
hd.Verts[105].Pos = [19, -70, 2]
hd.Verts[106].Pos = [31, -72, -1]
hd.Verts[107].Pos = [44, -69, -1]
hd.Verts[108].Pos = [52, -66, 4]
hd.Verts[109].Pos = [52, -67, 5]
polyop.DeleteFaces hd #(23) delIsoVerts:off
--
-- Elbow
cnnctDg hd #{38..39, 59..60} 2 0 0
ps = hd.Verts[110].Pos; hd.EditablePoly.WeldVerts 110 111 ps
hd.Verts[22].Pos = [49, -79, 27]
hd.Verts[110].Pos = [52, -71, 38]
-- 112, 116, 113
hd.Verts[112].Pos = [0, -91, 42]
hd.Verts[116].Pos = [11, -89, 41]
hd.Verts[113].Pos = [28, -84, 41]
-- 111, 115, 114
hd.Verts[111].Pos = [0, -87, 59]
hd.Verts[115].Pos = [19, -85, 59]
hd.Verts[114].Pos = [34, -78, 56]
-- 30, 35, 36
hd.Verts[30].Pos = [0, -74, 82]
hd.Verts[35].Pos = [20, -69, 81]
hd.Verts[36].Pos = [42, -62, 74]
--
--
select hd
max modify mode
subobjectLevel = 2
hd.SetSelection #Edge #{40}
hd.Remove()
subobjectLevel = 0
polyop.CreateEdge hd 82 110
--
-- Nose
cnnctDg hd #{60, 62, 79, 82} 2 0 0
-- 118, 120, 122, 123
hd.Verts[118].Pos = [0, -106, -22]
hd.Verts[120].Pos = [5, -104, -22]
hd.Verts[122].Pos = [11, -97, -24]
hd.Verts[123].Pos = [13, -83, -28]
-- 117, 119, 121, 124
hd.Verts[117].Pos = [0, -97, -6]
hd.Verts[119].Pos = [5, -97, -7]
hd.Verts[121].Pos = [10, -91, -11]
hd.Verts[124].Pos = [12, -85, -17]
--
-- Cheecks
hd.InsertVertexInEdge 31 0.5
polyop.CreateEdge hd 124 125
for k = 1 to 2 do hd.InsertVertexInEdge 229 0.5
--
hd.Verts[127].Pos = [20, -80, -23]
hd.Verts[126].Pos = [42, -69, -32]
hd.Verts[125].Pos = [63, -51, -22]
polyop.CreateEdge hd 85 127
polyop.CreateEdge hd 84 126
--
select hd
subobjectLevel = 2
hd.SetSelection #Edge #{23}
hd.Remove()
subobjectLevel = 0
--
hd.Verts[15].Pos = [53, -43, -66]
hd.Verts[51].Pos = [40, -54, -94]
polyop.CreateEdge hd 15 53
polyop.CreateEdge hd 69 126
polyop.CreateEdge hd 31 127
hd.InsertVertexInEdge 30 0.5
hd.Verts[128].Pos = [57, -47, -44]
polyop.CreateEdge hd 53 128
--
hd.InsertVertexInEdge 22 0.5
hd.Verts[10].Pos = [0, -86, -116]
hd.Verts[9].Pos = [12, -86, -114]
hd.Verts[129].Pos = [31, -74, -103]
polyop.CreateEdge hd 74 129
polyop.CreateEdge hd 9 73
--
cnnctDg hd #{89, 123, 233..235, 237, 239..240} 2 0 0
--
hd.Verts[133].Pos = [0, -92, -106]
hd.Verts[144].Pos = [12, -91, -104]
hd.Verts[143].Pos = [31, -77, -94]
hd.Verts[130].Pos = [37, -65, -80]
hd.Verts[134].Pos = [40, -64, -70]
hd.Verts[141].Pos = [37, -65, -61]
hd.Verts[137].Pos = [31, -70, -49]
hd.Verts[139].Pos = [18, -81, -32]
--
hd.Verts[132].Pos = [0, -95, -96]
hd.Verts[145].Pos = [12, -93, -95]
hd.Verts[142].Pos = [29, -81, -86]
hd.Verts[131].Pos = [35, -66, -76]
hd.Verts[135].Pos = [38, -66, -70]
hd.Verts[140].Pos = [35, -66, -63]
hd.Verts[136].Pos = [30, -71, -52]
hd.Verts[138].Pos = [17, -79, -36]
--
hd.InsertVertexInEdge 233 0.5
hd.InsertVertexInEdge 252 0.5
polyop.CreateEdge hd 126 147
polyop.CreateEdge hd 51 146
polyop.CreateEdge hd 146 147
hd.Verts[147].Pos = [44, -59, -56]
hd.Verts[146].Pos = [43, -55, -70]
--
-- Right
--
hd.Verts[23].Pos = [86, 16, 21]
--
hd.Verts[3].Pos = [47, 14, -79] -- 20
hd.Verts[14].Pos = [29, 82, -60] -- 25
hd.Verts[7].Pos = [0, 90, -58] -- 3
hd.Verts[17].Pos = [0, 101, -34] -- 18
hd.Verts[21].Pos = [0, 101, -34] -- 18
hd.Verts[26].Pos = [0, 118, 19] -- 19
hd.Verts[27].Pos = [0, 94, 92] -- 17
hd.Verts[29].Pos = [0, 16, 121] -- 6
hd.Verts[30].Pos = [0, -74, 83] -- 16
--
hd.Verts[12].Pos = [19, 16, -64] -- 12
hd.Verts[13].Pos = [65, 68, -42] -- 11
hd.Verts[20].Pos = [65, 68, -42] -- 11
hd.Verts[25].Pos = [80, 64, 20] -- 15
hd.Verts[24].Pos = [67, 58, 94] -- 8
hd.Verts[28].Pos = [72, 16, 98] -- 7
hd.Verts[36].Pos = [42, -62, 74] -- 5
--
ps = hd.Verts[17].Pos; hd.EditablePoly.WeldVerts 17 21 ps
ps = hd.Verts[13].Pos; hd.EditablePoly.WeldVerts 13 20 ps
--
cnnctDg hd #{36, 38, 41} 1 0 0
cnnctDg hd #{30, 40, 42, 44, 277} 1 0 0
polyop.CreateEdge hd 14 149
polyop.CreateEdge hd 33 152
polyop.CreateEdge hd 108 146
polyop.CreateEdge hd 21 80
hd.Verts[149].Pos = [37, 98, -35]
hd.Verts[150].Pos = [46, 109, 20]
hd.Verts[153].Pos = [43, 102, 58]
hd.Verts[151].Pos = [38, 86, 89]
hd.Verts[152].Pos = [40, 16, 118.5] -- 174
--
hd.Verts[146].Pos = [86, 16, 51] -- 153
--
cnnctDg hd #{5, 15, 24, 43, 45, 288..290} 1 0 0
hd.Verts[154].Pos = [45, -4, -89]
hd.Verts[155].Pos = [62, -1, -79]
hd.Verts[156].Pos = [69, -3, -53]
hd.Verts[161].Pos = [75, -9, 17]
hd.Verts[160].Pos = [77, -16, 51]
hd.Verts[157].Pos = [63, -31, 90]
hd.Verts[159].Pos = [31, -35, 112]
hd.Verts[158].Pos = [0, -37, 113]
--
cnnctDg hd #{289, 291..293, 298} 1 0 0
polyop.CreateEdge hd 34 162
cnnctDg hd #{29, 304, 313} 3 0 0
polyop.CreateEdge hd 126 173
polyop.CreateEdge hd 123 174
polyop.CreateEdge hd 81 175
hd.Verts[163].Pos = [41, -22, -97]
hd.Verts[164].Pos = [55, -22, -90]
hd.Verts[165].Pos = [65, -23, -60]
hd.Verts[173].Pos = [69, -25, -40]
hd.Verts[174].Pos = [67, -29, -20]
hd.Verts[175].Pos = [69, -30, -1]
hd.Verts[166].Pos = [69, -35, 15]
hd.Verts[162].Pos = [65, -45, 45]
--
hd.Verts[170].Pos = [72, -5, -36]
hd.Verts[171].Pos = [71, -7, -18]
hd.Verts[172].Pos = [73, -9, 0]
hd.Verts[12].Pos = [65, 18, -68]
hd.Verts[16].Pos = [70, 19, -48]
--
-- Neck
cnnctDg hd #{7, 17} 1 0 0
hd.InsertVertexInEdge 3 0.5
polyop.CreateEdge hd 154 178
polyop.CreateEdge hd 13 177
--
cnnctDg hd #{2, 4, 6, 8, 10, 336, 338} 1 0 0
polyop.CreateEdge hd 163 181
polyop.CreateEdge hd 49 164
--
select hd
subobjectLevel = 2
hd.SetSelection #Edge #{16}
hd.Remove()
hd.SetSelection #Edge #{17}
hd.Remove()
hd.SetSelection #Edge #{82}
hd.Remove()
hd.SetSelection #Edge #{193}
hd.Remove()
hd.SetSelection #Edge #{304}
hd.Remove()
subobjectLevel = 0
--
hd.InsertVertexInEdge 349 0.5
polyop.CreateEdge hd 15 186
hd.Verts[127].Pos = [31, -74, -108]
hd.Verts[49].Pos = [40, -54, -102]
hd.Verts[186].Pos = [49, -39, -97]
--
hd.Verts[8].Pos = [0, -44, -118]
hd.Verts[11].Pos = [37, -38, -104]
polyop.CreateEdge hd 11 49
polyop.CreateEdge hd 11 186
hd.InsertVertexInEdge 13 0.5
polyop.CreateEdge hd 49 187
cnnctDg hd #{11, 355} 2 0 0
hd.Verts[187].Pos = [0, -64, -116]
hd.Verts[191].Pos = [12, -63, -114]
hd.Verts[190].Pos = [27, -58, -108]
hd.Verts[188].Pos = [12, -43, -115]
hd.Verts[189].Pos = [25, -41, -110]
polyop.CreateEdge hd 9 191
polyop.CreateEdge hd 127 190
polyop.CreateEdge hd 179 188
polyop.CreateEdge hd 181 189
--
-- Top
cnnctDg hd #{45, 281} 1 0 0
polyop.CreateEdge hd 22 193
hd.Verts[192].Pos = [0, 57, 113]
hd.Verts[193].Pos = [40, 58, 108]
--
-- Ear
hd.InsertVertexInEdge 30 0.5
polyop.CreateEdge hd 168 194
hd.InsertVertexInEdge 371 0.5
polyop.CreateEdge hd 13 195
polyop.CreateEdge hd 23 195
cnnctDg hd #{372..374} 2 0 0
hd.InsertVertexInEdge 313 0.5
polyop.CreateEdge hd 202 198
polyop.CreateEdge hd 169 199
polyop.CreateEdge hd 167 201
polyop.CreateEdge hd 167 200
cnnctDg hd #{371, 386..389} 1 0 0
--
select hd
subobjectLevel = 2
hd.SetSelection #Edge #{378}
hd.Remove()
hd.SetSelection #Edge #{379}
hd.Remove()
subobjectLevel = 1
hd.SetSelection #Vertex #{195}
hd.Remove()
subobjectLevel = 0
--
p = hd.Verts[203].Pos; x = p[1]; hd.Verts[203] = [x, 33, -43] -- [84, 33, -47]
p = hd.Verts[204].Pos; x = p[1]; hd.Verts[204] = [x, 31, -35] -- [82, 31, -35]
p = hd.Verts[202].Pos; x = p[1]; hd.Verts[202] = [x, 30, -14] -- [78, 30, -14]
p = hd.Verts[205].Pos; x = p[1]; hd.Verts[205] = [x, 30, 3] -- [87, 30, 3]
p = hd.Verts[206].Pos; x = p[1]; hd.Verts[206] = [x, 29, 16] -- [96, 29, 16]
--
p = hd.Verts[198].Pos; x = p[1]; hd.Verts[198] = [x, 40, -25] -- [85, 40, -25]
p = hd.Verts[195].Pos; x = p[1]; hd.Verts[195] = [x, 45, -11] -- [90, 45, -12]
p = hd.Verts[200].Pos; x = p[1]; hd.Verts[200] = [x, 41, 1] -- [90, 41, 1]
--
p = hd.Verts[197].Pos; x = p[1]; hd.Verts[197] = [x, 56, -30] -- [88, 48, -30]
p = hd.Verts[196].Pos; x = p[1]; hd.Verts[196] = [x, 64, -11] -- [96, 64, 0]
p = hd.Verts[199].Pos; x = p[1]; hd.Verts[199] = [x, 51, 14] -- [98, 51, 19]
--
for k = 1 to 2 do polyop.ExtrudeFaces hd #{10, 178, 184, 186, 188..189} 4
ps = hd.Verts[169].Pos; hd.EditablePoly.WeldVerts 169 207 ps
ps = hd.Verts[169].Pos; hd.EditablePoly.WeldVerts 169 219 ps
ps = hd.Verts[169].Pos; hd.EditablePoly.WeldVerts 169 201 ps
ps = hd.Verts[167].Pos; hd.EditablePoly.WeldVerts 167 212 ps
ps = hd.Verts[167].Pos; hd.EditablePoly.WeldVerts 167 223 ps
ps = hd.Verts[204].Pos; hd.EditablePoly.WeldVerts 204 212 ps
ps = hd.Verts[204].Pos; hd.EditablePoly.WeldVerts 204 222 ps
ps = hd.Verts[200].Pos; hd.EditablePoly.WeldVerts 200 212 ps
ps = hd.Verts[200].Pos; hd.EditablePoly.WeldVerts 200 221 ps
ps = hd.Verts[195].Pos; hd.EditablePoly.WeldVerts 195 212 ps
ps = hd.Verts[195].Pos; hd.EditablePoly.WeldVerts 195 220 ps
ps = hd.Verts[198].Pos; hd.EditablePoly.WeldVerts 198 212 ps
ps = hd.Verts[198].Pos; hd.EditablePoly.WeldVerts 198 219 ps
ps = hd.Verts[203].Pos; hd.EditablePoly.WeldVerts 203 212 ps
ps = hd.Verts[203].Pos; hd.EditablePoly.WeldVerts 203 218 ps
--
hd.Verts[206] = [73, 21, -44]
hd.Verts[212] = [82, 20, -45]
hd.Verts[207] = [74, 32, -45]
hd.Verts[213] = [84, 33, -47]
hd.Verts[208] = [75, 47, -32]
hd.Verts[214] = [88, 48, -30]
hd.Verts[209] = [82, 63, 0]
hd.Verts[215] = [97, 64, 0]
hd.Verts[210] = [88, 50, 20]
hd.Verts[216] = [98, 51, 19]
hd.Verts[211] = [89, 28, 17]
hd.Verts[217] = [96, 29, 16]
--
hd.Verts[198] = [85, 40, -25]
hd.Verts[195] = [90, 45, -12]
hd.Verts[200] = [90, 41, 1]
--
hd.Verts[203] = [82, 31, -35]
hd.Verts[201] = [78, 30, -14]
hd.Verts[204] = [87, 30, 3]
--
hd.Verts[169] = [74, 19, -34]
hd.Verts[168] = [78, 17, -14]
hd.Verts[167] = [83, 12, 2]
-- Ear top
ps = hd.Verts[167].Pos; hd.EditablePoly.WeldVerts 167 205 ps
hd.Verts[198] = [80, 43, 6]
hd.Verts[195] = [76, 44, -12]
-- Ear bottom
ps = hd.Verts[201].Pos; hd.EditablePoly.WeldVerts 201 196 ps
hd.Verts[200] = [72, 39, -26]
--
select hd
subobjectLevel = 2
hd.SetSelection #Edge #{394}
hd.Remove()
subobjectLevel = 0
-- Создает УО
function mkCntrllr txt mv = (
local cir
--viewport.ActiveViewport = 2
--viewport.SetType #view_front
rec = rectangle length:50 width:50 wireColor:blue displayRenderMesh:true
cir = circle radius:2 wireColor:red
cir.parent = rec
-- Нельзя вращать объект cir
setTransformLockFlags cir #{3..9}
-- Вводим для наглядности текст Control N
conName = text text:txt size:8 wireColor:[0, 150, 0] pos:[0, 27.5, 0] alignment:2 displayRenderMesh:false
conName.parent = rec
addModifier conName (meshSelect())
setTransformLockFlags conName #{1..9}
rec.rotation.x_rotation = 90
-- Перемещаем прямоугольник в зону видимости
select rec
move rec mv
-- Контроллер position_script(), не позволяющий окружности покинуть прямоугольник
-- помещаем в список position_list()
listCon = cir.pos.controller = position_list()
scCon = listCon.available.controller = position_script()
-- Формируем атрибуты контроллера
def = attributes clamp (
parameters clampP (
L type:#float
w type:#float
xc type:#float
yc type:#float
)
)
custAttributes.add scCon def
-- Во всех случаях употребляем контроллер bezier_float()
scCon.L.controller = rec.length.controller = bezier_float()
scCon.L = 40
scCon.w.controller = rec.width.controller = bezier_float()
scCon.w = 40
scCon.xc.controller = listCon[1].x_position.controller = bezier_float()
scCon.yc.controller = listCon[1].y_position.controller = bezier_float()
script = "x = 0
y = 0
w = 0.5 * this.w
L = 0.5 * this.L
if this.xc > w do (x = -this.xc + w; this.xc = w)
if this.xc < -w do (x = -this.xc - w; this.xc = -w)
if this.yc > L do (y = -this.yc + L; this.yc = L)
if this.yc < -L do (y = -this.yc - L; this.yc = -L)
[x, y, 0]"
scCon.script = script
cir
)
--
-- Выбираем модель половины головы, созданную приведенным в приложении кодом
select hd
subobjectLevel = 4
-- Выделяем полигоны нижней губы
hd.SetSelection #Face #{53..54, 57}
for k = 1 to 3 do hd.tessellate #Face
-- Формируем разрез между губами, удаляя выбранные полигоны
hd.SetSelection #Face #{198, 201, 210..211, 230..231, 234, 237, 246, 249, 258..259, 294..295, 298, 301, 310, 313, 322..323, 358..359, 362, 365}
hd.delete #Face
subobjectLevel = 0
-- Добавляем в модель вторую половину головы
sym = Symmetry()
addModifier hd sym
convertToPoly hd
-- Создаем кости шеи, головы и челюсти
vN = 3
viewport.ActiveViewport = vN
viewport.SetType #view_left
viewport.SetGridVisibility vN false
-- Кость шеи
b1 = boneSys.createBone [0, 42.3, -131.6] [0, 25, -75] [0, 0, 1]
-- Кости головы
b2 = boneSys.createBone [0, 25, -75] [0,8,120] [0, 0, 1]
b3 = boneSys.createBone [0, 8, 120] [0, 8, 124] [0, 0, 1]
b3.parent = b2
-- Кости челюсти
b4 = boneSys.createBone [0, 16.5, -67] [0 ,-91.5, -105.9] [0, 0, 1]
b5 = boneSys.createBone [0, -91.5, -105.9] [0, -94, -107] [0, 0, 1]
b5.parent = b4
#(b1, b2, b3, b4, b5).wireColor = yellow
-- Употребляем Skin и ассоциируем кости с вершинами модели головы
max modify mode
select hd
skn = Skin()
addModifier hd skn
skinOps.addBone skn b1 1
skinOps.addBone skn b2 1
skinOps.addBone skn b4 1
-- Переходим на подуровень Envelope
subobjectLevel = 1
-- Активизируем фильтры вершин, оболочки и сечений
skn.filter_vertices = on
skn.filter_envelopes = on
skn.filter_cross_sections = on
-- Выбираем кость головы и выделяем все вершины модели, устанавливая их вес равным 1
-- Кость головы
skinOps.SelectBone skn 2
-- hd.verts.count вернет 784
skinOps.SelectVertices skn#{1..hd.verts.count}
skinOps.setWeight skn 1
-- Кость челюсти (кость b4). Она третья в списке костей
skinOps.SelectBone skn 3
skinOps.SelectVertices skn #{5, 6, 11, 41, 59..65, 113..115, 116, 117, 118, 119, 127..132, 142..143, 150..151, 155, 158, 171..175, 201..202, 204..205, 207..211, 213..214, 215..219, 222..229, 232..239, 241..251, 253..255, 256..262, 264..274, 276, 277, 279..281, 283..292, 294..311, 313, 314, 316..317, 319..327, 329..345, 347..348, 350..359, 360..375, 376..395, 408..411,416, 418..419, 486, 509..537, 580..592, 604..605, 609, 612, 615..616, 634..784}
skinOps.setWeight skn 1
-- Создаем первый УО
cir = mkCntrllr "Control 1" [-125, 0, -56]
select cir
-- Привязка кости челюсти к УО
-- Кость челюсти
select b4
--actionMan.executeAction 1 "440"
b4.rotation.controller = rotation_list()
RC = b4.rotation.controller
-- Добавим в список контроллер EulerXYZ
--actionMan.executeAction 1 "440"
RC.Available.controller = Euler_XYZ ()
-- Теперь в списке два контроллера Euler XYZ
-- Переименуем только что добавленный
RC.SetName 2 "abc"
-- Выполняем привязку кости к контроллеру
select cir
PC = cir.pos.controller
-- Перемещение окружности по X будет вызывать поворот кости относительно оси Z
paramWire.connect PC.Position_XYZ.controller[#X_Position] RC.abc.controller[#Z_Rotation] "X_Position * 0.01"
-- Перемещение окружности по Y будет вызывать поворот кости относительно оси Y
paramWire.connect PC.Position_XYZ.controller[#Y_Position] RC.abc.controller[#Y_Rotation] "-Y_Position * 0.01"
-- Морф-цель для верхней губы
hdCopy =copy(hd)
move hdCopy [350, 0, 0]
select hdCopy
deleteModifier hdCopy 1
subobjectLevel = 4
hdCopy.EditablePoly.SetSelection #Face #{34..35, 39..44, 383..384, 388..393}
move hdCopy.selectedFaces [0, 0, 3]
-- Выбираем исходный объект и добавляем к нему модификатор Morpher, а в качестве морф-цели указываем копию головы
select hd
mrph = Morpher()
addModifier hd mrph
-- Морф-цель
WM3_MC_BuildFromNode mrph 1 hdCopy
-- Создаем второй УО (по аналогии с вышерассмотренным)
cir2 = mkCntrllr "Control 2" [125, 0, -56]
-- Выбираем второй УО и привязываем его, используя paramWire, к морф-цели
select cir2
PC2 = cir2.pos.controller
paramWire.connect PC2.Position_XYZ.controller[#Y_Position] mrph[1] "Y_Position*10"
-- Анимации контроллеров cir и cir2
animate on (
at time 25 (
move cir [0, 0, -10]
move cir2 [0, 0, 25]
)
at time 50 (
move cir [0, 0, 10]
move cir2 [0, 0, -25]
)
at time 75 (
move cir [0, 0, -10]
move cir2 [0, 0, 25]
)
at time 100 (
move cir [0, 0, 10]
move cir2 [0, 0, -25]
)
)
playAnimation()