Список работ

Создание 3d-модели головы персонажа и воспроизведение по слогам простых слов

Бартеньев О. В., Титов Д. А.

Содержание

1. Постановка задачи

Разработать 3d-модель головы персонажа и выполнить анимацию движения губ и челюсти, применяя различные инструменты.
Использовать 3ds Max и язык программирования MaxScript.

2. Порядок решения

  1. Создать полигональную модель головы персонажа.
  2. Ввести кости и употребив модификатор Skin, ассоциировать с ними соответствующие вершины модели головы.
  3. Разработать модель управляющего объекта (контроллера).
  4. Связать управляющий объект с костями.
  5. Реализовать, привлекая управляющий объект, морфинг-анимацию как вариант управления моделью головы.
  6. Выполнить, используя управляющие объекты, AutoKey-анимацию движения губ.
  7. Рассмотреть Linked_XForm как средство анимации движения частей модели головы.

3. Решение задачи

3.1. Полигональная модель

Для решения данной задачи воспользуемся полигональной моделью половины головы, генерируемую приведенной в приложении MAXScript-программой.
Исходная модель содержит 200 полигонов (рис. 1).

Половина головы

Рис. 1. Полигональная модель головы

Модель представлена на рис. 1. в четырех проекциях (Top, Front, Left и Perspective).

3.2. Подготовка модели к анимации

Так как модель - это единое целое, то при попытке сдвинуть полигоны/вершины/ребра сдвигаемые компоненты будут тянуть за собой другие. Поэтому для раздельного перемещения губ между ними нужно создать разрез.
Преобразуем модель в EditablePoly, перейдем на подуровень Polygon.
Выделим полигоны нижней губы и применим к ним трижды метод Tessellate, выполняющий дробление полигонов (рис. 2).

Применили Tessellate

Рис. 2. Нижняя губа после применения Tessellate

Далее выделим и удалим верхний ряд полигонов (рис. 3).

Разделяем губы

Рис. 3. Между губами есть разрез

Tessellate-дробление полигонов имеет и другое назначение: моделирование мимики губ будет более точным при большом числе полигонов.
Возвратимся на нулевой подуровень EditablePoly и употребим модификатор Symmetry, чтобы получить модель всей головы (рис. 4).

Применен Symmetry

Рис. 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

3.3. Кости и кожа Skin

Создаем кости шеи, головы и челюсти (рис. 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).

Вершины кости 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

Вершины кости b4

Рис. 7. Оболочка кости b4

Теперь вершины кости b4 при перемещении кости будут следовать ее движениям.
Для вращения кости b4 относительно ее основания можно выбрать и перемещать кость b5 (рис. 8), которая является дочерним объектом b4.

Перемещаем кость b5

Рис. 8. Сдвиг вниз кости b5 (b5.parent = b4)

Управление движением челюстью можно так же выполнить, применив рассмотренный в следующем разд. механизм.

3.4. Управляющий объект

Управляющий объект (УО) заимствован из [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. Окружность не может покинуть прямоугольник

3.5. Привязка кости челюсти к управляющему объекту

Привязка контроллера осуществляется при помощи объекта 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

3.6. Создание и привязка морф-цели для губ

Движение верхней губы осуществим средствами морфинга. Для этого создаем копию головы и удаляем в копии все модификаторы. Далее выделяем полигоны верхней губы и передвигаем их вверх, как показано на рис. 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.
Первый контроллер по-прежнему действует на челюсть.

3.7. Анимация слова Мама

Имея 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

3.8. Употребление Linked_XForm

Анимацию выбранных полигонов, например верхней губы, можно выполнить, применив модификатор 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

Заключение

Рассмотрены варианты анимации движения челюсти и губ модели головы персонажа. Анимация движения головы, мимики лица и речи потребует усложнения модели и, возможно, поиск других решений.
На текущем этапе очевидно следующее:

Сочетая эти технологии, можно решать задачу воспроизведения речи и соответствующих эмоций.

Приложение

П1. Половина головы

Половина головы строится следующим кодом:

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

П2. Полный код создания анимации

-- Создает УО
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()

Источники

  1. http://render.ru/books/show_book.php?book_id=792&com_start=40.
  2. Paul Neale. Facial Rigging Techniques.

Список работ

Рейтинг@Mail.ru