diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js
index d84de114fb5d7aa8a99d37bdf2cb214a4b6f891b..fe83336aefa8f3e7774b8a48e24bfca3c33d5a69 100644
--- a/src/art/webgl/art.js
+++ b/src/art/webgl/art.js
@@ -636,7 +636,6 @@ App.Art.applyFigures = function(slave, scene, p) {
 		p.hideDick = true;
 	}
 
-	/*
 	switch (slave.vaginalAccessory) {
 		case "dildo":
 			figures.push("Dildo 1");
@@ -663,7 +662,6 @@ App.Art.applyFigures = function(slave, scene, p) {
 			figures.push("Anal Plug 3");
 			break;
 	}
-	*/
 
 	if (slave.chastityAnus || slave.chastityVagina || slave.chastityPenis) {
 		figures.push("Chastity Belt Base");
@@ -680,12 +678,28 @@ App.Art.applyFigures = function(slave, scene, p) {
 		}
 	}
 
+	if (slave.piercing.ear.weight === 1) { figures.push("Light Piercing Left Ear", "Light Piercing Left Ear"); }
+	if (slave.piercing.nose.weight === 1) { figures.push("Light Piercing Left Nose"); }
+	if (slave.piercing.nose.weight === 2) { figures.push("Heavy Piercing Nose"); }
+	if (slave.piercing.eyebrow.weight === 1) { figures.push("Light Piercing Left Eyebrow"); }
+	if (slave.piercing.lips.weight === 1) { figures.push("Light Piercing Right Lips"); }
+	if (slave.piercing.navel.weight === 1) { figures.push("Light Piercing Belly"); }
+	if (slave.piercing.areola.weight === 1) { figures.push("Light Piercing Left Areola"); }
+	if (slave.piercing.nipple.weight === 1) { figures.push("Light Piercing Left Nipple", "Light Piercing Right Nipple"); }
+	if (slave.piercing.nipple.weight === 2) { figures.push("Heavy Piercing Left Nipple", "Heavy Piercing Right Nipple"); }
+
 	switch (slave.bellyAccessory) {
 		// case "a corset": figures.push("Corset A_80240"); break;
 		// case "an extreme corset": figures.push("corset1_112951"); break;
 		default: break;
 	}
 
+	switch (slave.legAccessory) {
+		case "long stockings":
+			figures.push("Long Stockings");
+			break;
+	}
+
 	switch (slave.faceAccessory) {
 		case "porcelain mask":
 			figures.push("Porcelain Mask");
@@ -733,43 +747,84 @@ App.Art.applyFigures = function(slave, scene, p) {
 	}
 
 	if (!p.hideHair) {
-		switch (slave.hStyle) {
-			case "afro": figures.push("Yara Hair"); break;
-			case "cornrows": figures.push("HR TIGER BRAIDS G2F"); break;
-			case "bun": figures.push("Adia Hair"); break;
-			case "neat": figures.push("Samira Hair"); break;
-			case "strip": figures.push("Rebel Hair"); break;
-			case "tails": figures.push("Kinley Hair G8"); break;
-			case "up": figures.push("Pina Hair G8F"); break;
-			case "ponytail": figures.push("Ponytail"); break;
-			case "braided": figures.push("LLF-MishkaGeBase1"); break;
-			case "dreadlocks": figures.push("Dreads"); break;
-			case "permed": figures.push("IchigoHair"); break;
-			case "curled": figures.push("Havana Hair"); break;
-			case "luxurious": figures.push("BaronessHR"); break;
-			case "messy bun": figures.push("Krayon Hair"); break;
-			case "messy": figures.push("MessyHairG3"); break;
-			case "eary": figures.push("GeorginaHair"); break;
-			case "undercut": figures.push("Edit Female Hair"); break;
-			case "bangs": figures.push("Neko Hair Genesis 8 Female"); break;
-			case "hime": figures.push("Nyohair"); break;
-			case "drills": figures.push("LLF-BunnyCurls-G3"); break;
-			case "bald": break;
-			case "shaved": break;
-			case "buzzcut": break;
-			case "trimmed": break;
-
-			// not implemented
-			case "double buns": figures.push("Krayon Hair"); break;
-			case "chignon": figures.push("BaronessHR"); break;
-			case "french twist": figures.push("HR TIGER BRAIDS G2F"); break;
-			case "crown braid": figures.push("HR TIGER BRAIDS G2F"); break;
-			case "dutch braid": figures.push("HR TIGER BRAIDS G2F"); break;
-			case "double dutch braid": figures.push("HR TIGER BRAIDS G2F"); break;
-			case "pixie cut": break;
-			case "bob cut": break;
-
-			default: break;
+		if (slave.hLength < 50) {
+			switch (slave.hStyle) {
+				case "afro": figures.push("Yara Hair"); break;
+				case "cornrows": figures.push("Lush"); break;
+				case "bun": figures.push("Adia Hair"); break;
+				case "neat": figures.push("Samira Hair"); break;
+				case "strip": figures.push("Rebel Hair"); break;
+				case "tails": figures.push("Kinley Hair G8"); break;
+				case "up": figures.push("Pina Hair G8F"); break;
+				case "ponytail": figures.push("Ponytail"); break;
+				case "braided": figures.push("LLF-MishkaGeBase1"); break;
+				case "dreadlocks": figures.push("Dreads"); break;
+				case "permed": figures.push("IchigoHair"); break;
+				case "curled": figures.push("Havana Hair"); break;
+				case "luxurious": figures.push("Rose59"); break;
+				case "messy bun": figures.push("Krayon Hair"); break;
+				case "messy": figures.push("MessyHairG3"); break;
+				case "eary": figures.push("GeorginaHair"); break;
+				case "undercut": figures.push("Edit Female Hair"); break;
+				case "bangs": figures.push("Neko Hair Genesis 8 Female"); break;
+				case "hime": figures.push("Nyohair"); break;
+				case "drills": figures.push("LLF-BunnyCurls-G3"); break;
+				case "bald": break;
+				case "shaved": break;
+				case "buzzcut": break;
+				case "trimmed": break;
+
+				// not implemented
+				case "double buns": figures.push("Gaze"); break;
+				case "chignon": figures.push("BaronessHR"); break;
+				case "french twist": figures.push("HR TIGER BRAIDS G2F"); break;
+				case "crown braid": figures.push("Sky293"); break;
+				case "dutch braid": figures.push("Carrousel"); break;
+				case "double dutch braid": figures.push("HR TIGER BRAIDS G2F"); break;
+				case "pixie cut": break;
+				case "bob cut": break;
+
+				default: break;
+			}
+		} else {
+			switch (slave.hStyle) {
+				case "afro": figures.push("Yara Hair"); break;
+				case "cornrows": figures.push("Lush"); break;
+				case "bun": figures.push("Sky158"); break;
+				case "neat": figures.push("Samira Hair"); break;
+				case "strip": figures.push("Wanda"); break;
+				case "tails": figures.push("Suckerpunch"); break;
+				case "up": figures.push("Pina Hair G8F"); break;
+				case "ponytail": figures.push("Paraguay"); break;
+				case "braided": figures.push("Butterfly160"); break;
+				case "dreadlocks": figures.push("Sparks"); break;
+				case "permed": figures.push("Nightwish"); break;
+				case "curled": figures.push("Eyesonme"); break;
+				case "luxurious": figures.push("Rose59"); break;
+				case "messy bun": figures.push("Alice"); break;
+				case "messy": figures.push("MessyHairG3"); break;
+				case "eary": figures.push("GeorginaHair"); break;
+				case "undercut": figures.push("Roulette"); break;
+				case "bangs": figures.push("Neko Hair Genesis 8 Female"); break;
+				case "hime": figures.push("Peak"); break;
+				case "drills": figures.push("Didar"); break;
+				case "bald": break;
+				case "shaved": break;
+				case "buzzcut": break;
+				case "trimmed": break;
+
+				// not implemented
+				case "double buns": figures.push("Gaze"); break;
+				case "chignon": figures.push("BaronessHR"); break;
+				case "french twist": figures.push("HR TIGER BRAIDS G2F"); break;
+				case "crown braid": figures.push("Sky293"); break;
+				case "dutch braid": figures.push("Carrousel"); break;
+				case "double dutch braid": figures.push("HR TIGER BRAIDS G2F"); break;
+				case "pixie cut": break;
+				case "bob cut": break;
+
+				default: break;
+			}
 		}
 	}
 
@@ -1300,7 +1355,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 
 	let makeupColor;
 	let makeupOpacity;
-	let lipsGloss = 1.49;
+	let lipsGloss = 2.5;
 	let lipsRough = 0.45;
 	let lipsMetal = 0;
 
@@ -1359,9 +1414,6 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	}
 
 	makeupColor = App.Art.hexToRgb(makeupColor);
-	lipsColor[0] = makeupColor[0] * makeupOpacity + lipsColor[0] * (1 - makeupOpacity);
-	lipsColor[1] = makeupColor[1] * makeupOpacity + lipsColor[1] * (1 - makeupOpacity);
-	lipsColor[2] = makeupColor[2] * makeupOpacity + lipsColor[2] * (1 - makeupOpacity);
 
 	let nailColor;
 	switch (slave.nails) {
@@ -1400,112 +1452,231 @@ App.Art.applyMaterials = function(slave, scene, p) {
 
 	nailColor = App.Art.hexToRgb(nailColor);
 
-	switch (slave.hStyle) {
-		case "afro":
-			materials.push(["yara_scalp", "Ka", hairColor]);
-			materials.push(["yara_hair", "Ka", hairColor]);
-			break;
-		case "cornrows":
-			materials.push(["tiger_scalp", "Ka", hairColor]);
-			materials.push(["tiger_hair", "Ka", hairColor]);
-			break;
-		case "bun":
-			materials.push(["adia_scalp", "Ka", hairColor]);
-			materials.push(["adia_hair", "Ka", hairColor]);
-			break;
-		case "neat":
-			materials.push(["samira_scalp", "Ka", hairColor]);
-			materials.push(["samira_hair", "Ka", hairColor]);
-			break;
-		case "strip":
-			materials.push(["rebel_scalp", "Ka", hairColor]);
-			materials.push(["rebel_hair", "Ka", hairColor]);
-			break;
-		case "tails":
-			materials.push(["kinley_scalp", "Ka", hairColor]);
-			materials.push(["kinley_hair_thin_strands", "Ka", hairColor]);
-			materials.push(["kinley_hair_long", "Ka", hairColor]);
-			materials.push(["kinley_hair_strands", "Ka", hairColor]);
-			materials.push(["kinley_hair_base", "Ka", hairColor]);
-			materials.push(["kinley_hair_tie", "Ka", hairColor]);
-			break;
-		case "up":
-			materials.push(["pina_scalp", "Ka", hairColor]);
-			materials.push(["pina_hair1", "Ka", hairColor]);
-			materials.push(["pina_hair2", "Ka", hairColor]);
-			break;
-		case "ponytail":
-			materials.push(["ponytail_scalp", "Ka", hairColor]);
-			materials.push(["ponytail_hair1", "Ka", hairColor]);
-			materials.push(["ponytail_hair2", "Ka", hairColor]);
-			materials.push(["ponytail_hair3", "Ka", hairColor]);
-			materials.push(["ponytail_holder", "Ka", hairColor]);
-			break;
-		case "braided":
-			materials.push(["mishka_scalp", "Ka", hairColor]);
-			materials.push(["mishka_hair1", "Ka", hairColor]);
-			materials.push(["mishka_hair2", "Ka", hairColor]);
-			materials.push(["mishka_hair3", "Ka", hairColor]);
-			break;
-		case "dreadlocks":
-			materials.push(["dreads_scalp", "Ka", hairColor]);
-			materials.push(["dreads_hair", "Ka", hairColor]);
-			break;
-		case "permed":
-			materials.push(["ichigo_scalp", "Ka", hairColor]);
-			materials.push(["ichigo_hair1", "Ka", hairColor]);
-			materials.push(["ichigo_hair2", "Ka", hairColor]);
-			break;
-		case "curled":
-			materials.push(["havana_hair", "Ka", hairColor]);
-			break;
-		case "luxurious":
-			materials.push(["baroness_scalp", "Ka", hairColor]);
-			materials.push(["baroness_hair", "Ka", hairColor]);
-			break;
-		case "messy bun":
-			materials.push(["krayon_scalp", "Ka", hairColor]);
-			materials.push(["krayon_hair1", "Ka", hairColor]);
-			materials.push(["krayon_hair2", "Ka", hairColor]);
-			materials.push(["krayon_hair3", "Ka", hairColor]);
-			materials.push(["krayon_hair4", "Ka", hairColor]);
-			break;
-		case "messy":
-			materials.push(["messy_scalp", "Ka", hairColor]);
-			materials.push(["messy_hair", "Ka", hairColor]);
-			break;
-		case "eary":
-			materials.push(["georgina_scalp", "Ka", hairColor]);
-			materials.push(["georgina_hair1", "Ka", hairColor]);
-			materials.push(["georgina_hair2", "Ka", hairColor]);
-			break;
-		case "undercut":
-			materials.push(["edit_scalp", "Ka", hairColor]);
-			materials.push(["edit_hair", "Ka", hairColor]);
-			break;
-		case "bangs":
-			materials.push(["neko_scalp", "Ka", hairColor]);
-			materials.push(["neko_hair", "Ka", hairColor]);
-			break;
-		case "hime":
-			materials.push(["nyo_scalp", "Ka", hairColor]);
-			materials.push(["nyo_hair", "Ka", hairColor]);
-			break;
-		case "drills":
-			materials.push(["bunny_scalp", "Ka", hairColor]);
-			materials.push(["bunny_hair1", "Ka", hairColor]);
-			materials.push(["bunny_hair2", "Ka", hairColor]);
-			materials.push(["bunny_hair3", "Ka", hairColor]);
-			materials.push(["bunny_hair4", "Ka", hairColor]);
-			break;
-		case "buzzcut":
-		case "trimmed":
-			materials.push(["shaved_face", "Ka", hairColor]);
-			materials.push(["shaved_torso", "Ka", hairColor]);
-			break;
-		case "bald":
-		case "shaved":
-		default: break;
+	if (slave.hLength < 50) {
+		switch (slave.hStyle) {
+			case "afro":
+				materials.push(["yara_scalp", "Ka", hairColor]);
+				materials.push(["yara_hair", "Ka", hairColor]);
+				break;
+			case "cornrows":
+				materials.push(["lush_s4studio_mesh_4", "Ka", hairColor]);
+				break;
+			case "bun":
+				materials.push(["adia_scalp", "Ka", hairColor]);
+				materials.push(["adia_hair", "Ka", hairColor]);
+				break;
+			case "neat":
+				materials.push(["samira_scalp", "Ka", hairColor]);
+				materials.push(["samira_hair", "Ka", hairColor]);
+				break;
+			case "strip":
+				materials.push(["rebel_scalp", "Ka", hairColor]);
+				materials.push(["rebel_hair", "Ka", hairColor]);
+				break;
+			case "tails":
+				materials.push(["kinley_scalp", "Ka", hairColor]);
+				materials.push(["kinley_hair_thin_strands", "Ka", hairColor]);
+				materials.push(["kinley_hair_long", "Ka", hairColor]);
+				materials.push(["kinley_hair_strands", "Ka", hairColor]);
+				materials.push(["kinley_hair_base", "Ka", hairColor]);
+				materials.push(["kinley_hair_tie", "Ka", hairColor]);
+				break;
+			case "up":
+				materials.push(["pina_scalp", "Ka", hairColor]);
+				materials.push(["pina_hair1", "Ka", hairColor]);
+				materials.push(["pina_hair2", "Ka", hairColor]);
+				break;
+			case "ponytail":
+				materials.push(["ponytail_scalp", "Ka", hairColor]);
+				materials.push(["ponytail_hair1", "Ka", hairColor]);
+				materials.push(["ponytail_hair2", "Ka", hairColor]);
+				materials.push(["ponytail_hair3", "Ka", hairColor]);
+				materials.push(["ponytail_holder", "Ka", hairColor]);
+				break;
+			case "braided":
+				materials.push(["mishka_scalp", "Ka", hairColor]);
+				materials.push(["mishka_hair1", "Ka", hairColor]);
+				materials.push(["mishka_hair2", "Ka", hairColor]);
+				materials.push(["mishka_hair3", "Ka", hairColor]);
+				break;
+			case "dreadlocks":
+				materials.push(["dreads_scalp", "Ka", hairColor]);
+				materials.push(["dreads_hair", "Ka", hairColor]);
+				break;
+			case "permed":
+				materials.push(["ichigo_scalp", "Ka", hairColor]);
+				materials.push(["ichigo_hair1", "Ka", hairColor]);
+				materials.push(["ichigo_hair2", "Ka", hairColor]);
+				break;
+			case "curled":
+				materials.push(["havana_hair", "Ka", hairColor]);
+				break;
+			case "luxurious":
+				materials.push(["rose59_geom_00", "Ka", hairColor]);
+				break;
+			case "messy bun":
+				materials.push(["krayon_scalp", "Ka", hairColor]);
+				materials.push(["krayon_hair1", "Ka", hairColor]);
+				materials.push(["krayon_hair2", "Ka", hairColor]);
+				materials.push(["krayon_hair3", "Ka", hairColor]);
+				materials.push(["krayon_hair4", "Ka", hairColor]);
+				break;
+			case "messy":
+				materials.push(["messy_scalp", "Ka", hairColor]);
+				materials.push(["messy_hair", "Ka", hairColor]);
+				break;
+			case "eary":
+				materials.push(["georgina_scalp", "Ka", hairColor]);
+				materials.push(["georgina_hair1", "Ka", hairColor]);
+				materials.push(["georgina_hair2", "Ka", hairColor]);
+				break;
+			case "undercut":
+				materials.push(["edit_scalp", "Ka", hairColor]);
+				materials.push(["edit_hair", "Ka", hairColor]);
+				break;
+			case "bangs":
+				materials.push(["neko_scalp", "Ka", hairColor]);
+				materials.push(["neko_hair", "Ka", hairColor]);
+				break;
+			case "hime":
+				materials.push(["nyo_scalp", "Ka", hairColor]);
+				materials.push(["nyo_hair", "Ka", hairColor]);
+				break;
+			case "drills":
+				materials.push(["bunny_scalp", "Ka", hairColor]);
+				materials.push(["bunny_hair1", "Ka", hairColor]);
+				materials.push(["bunny_hair2", "Ka", hairColor]);
+				materials.push(["bunny_hair3", "Ka", hairColor]);
+				materials.push(["bunny_hair4", "Ka", hairColor]);
+				break;
+			case "french twist":
+				materials.push(["tiger_scalp", "Ka", hairColor]);
+				materials.push(["tiger_hair", "Ka", hairColor]);
+				break;
+			case "chignon":
+				materials.push(["baroness_scalp", "Ka", hairColor]);
+				materials.push(["baroness_hair", "Ka", hairColor]);
+				break;
+			case "crown braid":
+				materials.push(["sky293_s4studio_mesh_2", "Ka", hairColor]);
+				break;
+			case "double dutch braid":
+				materials.push(["tiger_scalp", "Ka", hairColor]);
+				materials.push(["tiger_hair", "Ka", hairColor]);
+				break;
+			case "double buns":
+				materials.push(["gaze_s4studio_mesh_3", "Ka", hairColor]);
+				break;
+			case "dutch braid":
+				materials.push(["carrousel_dicksquad", "Ka", hairColor]);
+				break;
+			case "buzzcut":
+			case "trimmed":
+				materials.push(["shaved_face", "Ka", hairColor]);
+				materials.push(["shaved_torso", "Ka", hairColor]);
+				break;
+			case "bald":
+			case "shaved":
+			default: break;
+		}
+	} else {
+		switch (slave.hStyle) {
+			case "afro":
+				materials.push(["yara_scalp", "Ka", hairColor]);
+				materials.push(["yara_hair", "Ka", hairColor]);
+				break;
+			case "cornrows":
+				materials.push(["lush_s4studio_mesh_4", "Ka", hairColor]);
+				break;
+			case "bun":
+				materials.push(["sky158_groupname", "Ka", hairColor]);
+				break;
+			case "neat":
+				materials.push(["samira_scalp", "Ka", hairColor]);
+				materials.push(["samira_hair", "Ka", hairColor]);
+				break;
+			case "strip":
+				materials.push(["wanda_groupname", "Ka", hairColor]);
+				break;
+			case "tails":
+				materials.push(["suckerpunch_hair", "Ka", hairColor]);
+				break;
+			case "up":
+				materials.push(["inkstone_groupname", "Ka", hairColor]);
+				break;
+			case "ponytail":
+				materials.push(["paraguay_group_1", "Ka", hairColor]);
+				break;
+			case "braided":
+				materials.push(["butterfly160_s4studio_mesh_1", "Ka", hairColor]);
+				break;
+			case "dreadlocks":
+				materials.push(["sparks_s4studio_mesh_2", "Ka", hairColor]);
+				break;
+			case "permed":
+				materials.push(["nightwish_group_1", "Ka", hairColor]);
+				break;
+			case "curled":
+				materials.push(["eyesonme_hair", "Ka", hairColor]);
+				break;
+			case "luxurious":
+				materials.push(["rose59_geom_00", "Ka", hairColor]);
+				break;
+			case "messy bun":
+				materials.push(["alice_group_1", "Ka", hairColor]);
+				break;
+			case "messy":
+				materials.push(["messy_scalp", "Ka", hairColor]);
+				materials.push(["messy_hair", "Ka", hairColor]);
+				break;
+			case "eary":
+				materials.push(["georgina_scalp", "Ka", hairColor]);
+				materials.push(["georgina_hair1", "Ka", hairColor]);
+				materials.push(["georgina_hair2", "Ka", hairColor]);
+				break;
+			case "undercut":
+				materials.push(["roulette_roulette", "Ka", hairColor]);
+				break;
+			case "bangs":
+				materials.push(["neko_scalp", "Ka", hairColor]);
+				materials.push(["neko_hair", "Ka", hairColor]);
+				break;
+			case "hime":
+				materials.push(["peak_group_1", "Ka", hairColor]);
+				break;
+			case "drills":
+				materials.push(["didar_didar", "Ka", hairColor]);
+				break;
+			case "french twist":
+				materials.push(["tiger_scalp", "Ka", hairColor]);
+				materials.push(["tiger_hair", "Ka", hairColor]);
+				break;
+			case "chignon":
+				materials.push(["baroness_scalp", "Ka", hairColor]);
+				materials.push(["baroness_hair", "Ka", hairColor]);
+				break;
+			case "crown braid":
+				materials.push(["sky293_s4studio_mesh_2", "Ka", hairColor]);
+				break;
+			case "double dutch braid":
+				materials.push(["tiger_scalp", "Ka", hairColor]);
+				materials.push(["tiger_hair", "Ka", hairColor]);
+				break;
+			case "double buns":
+				materials.push(["gaze_s4studio_mesh_3", "Ka", hairColor]);
+				break;
+			case "dutch braid":
+				materials.push(["carrousel_dicksquad", "Ka", hairColor]);
+				break;
+			case "buzzcut":
+			case "trimmed":
+				materials.push(["shaved_face", "Ka", hairColor]);
+				materials.push(["shaved_torso", "Ka", hairColor]);
+				break;
+			case "bald":
+			case "shaved":
+			default: break;
+		}
 	}
 
 	let eyebrowColor = App.Art.hexToRgb(extractColor(slave.eyebrowHColor));
@@ -1537,29 +1708,19 @@ App.Art.applyMaterials = function(slave, scene, p) {
 			break;
 	}
 
-	if (slave.face < -66) {
-		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_0.jpg"]);
-	} else if (slave.face < -33) {
-		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_1.jpg"]);
-	} else if (slave.face < 0) {
-		materials.push(["Eyelashes", "map_D", "base/G8FBaseEyelashes_1006.jpg"]);
-	} else if (slave.face < 33) {
-		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_2.jpg"]);
-	} else if (slave.face < 66) {
-		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_3.jpg"]);
-	} else {
-		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_4.jpg"]);
-	}
+	let eyelashes = ["base2/eyelash/EyeLash_2.jpg", "base2/eyelash/EyeLash_5.jpg", "base/G8FBaseEyelashes_1006.jpg"];
+	let eyelash = Math.floor(App.Art.random() * eyelashes.length);
+	materials.push(["Eyelashes", "map_D", eyelashes[eyelash]]);
 
 	let irisColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.iris) : extractColor("black")));
 	let irisColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.iris) : extractColor("black")));
 	let scleraColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.sclera) : extractColor("black")));
 	let scleraColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.sclera) : extractColor("black")));
 
-	materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*1.4*2.5, irisColorLeft[1]*1.4*2.5, irisColorLeft[2]*1.4*2.5]]);
-	materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4*2.5, irisColorRight[1]*1.4*2.5, irisColorRight[2]*1.4*2.5]]);
-	materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.8*1.8, scleraColorLeft[1]*0.8*2, scleraColorLeft[2]*0.8*2]]);
-	materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.8*1.8, scleraColorRight[1]*0.8*2, scleraColorRight[2]*0.8*2]]);
+	materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*2.2, irisColorLeft[1]*2.2, irisColorLeft[2]*2.2]]);
+	materials.push(["Iris_Right", "Ka", [irisColorRight[0]*2.2, irisColorRight[1]*2.2, irisColorRight[2]*2.2]]);
+	materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*1.4, scleraColorLeft[1]*1.4, scleraColorLeft[2]*1.4]]);
+	materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*1.4, scleraColorRight[1]*1.4, scleraColorRight[2]*1.4]]);
 
 	// expected skin color
 	let O = App.Art.hexToRgb(skinColorCatcher(slave).skinColor);
@@ -1569,6 +1730,10 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	lipsColor = [O[0]*0.76*lbrf, O[1]*0.55*lbrf, O[2]*0.6*lbrf];
 	areolaColor = lipsColor;
 
+	lipsColor[0] = makeupColor[0] * makeupOpacity + lipsColor[0] * (1 - makeupOpacity);
+	lipsColor[1] = makeupColor[1] * makeupOpacity + lipsColor[1] * (1 - makeupOpacity);
+	lipsColor[2] = makeupColor[2] * makeupOpacity + lipsColor[2] * (1 - makeupOpacity);
+
 	// average skin texture color
 	let oSkinColors = {
 		"Tara": 		[194/255, 148/255, 124/255],
diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js
index a89db2b1a5df24998f3057ee8e93f5e4f8ddae34..68315d1b2645db9f37c5a17e33f3ad24d9ec22b4 100644
--- a/src/art/webgl/engine.js
+++ b/src/art/webgl/engine.js
@@ -36,19 +36,24 @@ App.Art.Engine = class {
 				layout(location = 1) out vec3 gNormal;
 
 				uniform sampler2D alpha;
+				uniform float d;
 
 				in vec2 textureCoord;
 				in vec3 pos;
 				in vec3 normal;
 
 				void main() {
-					float map_d = texture(alpha, textureCoord).r;
+					float map_d = d * texture(alpha, textureCoord).r;
 
 					if (map_d < 0.85)
 						discard;
 					
 					gPosition = pos;
-					gNormal = normalize(normal);
+
+					if (!gl_FrontFacing)
+						gNormal = -normalize(normal);
+					else
+						gNormal = normalize(normal);
 				}`;
 	}
 
@@ -73,11 +78,12 @@ App.Art.Engine = class {
 				precision highp float;
 
 				uniform sampler2D alpha;
+				uniform float d;
 				in vec2 textureCoord;
 				out float gShadowDepth;
 
 				void main() {
-					float map_d = texture(alpha, textureCoord).r;
+					float map_d = d * texture(alpha, textureCoord).r;
 
 					if (map_d < 0.85)
 						discard;
@@ -268,65 +274,66 @@ App.Art.Engine = class {
 
 	getFsSourceForwardPass(dl, pl) {
 		return `#version 300 es
-                precision highp float;
+		precision highp float;
                 
-                uniform float lightInt[${dl}];
-				uniform vec3 lightAmb[${dl}];
-                uniform vec3 lightColor[${dl}];
-                uniform vec3 lightVect[${dl}];
-
-                uniform float pointLightInt[${pl}];
-				uniform vec3 pointLightAmb[${pl}];
-                uniform vec3 pointLightColor[${pl}];
-                uniform vec3 pointLightPos[${pl}];
-
-				uniform vec3 shadowDir;
-				uniform float shadowBiasMin;
-				uniform float shadowBiasMax;
-				uniform float shadowIntensity;
-
-				uniform float sssPower;
-				uniform float sssDistortion;
-				uniform float sssIntensity;
-				uniform float sssAmbient;
-
-                uniform float whiteM;
-                uniform float gammaY;
-				uniform float uchimuraP;
-				uniform float uchimuraA;
-				uniform float ssaoInt;
-
-				uniform float d;
-				uniform vec3 Ka;
-				uniform vec3 Ks;
-				uniform vec3 Ke;
-				uniform float Ni;
-				uniform float r;
-				uniform float m;
-				uniform float sss;
-
-				uniform vec2 offset;
-				uniform float angle;
-				uniform float scale;
-
-				uniform float sNormals;
-				uniform float sSSAO;
-				uniform float sAO;
-				uniform float sIAO;
-				uniform float sAmbient;
-				uniform float sAlbedo;
-				uniform float sSpecular;
-				uniform float sEmission;
-				uniform float sAlpha;
-				uniform float sGamma;
-				uniform float sReinhard;
-				uniform float sUchimura;
-				uniform float sNormal;
-				uniform float sHDR;
-				uniform float sShadows;
-				uniform float sSSS;
-
-				uniform float overlay;
+		uniform float lightInt[${dl}];
+		uniform vec3 lightAmb[${dl}];
+		uniform vec3 lightColor[${dl}];
+		uniform vec3 lightVect[${dl}];
+
+		uniform float pointLightInt[${pl}];
+		uniform vec3 pointLightAmb[${pl}];
+		uniform vec3 pointLightColor[${pl}];
+		uniform vec3 pointLightPos[${pl}];
+
+		uniform vec3 shadowDir;
+		uniform float shadowBiasMin;
+		uniform float shadowBiasMax;
+		uniform float shadowIntensity;
+
+		uniform float sssPower;
+		uniform float sssDistortion;
+		uniform float sssIntensity;
+		uniform float sssAmbient;
+
+		uniform float whiteM;
+		uniform float gammaY;
+		uniform float uchimuraP;
+		uniform float uchimuraA;
+		uniform float ssaoInt;
+		uniform bool opaque;
+
+		uniform float d;
+		uniform vec3 Ka;
+		uniform vec3 Ks;
+		uniform vec3 Ke;
+		uniform float Ni;
+		uniform float r;
+		uniform float m;
+		uniform bool sss;
+
+		uniform vec2 offset;
+		uniform float angle;
+		uniform float scale;
+
+		uniform bool sNormals;
+		uniform bool sSSAO;
+		uniform bool sAO;
+		uniform bool sIAO;
+		uniform bool sAmbient;
+		uniform bool sAlbedo;
+		uniform bool sSpecular;
+		uniform bool sEmission;
+		uniform bool sAlpha;
+		uniform bool sGamma;
+		uniform bool sReinhard;
+		uniform bool sUchimura;
+		uniform bool sNormal;
+		uniform bool sHDR;
+		uniform bool sShadows;
+		uniform bool sSSS;
+
+		uniform bool overlay;
                 
                 uniform vec3 cameraPos;
 
@@ -341,25 +348,6 @@ App.Art.Engine = class {
 
 				const float PI = 3.14159265359;
 
-				vec2 poissonDisk[16] = vec2[]( 
-					vec2( -0.94201624, -0.39906216 ), 
-					vec2( 0.94558609, -0.76890725 ), 
-					vec2( -0.094184101, -0.92938870 ), 
-					vec2( 0.34495938, 0.29387760 ), 
-					vec2( -0.91588581, 0.45771432 ), 
-					vec2( -0.81544232, -0.87912464 ), 
-					vec2( -0.38277543, 0.27676845 ), 
-					vec2( 0.97484398, 0.75648379 ), 
-					vec2( 0.44323325, -0.97511554 ), 
-					vec2( 0.53742981, -0.47373420 ), 
-					vec2( -0.26496911, -0.41893023 ), 
-					vec2( 0.79197514, 0.19090188 ), 
-					vec2( -0.24188840, 0.99706507 ), 
-					vec2( -0.81409955, 0.91437590 ), 
-					vec2( 0.19984126, 0.78641367 ), 
-					vec2( 0.14383161, -0.14100790 ) 
-				 );
-
 				float distributionGGX(vec3 N, vec3 H, float roughness)
 				{
 					float a = roughness*roughness;
@@ -441,19 +429,13 @@ App.Art.Engine = class {
 					return T * w0 + L * w1 + S * w2;
 				}
 
-				float random(vec3 seed, int i){
-					vec4 seed4 = vec4(seed,i);
-					float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
-					return fract(sin(dot_product) * 43758.5453);
-				}
-
 				void main() {
-					vec2 resolution = vec2(textureSize(textSampler[0], 0));
-					mat3 TBN = createTBNMatrix(pos, textureCoord, normal);
-
 					vec3 new_normal = normal;
-					vec3 map_Ka = vec3(0.0,0.0,0.0);
-					vec3 map_Ks = vec3(0.0,0.0,0.0);
+					if (!gl_FrontFacing)
+						new_normal = -normal;
+
+                    vec3 map_Ka = vec3(0.0,0.0,0.0);
+                    vec3 map_Ks = vec3(0.0,0.0,0.0);
 					vec3 map_Ke = vec3(0.0,0.0,0.0);
 					float map_d = 1.0;
 					float ao = 1.0;
@@ -462,56 +444,68 @@ App.Art.Engine = class {
 					float roughness = 0.0;
 					float metallic = 0.0;
 
+					vec2 resolution = vec2(textureSize(textSampler[0], 0));
 					vec2 coord = textureCoord - vec2(0.5, 0.5) - offset;
 					coord = vec2(coord.x * cos(angle) - coord.y * sin(angle), coord.x * sin(angle) + coord.y * cos(angle));
 					coord = coord / scale;
 					coord = coord + vec2(0.5, 0.5);
 					
-                    if (sAlpha == 1.0)
-                        map_d = d * texture(textSampler[1], coord).r;
-
-					if (map_d < 0.05)
-						discard;
+					if (sAlpha)
+						map_d = d * texture(textSampler[1], coord).r;
+
+					if (!opaque) {
+						if (map_d < 0.01 || map_d == 1.0)
+							discard;
+					} else {
+						if (map_d < 1.0)
+							discard;
+					}
 
-					if (sNormal == 1.0) {
+					if (sNormal) {
                         vec3 map_Kn = texture(textSampler[5], coord).rgb *2.0-1.0;
-                        if (map_Kn != vec3(-1.0,-1.0,-1.0))
+                        if (map_Kn != vec3(-1.0,-1.0,-1.0)) {
+							mat3 TBN = createTBNMatrix(pos, textureCoord, new_normal);
 							new_normal = normalize(TBN * map_Kn);
-					}
+						}
+                    }
 
-					if (sSSAO == 1.0)
+					if (sSSAO)
 						ao = texture(textSampler[0], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
 
-					if (sSSS == 1.0)
+					if (sSSS)
 						iao = texture(textSampler[9], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
 
-					if (sAlbedo == 1.0)
+					if (sAlbedo)
                         map_Ka = Ka * texture(textSampler[2], coord).rgb;
+					map_Ka += 0.0001;
 
-                    if (sSpecular == 1.0) {
+                    if (sSpecular) {
                         map_Ks = Ks * texture(textSampler[3], coord).rgb;
 						roughness = r * texture(textSampler[6], coord).r;
 						metallic = m * texture(textSampler[7], coord).r;
 					}
 
-					if (sEmission == 1.0)
+					if (sEmission)
                         map_Ke = Ke * texture(textSampler[4], coord).rgb;
 
-					if (sShadows == 1.0) {
+					if (sShadows) {
 						vec3 projCoords = shadowMap.xyz/shadowMap.w * 0.5 + 0.5;
 						float currentDepth = projCoords.z;
 						float bias = max(shadowBiasMax * (1.0 - dot(new_normal, shadowDir)), shadowBiasMin); 
 
 						vec2 texelSize = 1.0 / vec2(textureSize(textSampler[8], 0));
-						for (int i=0;i<16;i++){
-							int index = int(16.0*random(floor(pos.xyz*1000.0), i))%16;
-							float pcfDepth = texture(textSampler[8], projCoords.xy + poissonDisk[index]*texelSize).r;
-							shadow += currentDepth - bias < pcfDepth ? 1.0 : 0.0;
+						for(int x = -1; x <= 1; ++x)
+						{
+							for(int y = -1; y <= 1; ++y)
+							{
+								float pcfDepth = texture(textSampler[8], projCoords.xy + vec2(x, y) * texelSize).r; 
+								shadow += currentDepth - bias < pcfDepth ? 1.0 : 0.0;
+							}    
 						}
 						shadow /= 16.0;
 						shadow = min(shadow + (1.0-shadowIntensity), 1.0);
 					}
-
+					
 					// material parameters
 					vec3 albedo = pow(map_Ka, vec3(2.2));
 					ao = pow(ao, ssaoInt);
@@ -523,7 +517,7 @@ App.Art.Engine = class {
 					// reflectance equation
 					vec3 Lo = vec3(0.0);
 					vec3 Le = map_Ke;
-					for (int i = 0; i < ${dl}; i++) {
+                    for (int i = 0; i < ${dl}; i++) {
 						if (lightInt[i] <= 0.0)
 							continue;
 
@@ -535,7 +529,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);       
+						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);
 						
 						vec3 numerator = NDF * G * F;
 						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001;
@@ -547,7 +541,7 @@ App.Art.Engine = class {
 						
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						if (map_d >= 0.85 || overlay == 1.0) {
+						if (map_d >= 0.85 || overlay) {
 							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
 						}
 						else {
@@ -557,9 +551,9 @@ App.Art.Engine = class {
 						// ambient lighting
 						vec3 La = kD * albedo * lightAmb[i];
 
-						if (map_d >= 0.85 || overlay == 1.0) {
+						if (map_d >= 0.85 || overlay) {
 							// sss
-							if (sSSS == 1.0 && sss == 1.0) {
+							if (sSSS && sss) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
 								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
@@ -584,7 +578,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);       
+						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);
 						
 						vec3 numerator = NDF * G * F;
 						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001;
@@ -592,11 +586,11 @@ App.Art.Engine = class {
 
 						// kS is equal to Fresnel
 						vec3 kD = vec3(1.0) - F;
-						kD *= 1.0 - metallic;	  
-						
+						kD *= 1.0 - metallic;
+				
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						if (map_d >= 0.85 || overlay == 1.0) {
+						if (map_d >= 0.85 || overlay) {
 							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
 						}
 						else {
@@ -606,9 +600,9 @@ App.Art.Engine = class {
 						// ambient lighting
 						vec3 La = kD * albedo * pointLightAmb[i];
 
-						if (map_d >= 0.85 || overlay == 1.0) {
+						if (map_d >= 0.85 || overlay) {
 							// sss
-							if (sSSS == 1.0 && sss == 1.0) {
+							if (sSSS && sss) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
 								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
@@ -619,44 +613,45 @@ App.Art.Engine = class {
 						} else {
 							Lo += La;
 						}
-					}
+					}	
 
 					vec3 c = (Lo + Le);
 
 
-					if (sReinhard == 1.0) {
+					if (sReinhard) {
                         float l_old = 0.2126*c.r+0.7152*c.g+0.0722*c.b;
                         float numerator = l_old * (1.0 + (l_old / (whiteM*whiteM)));
                         float l_new = numerator / (1.0 + l_old);
                         c = c * (l_new / l_old);
 					}
 
-					if (sUchimura == 1.0) {
+					if (sUchimura) {
 						c = tonemapUchimura(c, uchimuraP, uchimuraA);
 					}
 
-					if (sGamma == 1.0)
-						c = pow(c, vec3(1.0/gammaY));
+					if (sGamma)
+                        c = pow(c, vec3(1.0/gammaY));
 					
-					if (sNormals == 1.0)
-						c = new_normal*0.5+0.5;
+                    if (sNormals)
+                        c = new_normal*0.5+0.5;
 
-					if (sAO == 1.0)
+					if (sAO)
 						c = vec3(ao);
 
-					if (sIAO == 1.0)
+					if (sIAO)
 						c = vec3(iao);
 
 					outputColor = vec4(c*map_d, map_d);
 				}`;
 	}
 
-	initBuffers(sceneData, dir) {
+	initBuffers(sceneData, sceneParams, dir) {
 		// init buffer containers
 		this.buffers = new class {};
 		this.buffers.models = [];
 		for (let m=0; m < sceneData.models.length; m++) {
 			this.buffers.models[m] = new class {};
+			this.buffers.models[m].tree = this.buildModelTree(sceneParams.models[m]);
 		}
 
 		// init quad buffers
@@ -688,15 +683,15 @@ App.Art.Engine = class {
 			modelBuffers.indexSizes = [];
 			modelBuffers.figureIndices = [];
 			modelBuffers.figureSeemMaps = [];
+			modelBuffers.figureTargets = [];
+			modelBuffers.figureWeights = [];
+			modelBuffers.figureTarget = [];
+
 			for (let i=0, count=0; i < modelData.figures.length; i++) {
 				modelBuffers.verticesPositionBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].verts), this.gl.STATIC_DRAW);
 				modelBuffers.vertexPositionBuffer[i] = this.base64ToFloat(modelData.figures[i].verts);
 
 				modelBuffers.verticesNormalBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(modelBuffers.vertexPositionBuffer[i].length), this.gl.STATIC_DRAW);
 
 				modelBuffers.verticesTextureCoordBuffer[i] = this.gl.createBuffer();
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
@@ -716,20 +711,21 @@ App.Art.Engine = class {
 				let seemMap = new Int32Array(seems.length*2);
 				let indices = modelBuffers.figureIndices[i];
 				for (let j=0, h=0; j <= seems.length-3; j+=3, h+=6) {
-					let pos = seems[j+0];
-					let oldInd = seems[j+1];
-					let newInd = seems[j+2];
-					indices[pos] = newInd/3;
-					seemMap[h] = oldInd;
-					seemMap[h+1] = newInd;
-					seemMap[h+2] = oldInd+1;
-					seemMap[h+3] = newInd+1;
-					seemMap[h+4] = oldInd+2;
-					seemMap[h+5] = newInd+2;
+					let idx = seems[j+1];
+					let value = seems[j+2];
+					indices[seems[j]] = value/3;
+					seemMap[h] = idx;
+					seemMap[h+1] = value;
+					seemMap[h+2] = idx+1;
+					seemMap[h+3] = value+1;
+					seemMap[h+4] = idx+2;
+					seemMap[h+5] = value+2;
 				}
 				modelBuffers.figureSeemMaps[i] = seemMap;
+				modelBuffers.figureTargets[i] = this.base64ToInt(modelData.figures[i].targets);
+				modelBuffers.figureWeights[i] = this.base64ToFloat(modelData.figures[i].weights);
+				modelBuffers.figureTarget[i] = parseInt(modelData.figures[i].target);
 			}
-
 			this.initMorphs(modelBuffers, modelData, dir);
 		}
 	}
@@ -762,6 +758,12 @@ App.Art.Engine = class {
 		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur);
 		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
 
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.guassianColorBuffer);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA32F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+
+		this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.buffers.forwardFBO);
+		this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT32F, screenWidth, screenHeight);
+
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 	}
 
@@ -868,6 +870,25 @@ App.Art.Engine = class {
 		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBufferBlur, 0);
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 
+		// forward color buffer
+		buffers.forwardBuffer = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.forwardBuffer);
+
+		buffers.guassianColorBuffer = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.guassianColorBuffer);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA32F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.guassianColorBuffer, 0);
+
+		this.gl.drawBuffers([this.gl.COLOR_ATTACHMENT0]);
+
+		buffers.forwardFBO = this.gl.createRenderbuffer();
+		this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, buffers.forwardFBO);
+		this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT32F, screenWidth, screenHeight);
+		this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, buffers.forwardFBO);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
 		const random = function(seed) {
 			let x = Math.sin(seed+1) * 10000;
 			return x - Math.floor(x);
@@ -1155,7 +1176,7 @@ App.Art.Engine = class {
 		this.gl.depthFunc(this.gl.LEQUAL);
 		this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
 
-		this.initBuffers(sceneData, dir);
+		this.initBuffers(sceneData, sceneParams, dir);
 		this.initTextures(sceneData, dir);
 		this.initShaders(sceneParams);
 	}
@@ -1164,16 +1185,10 @@ App.Art.Engine = class {
 		// update logic
 		this.update(sceneParams);
 
-		// set culling
-		if (V.setFaceCulling) {
-			this.gl.enable(this.gl.CULL_FACE);
-			this.gl.cullFace(this.gl.BACK);
-		} else {
-			this.gl.disable(this.gl.CULL_FACE);
-		}
 		this.gl.clearColor(0, 0, 0, 0);
 
 		// draw scene
+		this.gl.enable(this.gl.CULL_FACE);
 		this.gl.disable(this.gl.BLEND);
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.gBuffer);
 		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
@@ -1183,6 +1198,7 @@ App.Art.Engine = class {
 		this.gl.enable(this.gl.BLEND);
 
 		if (V.setShadowMapping) {
+			this.gl.disable(this.gl.CULL_FACE);
 			this.gl.disable(this.gl.BLEND);
 			this.gl.cullFace(this.gl.FRONT);
 			this.gl.viewport(0, 0, sceneParams.settings.rwidth*4, sceneParams.settings.rheight*4);
@@ -1197,6 +1213,7 @@ App.Art.Engine = class {
 		}
 
 		if (V.setSSAO) {
+			this.gl.enable(this.gl.CULL_FACE);
 			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.ssaoFBO);
 			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
 			this.gl.useProgram(this.shaderProgramSSAO);
@@ -1211,6 +1228,7 @@ App.Art.Engine = class {
 		}
 
 		if (V.setSSS) {
+			this.gl.enable(this.gl.CULL_FACE);
 			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssFBO);
 			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
 			this.gl.useProgram(this.shaderProgramSSS);
@@ -1224,9 +1242,19 @@ App.Art.Engine = class {
 			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 		}
 
+		// this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.forwardBuffer);
+		if (V.setFaceCulling) {
+			this.gl.enable(this.gl.CULL_FACE);
+		} else {
+			this.gl.disable(this.gl.CULL_FACE);
+		}
 		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
 		this.gl.useProgram(this.shaderProgramForwardPass);
-		this.drawForwardPass(sceneParams);
+		this.drawForwardPass(sceneParams, true);
+
+		this.gl.disable(this.gl.CULL_FACE);
+		this.gl.useProgram(this.shaderProgramForwardPass);
+		this.drawForwardPass(sceneParams, false);
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 
 		// clone from offscreen to real canvas
@@ -1283,7 +1311,7 @@ App.Art.Engine = class {
 			}
 
 			// create model transforms
-			this.applyMorphs(modelParams, modelBuffers);
+			this.applyMorphs(sceneParams, modelParams, modelBuffers);
 
 			let matRot = this.matrixMakeRotation(this.degreeToRad(modelParams.transform.xr), this.degreeToRad(modelParams.transform.yr), this.degreeToRad(modelParams.transform.zr));
 			let matTrans = this.matrixMakeTranslation(modelParams.transform.x, modelParams.transform.y, modelParams.transform.z);
@@ -1352,6 +1380,7 @@ App.Art.Engine = class {
 						this.gl.activeTexture(this.gl.TEXTURE0);
 						this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_D]);
 						this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramGeometry, "alpha"), 0);
+						this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramGeometry, "d"), mat.d);
 
 						this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
 						this.gl.drawElements(this.gl.TRIANGLES, modelBuffers.indexSizes[count], this.gl.UNSIGNED_INT, 0);
@@ -1404,6 +1433,7 @@ App.Art.Engine = class {
 						this.gl.activeTexture(this.gl.TEXTURE0);
 						this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_D]);
 						this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramShadow, "alpha"), 0);
+						this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramShadow, "d"), mat.d);
 
 						this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
 						this.gl.drawElements(this.gl.TRIANGLES, modelBuffers.indexSizes[count], this.gl.UNSIGNED_INT, 0);
@@ -1511,21 +1541,23 @@ App.Art.Engine = class {
 		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
 	}
 
-	drawForwardPass(sceneParams) {
+	drawForwardPass(sceneParams, opaque) {
 		// set scene uniforms
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormals"), sceneParams.settings.normals);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAO"), sceneParams.settings.ao);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sIAO"), sceneParams.settings.iao);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSAO"), V.setSSAO);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSS"), sceneParams.settings.sss);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sShadows"), V.setShadowMapping);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAlbedo"), sceneParams.settings.albedo);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSpecular"), sceneParams.settings.specular);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sEmission"), sceneParams.settings.emission);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormal"), sceneParams.settings.normal);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAlpha"), sceneParams.settings.alpha);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sReinhard"), sceneParams.settings.reinhard);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sGamma"), sceneParams.settings.gamma);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "opaque"), opaque);
+
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormals"), sceneParams.settings.normals);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAO"), sceneParams.settings.ao);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sIAO"), sceneParams.settings.iao);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSAO"), V.setSSAO);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSS"), sceneParams.settings.sss);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sShadows"), V.setShadowMapping);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAlbedo"), sceneParams.settings.albedo);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSpecular"), sceneParams.settings.specular);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sEmission"), sceneParams.settings.emission);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormal"), sceneParams.settings.normal);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAlpha"), sceneParams.settings.alpha);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sReinhard"), sceneParams.settings.reinhard);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sGamma"), sceneParams.settings.gamma);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "whiteM"), sceneParams.settings.whiteM);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "gammaY"), sceneParams.settings.gammaY);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sUchimura"), sceneParams.settings.uchimura);
@@ -1609,9 +1641,9 @@ App.Art.Engine = class {
 
 						if (matId !== "ao_surface") {
 							if (h > 0) {
-								this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "overlay"), 1.0);
+								this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "overlay"), 1);
 							} else {
-								this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "overlay"), 0.0);
+								this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "overlay"), 0);
 							}
 
 							this.gl.activeTexture(this.gl.TEXTURE0);
@@ -1666,7 +1698,7 @@ App.Art.Engine = class {
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "angle"), mat.transform[2]);
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "scale"), mat.transform[3]);
 
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sss"), mat.sss);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sss"), mat.sss);
 
 							// draw materials
 							this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
@@ -1678,18 +1710,76 @@ App.Art.Engine = class {
 		}
 	}
 
-	applyMorphs(modelParams, modelBuffers) {
-		if (modelBuffers.oldMorphValues !== JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures)) {
-			for (let f=0; f < modelParams.figures.length; f++) {
-				if (!modelParams.visible || !modelParams.figures[f].visible) {
-					continue;
+	traverseTree(tree, current, callback) {
+		current = current.toString();
+		let children = tree[current];
+
+		let isRoot = tree.roots.includes(current);
+
+		current = parseInt(current);
+
+		if (children !== undefined) {
+			callback.call(this, current, isRoot, false);
+			children.forEach(child => this.traverseTree(tree, child, callback));
+		} else {
+			callback.call(this, current, isRoot, true);
+		}
+	}
+
+	buildModelTree(modelParams) {
+		let tree = {};
+		tree.roots = [];
+
+		for (let i = 0; i < modelParams.figures.length; i++) {
+		   let parent = modelParams.figures[i].morphTarget;
+		   let f = i.toString();
+
+			if (parent === '') {
+			    tree.roots.push(f);
+				continue;
+			}
+
+			parent = modelParams.figures.findIndex(x => x.figId === parent);
+			if (tree[parent] === undefined)  {
+				tree[parent] = [];
+			}
+			tree[parent].push(f);
+		}
+		return tree;
+	}
+
+	makeCheckString(sceneParams, modelParams) {
+		let checkString = '';
+		modelParams.morphs.forEach(m => checkString += m.value + '' + m.offset);
+		modelParams.figures.forEach(f => checkString += f.visible);
+		checkString += sceneParams.settings.morphOffsetA + '' + sceneParams.settings.morphOffsetB + '' + sceneParams.settings.morphOffsetM;
+		return checkString;
+	}
+
+	applyMorphs(sceneParams, modelParams, modelBuffers) {
+		if (modelParams.visible && modelBuffers.oldMorphValues !== this.makeCheckString(sceneParams, modelParams)) {
+			modelBuffers.tree.roots.forEach(root => this.traverseTree(modelBuffers.tree, root, function(f, isRoot, isLeaf) {
+				// check if children are visible
+				let visible = false;
+				this.traverseTree(modelBuffers.tree, f, function(f2, isRoot, isLeaf) {
+					if (modelParams.figures[f2].visible) {
+						visible = true;
+					}
+				});
+
+				if (!visible) {
+					return;
 				}
 
 				let vertexPosition = new Float32Array(modelBuffers.vertexPositionBuffer[f]);
 				let vertexNormal = new Float32Array(vertexPosition.length);
 
+				// apply morph
 				for (let m=0; m < modelParams.morphs.length; m++) {
 					let morphValue = modelParams.morphs[m].value;
+					if (modelParams.figures[f].morphOffset) {
+						morphValue *= modelParams.morphs[m].offset;
+					}
 
 					if (morphValue !== 0) {
 						let vp = modelBuffers.vertexPositionMorphs[f][m];
@@ -1707,6 +1797,71 @@ App.Art.Engine = class {
 					}
 				}
 
+				// get morph delta
+				if (!isLeaf) {
+					let pDelta = new Float32Array(vertexPosition);
+					let vp = new Float32Array(modelBuffers.vertexPositionBuffer[f]);
+					for (let j=0; j < vp.length; j+=3) {
+						pDelta[j]   -= vp[j];
+						pDelta[j+1] -= vp[j+1];
+						pDelta[j+2] -= vp[j+2];
+					}
+					modelBuffers.posDelta = pDelta; // save morph delta
+				}
+
+				if (!isRoot) {
+					let targets = modelBuffers.figureTargets[f];
+					let weights = modelBuffers.figureWeights[f];
+					let target = targets[modelBuffers.figureTarget[f]];
+					let pDelta = modelBuffers.posDelta;
+					let nDelta = modelBuffers.normDelta;
+					let mA = sceneParams.settings.morphOffsetA;
+					let mB = sceneParams.settings.morphOffsetB;
+					let mM = sceneParams.settings.morphOffsetM;
+
+					if (modelParams.figures[f].morphFollow) {
+						for (let j = 0; j < vertexPosition.length; j+=3) {
+							let t1 = targets[j]; // get closest triangle to base mesh
+							let t2 = targets[j+1];
+							let t3 = targets[j+2];
+
+							let w1 = weights[j];
+							let w2 = weights[j+1];
+							let w3 = weights[j+2];
+
+							// find precomputed baricentric point
+							let p1 = pDelta[t1]*w1 + pDelta[t2]*w2 + pDelta[t3]*w3;
+							let p2 = pDelta[t1+1]*w1 + pDelta[t2+1]*w2 + pDelta[t3+1]*w3;
+							let p3 = pDelta[t1+2]*w1 + pDelta[t2+2]*w2 + pDelta[t3+2]*w3;
+
+							vertexPosition[j]   += p1;
+							vertexPosition[j+1] += p2;
+							vertexPosition[j+2] += p3;
+
+							let n1 = nDelta[t1]*w1 + nDelta[t2]*w2 + nDelta[t3]*w3;
+							let n2 = nDelta[t1+1]*w1 + nDelta[t2+1]*w2 + nDelta[t3+1]*w3;
+							let n3 = nDelta[t1+2]*w1 + nDelta[t2+2]*w2 + nDelta[t3+2]*w3;
+
+							let l = this.vectorLength([p1, p2, p3]);
+							if (l > 0.01) { // apply morph delta and add offset according to base mesh normals
+								l = Math.min(l / mA, mM) * mB;
+								vertexPosition[j]   += l*n1;
+								vertexPosition[j+1] += l*n2;
+								vertexPosition[j+2] += l*n3;
+							}
+						}
+					} else { // static morph, follow one vertex
+						let pDeltaX = pDelta[target*3];
+						let pDeltaY = pDelta[target*3+1];
+						let pDeltaZ = pDelta[target*3+2];
+						for (let j = 0; j < vertexPosition.length; j+=3) {
+							vertexPosition[j]   += pDeltaX;
+							vertexPosition[j+1] += pDeltaY;
+							vertexPosition[j+2] += pDeltaZ;
+						}
+					}
+				}
+
 				// recalculate normals
 				let indices = modelBuffers.figureIndices[f];
 				for (let j=0; j < indices.length; j+=3) {
@@ -1737,19 +1892,28 @@ App.Art.Engine = class {
 					vertexNormal[seemMap[j]] = vertexNormal[seemMap[j+1]];
 				}
 
+				if (!isLeaf) {
+					for (let j=0; j < vertexNormal.length; j+=3) {
+						let l = this.vectorLength([vertexNormal[j], vertexNormal[j+1], vertexNormal[j+2]]);
+						vertexNormal[j]   /= l;
+						vertexNormal[j+1] /= l;
+						vertexNormal[j+2] /= l;
+					}
+					modelBuffers.normDelta = vertexNormal;
+				}
+
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[f]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPosition, this.gl.STATIC_DRAW);
 
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[f]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormal, this.gl.STATIC_DRAW);
-			}
-
-			modelBuffers.oldMorphValues = JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures);
+			}));
+			modelBuffers.oldMorphValues = this.makeCheckString(sceneParams, modelParams);
 		}
 	}
 
 	base64ToFloat(array) {
-		let b = window.atob(array);
+		let b = window.atob(this.gzipToBase64(array));
 		let fLen = b.length / (Float32Array.BYTES_PER_ELEMENT-1);
 		let dView = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT));
 		let fAry = new Float32Array(fLen);
@@ -1758,7 +1922,7 @@ App.Art.Engine = class {
 		for (let j=0; j < fLen; j++){
 			p = j * 3;
 			dView.setUint8(0, 0); // skip 1 precision byte
-			dView.setUint8(1, b.charCodeAt(p+0));
+			dView.setUint8(1, b.charCodeAt(p));
 			dView.setUint8(2, b.charCodeAt(p+1));
 			dView.setUint8(3, b.charCodeAt(p+2));
 			fAry[j] = dView.getFloat32(0, true);
@@ -1767,7 +1931,7 @@ App.Art.Engine = class {
 	}
 
 	base64ToInt(array) {
-		let b = window.atob(array);
+		let b = window.atob(this.gzipToBase64(array));
 		let fLen = b.length / Int32Array.BYTES_PER_ELEMENT;
 		let dView = new DataView(new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
 		let fAry = new Int32Array(fLen);
@@ -1784,7 +1948,11 @@ App.Art.Engine = class {
 		return fAry;
 	}
 
-	base64ToByte(array) {
+	gzipToBase64(array) {
+		if (array === '') {
+			return array;
+		}
+
 		let b = window.atob(array);
 		let fLen = b.length / Uint8Array.BYTES_PER_ELEMENT;
 		let dView = new DataView(new ArrayBuffer(Uint8Array.BYTES_PER_ELEMENT));
@@ -1794,7 +1962,9 @@ App.Art.Engine = class {
 			dView.setUint8(0, b.charCodeAt(j));
 			fAry[j] = dView.getUint8(0);
 		}
-		return fAry;
+
+		let data = pako.ungzip(fAry, {raw:false, "to":"string"});
+		return data;
 	}
 
 	degreeToRad(d) { return d * (Math.PI / 180); }
@@ -1816,6 +1986,11 @@ App.Art.Engine = class {
 		return [v[0]/l, v[1]/l, v[2]/l];
 	}
 
+	vectorLength(v) {
+		let l = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+		return l;
+	}
+
 	matrixMakeProjection(fov, aspect, near, far) {
 		return [[1/(aspect*Math.tan(fov*0.5/180*Math.PI)), 0, 0, 0],
 			[0, (1/Math.tan(fov*0.5/180*Math.PI)), 0, 0],