mannequin.js

This document is also available in English.

Съдържание

Обща информация

Mannequin.js е малка библиотека за правене движеща се фигура на манекен. Формата на фигурата и движенията ѝ се извършват изцяло в JavaScript. Изображението се генерира чрез Three.js. Кликнете върху картинката, за да пуснете демонстрация на живо.

Може да се пробвате да създадете собствени пози с онлайн Редактора на Пози

Това е четвъртата версия на библиотеката. Първата беше реализирана със софтуера Elica. Втората бе написана на C/C++ и OpenGL. Третата версия беше пренаписана на JavaScript и Three.js. Тя е прекият предшественик на текущата библиотека mannequin.js. Още от първата си версия mannequin.js се използва в курса Основи на компютърната графика за студенти от специалност Компютърни науки от Факултет по Математика и Информатика към Софийски Университет.

Mannequin.js е с лиценз GPL-3.0. Последната версия е 4.5 от януари 2023.

Three.js и OrbitControls.js са включени като предпазна мярка спрямо несъвместимост с бъдещи версии. Те не са част от mannequin.js.

Инициализация

Библиотеката mannequin.js се предоставя като самостоятелен JavaScript файл, който трябва да се използва съвместно с three.js или three.min.js.

Минимална програма

Това е най-късата програма, която създава мъжка фигура в браузър (пример на живо):

<!DOCTYPE html>
<html>
   <body>
      <script src="../three.min.js"></script>
      <script src="../mannequin.js"></script>
      <script>
         createScene();
         man = new Male();
      </script>
   </body>
</html>

Помощната функция createScene() създава сцената, осветлението, камерата, земята и т.н. С друга помощна функция animate(t) (тя не е използвана в минималния пример) се дефинира позата на фигурата в момент t. Ако сцената е създадена със собствена функция, трябва да се добави и изрично управление на анимационния цикъл.

Видове фигури

Фигурите в библиотеката се създават като инстанции на класовете Male(height), Female(height) или Child(height), където незадължителният параметър height е относителният размер на фигурата. По подразбиране Male има височина 1.00, Female има височина 0.95 и Child има височина 0.65 (пример на живо):

man = new Male();
man.position.x = 20;
man.turn = -120;
:
woman = new Female();
woman.position.x = -20;
woman.turn = -60;
:
kid = new Child();
kid.position.z = -7
:

Тези три класа има общ родиел – класът Mannequin(feminine,height), в който булевият параметър feminine определя дали формата е женствена или мъжествена (пример на живо):

Разликата между използването на различните класове за фигури е в това, че Mannequin придава подразбираща се неутрална поза на фигурата, докато Male и Female придават мъжествена и женствена поза.

Части на тялото

Всички видове фигури имат една и съща структура. Например, дясната ръка в кръстена r_arm. За някои части на тялото mannequin.js използва името на ставата – напр. лявата предмишница е кръстена на лакъта l_elbow. Левите и десните части на тялото са винаги спрямо фигурата, а не спрямо потребителя (пример на живо):

Всяка част от тялото има ротационни свойства, които определят нейната позиция. Стойностите им са ъгли на завъртане в градуси, така че 180 е завъртане на половин оборот, а 360 е пълен оборот. Отрицателни ъгли са разрешени и представляват завъртане в противоположни посоки.

Mannequin.js има два начина за настройка на въртене – абсолютно и относително. Когато свойството за ротация е зададено с конкретна стойност, това създава абсолютно завъртане. Следващият код ще зададе ъгъла на огъване напред на торса на 45°:

man.torso.bend = 45;

Абсолютните ротации се считат от някои хора за неинтуитивни. Някои стави, като китките, имат ротации по три ъгъла. Поради естеството на ротациите в 3D пространство, трите ротации са взаимосвързани – промяната на една от тях често засяга другите две. Следващият код демонстрира как променянето на свойството turn променя свойството bend.

man.torso.bend = 45; /* bend=45 */
man.torso.turn = 45; /* turn=45, но вече bend≈35.3 */

Относителните ротации се задават по отношение на текущата стойност на ротационно свойството. Модификациите са много по-безопасни, тъй като не разчитат на фиксирани стойности. Следващият код ще наведе торса на 45° от текущата му позиция и след това го завърти на 45°:

man.torso.bend += 45;
man.torso.turn += 45;

Централни части на тяло

Централните части на тялото са тези, които са единични – глава head, врат neck, торс torso, таз pelvis и цялото тяло като body. За да се завърти цялото тяло се използват свойствата bend, turn и tilt на елемента body на фигурата или самата фигура (пример на живо):

figure.body.bend = angle;
figure.body.turn = angle;
figure.body.tilt = angle;

figure.bend = angle;
figure.turn = angle;
figure.tilt = angle;

Главата head поддържа свойствата nod, turn and tilt (пример на живо):

figure.head.nod = angle;
figure.head.turn = angle;
figure.head.tilt = angle;

Торсът torso има свойства bend, turn и tilt (пример на живо):

figure.torso.bend = angle;
figure.torso.turn = angle;
figure.torso.tilt = angle;

Въпреки че вратът neck е отделна част от тялото, тя не се контролира индивидуално. Вместо това половината от въртенето на главата се разпределя върху врата. По същия начин тазът pelvis не се контролира индивидуално. Вместо това цялото тяло се контролира чрез навеждане, завъртане и накланяне.

Горни крайници

Горните крайници са симетрични части на тялото: ръка arm, лакът elbow, китка wrist, пръсти fingers и индивидуални пръсти върхове на пръсти finger_0 до finger_4 с техните средни фаланги (finger_0.mid до finger_4.mid) и върхове (finger_0.tip до finger_4.tip).

И двете ръце arms, l_arm и r_arm, поддържат свойства raise, straddle и turn (пример на живо). Следващият код показва свойствата на дясната ръка, но същите са налични и за лявата ръка:

figure.r_arm.raise = angle;
figure.r_arm.straddle = angle;
figure.r_arm.turn = angle;

По принцип ротациите на симетричните части на тялото се стремят да запазят симетрията. Например, положителни относителни стойности на straddle завъртат лявата ръка наляво, а дясната – надясно.

Завъртането на лакътя elbow е само с bend (пример на живо). Отрицателни стойности на angle водят до неестествена поза на лакътя.

figure.r_elbow.bend = angle;

Китките wrists имат същите свойства като торса: bend, turn и tilt (пример на живо), но подобно на ръцете, ротациите са симетрични:

figure.r_wrist.bend = angle;
figure.r_wrist.turn = angle;
figure.r_wrist.tilt = angle;

Последните части на горните крайници са пръстите fingers. Те са дефинирани като множества (l_fingers и r_fingers) от отделни пръсти (l_finger_0 до l_finger_4 и r_finger_0 до r_finger_0).

Множествата могат само да се свиват с bend. Свиването на пръстите автоматично свива и техните средни фаланги и върхове, така че с l_fingers и r_fingers могат да се свият целите пръсти (пример на живо):

figure.r_fingers.bend = angle;

Отделните пръсти са номерирани от палец (0) до кутре (4). Пръстите поддържат свойствата bend, straddle и turn. Средната фаланга на пръст е mid, а крайната фаланга, върхът на пръста, е tip. Свойствата mid и tip на пръст поддтржат само bend (пример на живо и пример на живо).

figure.r_finger_1.straddle = alpha;
figure.r_finger_1.bend = beta1;
figure.r_finger_1.mid.bend = beta2;
figure.r_finger_1.tip.bend = beta3;

Долни крайници

Долните крайници са симетрични части на тялото: крак leg, коляно knee и глезен ankle.

И двата крака legs поддържат свойствата raise, straddle и turn (пример на живо). Разкрачването straddle и завъртането turn са симетрични.

figure.r_leg.raise = angle;
figure.r_leg.straddle = angle;
figure.r_leg.turn = angle;

Движението на коляното knee е само bend (пример на живо). Отрицателни стойности на angle водят до неестествена поза на коляното.

figure.r_knee.bend = angle;

Глезените ankles имат същите свойства като китките: bend, turn и tilt (пример на живо):

figure.r_ankle.bend = angle;
figure.r_ankle.turn = angle;
figure.r_ankle.tilt = angle;

Поза на тялото

Позата на фигурата се определя чрез задаване на стойности на ротационните свойства на частите на тялото. Редът на завъртанията е важен, т.е. промяната на реда на завъртане води до различен резултат. Следващият пример показва навеждане на 45°, завъртане на 90° и накланяне встрани на 60° на три фигури. Тъй като редът на завъртане е различен за всяка фигура, крайните им пози също са различни (пример на живо):

man.torso.bend += 45;
man.torso.turn += 90;
man.torso.tilt += 60;

child.torso.tilt += 60;
child.torso.bend += 45;
child.torso.turn += 90;

woman.torso.turn += 90;
woman.torso.bend += 45;
woman.torso.tilt += 60;

Статична поза

Статичната поза определя позицията на част от тялото, която не се променя. Когато се създава фигура, частите на тялото ѝ заемат вградената поза по подразбиране. Ако не се изолзва редактор на поза, всички ротации трябва да бъдат дефинирани програмно (пример на живо):

Понякога е по-добре да се дефинира фигура стъпка по стъпка. Позата “Тай Чи Чуан”, показана по-горе, може да започне със задаване на позицията на цялото тяло:

// обща поза на тялото
man.position.y -= 11;
man.body.tilt = -5;
:
// торс и глава
man.torso.turn -= 30;
man.head.turn -= 70;:

След това може да се зададе ориентацията на краката:

// десен крак
man.r_leg.turn = 50;
man.r_knee.bend = 90;
man.r_ankle.bend = 15;
:
// ляв крак
man.l_leg.raise = -20;
man.l_knee.bend = 30;
man.l_ankle.bend = 42;
:

Накрая и ръцете се нагласяват:

// лява ръка
man.l_arm.straddle = 70;
man.l_elbow.bend = 155;
man.l_wrist.bend = -20;
:
// дясна ръка
man.r_arm.straddle += 70;
man.r_elbow.bend += 40;
man.r_wrist.turn -= 60;
:

Динамична поза

Динамичната поза – т.е. поза, която се променя с времето – се задава със същите свойства, които се използват за статична поза. Mannequin.js дефинира празна функция animate(t), която се извиква в цикъла на анимацията веднъж за всеки кадър. Всички промени в позата трябва да бъдат дефинирани в предефиниция на тази функция (пример на живо). Параметърът t е времето, измерено в десети от секундата. Тази функция е дефинирана в createScene(). Ако не се ползват createScene и animate, тогава цикълът на анимацията трябва да се управлява ръчно.

function animate(t)
{
    var time1 = (sin(2*t)+cos(3*t)+cos(5*t))/3,
        time2 = (sin(2*t-60)+cos(3*t-90)+cos(5*t-120))/3;
	
    ball.position.x = -3*time1;
	
    child.position.x = -3*time1;
    child.position.y = 4.2+cos(90*time1);

    child.turn = -90-20*time1+20*time2;
    child.tilt = 10*time1;
    :
	
    scene.rotation.y = time1/2;
}

За да се направи цикълът на анимацията по-бърз, всички фиксирани ротации трябва да бъдат дефинирани извън animate. Освен това, ако въртенето се променя в цикъла, няма нужда да се дават първоначални стойности извън цикъла.

Работа с пози

Позата може да бъде извлечена от фигура чрез свойството posture. То съдържа обект с елементи version за версията на формата на данните за позата и data – вложен масив с ъглите на завъртане на ставите. Свойството posture може да се използва и за задаване на поза на фигура.

{ "version":5,
  "data": [ [90,-85,74.8], [16.1,-29.5,26.3], [3.5,-34.8,6.1],
            [14.1,-2.9,-19.8], [30], [-6,-6,-42.6], [14.6,-46.9,98.9],
			[90], [4.9,9,-15.4], [68.9,-34.7,-2.1], [155], [-20,0,0],
			[-10,-10], [-77,4.9,-1.1], [55], [15,-60,-20], [100,100]
		  ]
}

Има алтернативно свойство postureString, с което се извлича или задава поза като текстов низ. Преобразуването на позата към и от текстов низ се прави с JSON.stringify и JSON.parse.

Позите могат да бъдат сливани чрез ойлерова интерполация (т.е. линейна интерполация на ойлерови ъгли). Методът на класа blend(posture0,posture1,k) слива първоначалната поза posture0 и крайната поза posture1 с коефициент k∈[0,1]. Когато k=0 резултатът е поза posture0, когато k=1 резултатът е поза posture1, когато k е между 0 и 1 резултатът е междинна поза между posture0 и posture1. Следващият пример слива позата на една фигура и я копира в друга фигура (пример на живо 1 и пример на живо 2):

// две фигури
man = new Male();
woman = new Female();

// две пози
A = {"version":5,"data":[[90,-85,74.8],...]};
B = {"version":5,"data":[[0,-90,0],...]};

// задаване на междинна поза
man.posture = Mannequin.blend(A,B,0.5);

// копиране на позата в друга фигура
woman.posture = man.posture;

Редактор на поза

Предстои да бъде описан.

Други функционалности

Освен за движение на части на тялото, текущата версия на mannequin.js предоставя основна функционалност за допълнителни промени по фигурата.

Собствени цветове

По подразбиране всички фигури използват предварително дефиниран набор от глобални цветове за частите на тялото. Глобалните цветове се съхраняват в масива Mannequin.colors като шест Three.js цвята или HTML/CSS имена на цветове в определен ред – глава, обувки, таз, стави, крайници и торс:

Mannequin.colors = [
    'antiquewhite',	// глава
    'gray',		// обувки
    'antiquewhite',	// таз
    'burlywood',	// стави
    'antiquewhite',	// крайници
    'bisque'		// торс
];

Глобалният цвят на ставите и крайниците се отнася до всички стави и всички крайници. Промяната на глобалните цветове в Mannequin.colors има ефект, ако е направена преди създаването на фигури. Цветовете на частите от тялото могат да се променят индивидуално чрез метода recolor (пример на живо):

// глобални цветове
Mannequin.colors = [ 'lightgreen', 'black', 'black', 'white', 'darkolivegreen', 'darkslategray'];

man = new Male();
:
// индивидуални цветове
man.l_elbow.recolor( 'yellow', 'black' );
man.l_wrist.recolor( 'orange' );
man.l_fingers.recolor( 'coral' );
man.l_fingers.tips.recolor( 'maroon' );
man.r_knee.recolor( 'antiquewhite', 'black' );

Първият параметър на recolor е цветът на основния елемент на частта от тялото. Вторият параметър е цветът на сферичния елемент (ако има такъв).

Достъпът до върховете на пръстите се осъществява чрез l_fingers.tips и r_fingers.tips.

Скриване на части от тялото

Всяка част от тялото може да бъде скрита. Това не премахва нея и нейния графичен образ от фигурата, а просто не я рисува. Методът за скриване е:

figure.joint.hide();

където joint е името на частта от тялото, която да се скрие. Скритите части на тялото могат да се въртят и това се отразява на частите на тялото, прикрепени към тях. Следващият пример скрива двете ръце и двата крака, но те все още същестуват и се използват от лактите и коленете (пример на живо):

man.l_leg.hide();
man.r_leg.hide();
man.l_arm.hide();
man.r_arm.hide();

Собствени части на тяло

Частите на тялото са наследници на класа THREE.Object3D и поддържат неговите свойства и методи. Въпреки това, поради конструкцията на скелета и свързването на ставите, мащабирането на част от тялото трябва да е еднакво по всички оси, в противен случай позате трябва да бъде ръчно коригирана (пример на живо):

man = new Male();

man.head.scale.set(3,3,3);

man.l_arm.scale.set(1/2,1/2,1/2);
man.r_arm.scale.set(1/2,1/2,1/2);

man.l_wrist.scale.set(3,5,3);
man.r_wrist.scale.set(3,5,3);

Всеки THREE.Object3D или негов наследник може да бъде прикрепен към част от тялото. Прикрепеният обект е включен в тялото и прави всяко движение, което тялото извършва:

figure.joint.attach(object);

Обектите могат да бъдат прикрепени към скрити части на тялото, но те не се скриват автоматично. Този подход се използва за замяна на част от тялото с изцяло собствен потребителски обект (пример на живо):

man = new Male();

// добавяне на гривни
bracelet = new THREE.Mesh(
    new THREE.CylinderGeometry(3,3,1,16),	
    new THREE.MeshPhongMaterial({color:'crimson',shininess:200})
);
bracelet.castShadow = true;
bracelet.position.y = 6;
man.l_elbow.attach(bracelet);

bracelet = bracelet.clone();
man.r_elbow.attach(bracelet);


// замяна на крака с други обекти
man.r_leg.hide();

material = new THREE.MeshPhongMaterial({color:'crimson',shininess:200});

obj = new THREE.Mesh(new THREE.CylinderGeometry(3,2,3,32), material);
obj.castShadow = true;
obj.position.y = 2;
man.r_leg.attach(obj);

Глобална позиция

Не всяко взаимодействие на фигури с други обекти може да се осъществи чрез прикачване. Mannequin.js предоставя метод point(x,y,z) за всяка част от тялото. Този метод прилага права кинематика и изчислява глобалните координати на точката (x,y,z), дефинирана в локалната координатна система на частта от тялото.

Следващият пример създава въже, преминаващо през 5 точки от частите на тялото на фигура (пример на живо):

setLoopVertex( 0, man.r_fingers.tips.point(0,1,0) );
setLoopVertex( 1, man.head.point(3,1.2,0) );
setLoopVertex( 2, man.l_fingers.tips.point(0,1,0) );
setLoopVertex( 3, man.l_ankle.point(6,2,0) );
setLoopVertex( 4, man.r_ankle.point(6,2,0) );

Глобалните позиции могат да се използват за поставяне на фигури към земята. Въпреки това, mannequin.js не съдържа никаква функционалност за докосване, така че потребителят трябва да избере точки на контакт и да използва техните глобални позиции.

Следващият пример използва четири контактни точки на всяка обувка (т.е. man.r_ankle и man.l_ankle). Контактните точки на лявата обувка са показани като червени точки. Минималното вертикално положение на осемте контактни точки се използва за регулиране на вертикалното положение на фигурата (пример на живо):

// изчисляване на минималното вертикално отклонение на контактните точки
bottom = Math.min(
    man.l_ankle.point(6,2,0).y,
    man.l_ankle.point(-2,2.5,0).y,
    man.l_ankle.point(2,2.5,2).y,
    man.l_ankle.point(2,2.5,-2).y,

    man.r_ankle.point(6,2,0).y,
    man.r_ankle.point(-2,2.5,0).y,
    man.r_ankle.point(2,2.5,2).y,
    man.r_ankle.point(2,2.5,-2).y
);

man.position.y += (-29.5-bottom);

Общност

Списък от сайтове, които използват mannequin.js:


Януари, 2023