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_fingers_0
до l_fingers_4
и r_fingers_0
до r_fingers_0
).
Множествата могат само да се свиват с bend. Свиването на пръстите автоматично свива и техните средни фаланги и върхове, така че с l_fingers
и r_fingers
могат да се свият целите пръсти (пример на живо):
figure.r_fingers.bend = angle;
Отделните пръсти са номерирани от палец (0) до кутре (4). Пръстите поддържат свойствата bend
, straddle
и turn
. Средната фаланга на пръст е mid
, а крайната фаланга, върхът на пръста, е tip
. Свойствата mid
и tip
на пръст поддтржат само bend
(пример на живо и пример на живо).
figure.r_fingers_1.straddle = alpha;
figure.r_fingers_1.bend = beta1;
figure.r_fingers_1.mid.bend = beta2;
figure.r_fingers_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