diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d9f8c5d756489d1bf27156e11a1776e190504a62..7a65b78b2e51d6715047f525f9c395a1bdd8a30f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,7 +18,7 @@ variables:
   # precompiled / assets
   # the URLs are the same as inside the game, change both if updating
   WEBGL_NAME: "WebGL art assets"
-  WEBGL_URL: "https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA"
+  WEBGL_URL: "https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA"
   RENDER_NAME: "Rendered imagepack (outdated)"
   RENDER_URL: "https://mega.nz/file/upoAlBaZ#EbZ5wCixxZxBhMN_ireJTXt0SIPOywO2JW9XzTIPhe0"
 
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 2977f3da51072e7e1b4e87e3882b2b56c94703d3..0d7f4a6b8bd84be738d56e4899da8bb196fe5983 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -155,6 +155,7 @@ App.Data.defaultGameStateVariables = {
 	setTextureResolution: 1024,
 	setShadowMapping: true,
 	setSSAO: true,
+	setSSS: true,
 	setTonemapping: true,
 	setImageSize: 1,
 	showAgeDetail: 1,
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 64ebc0a0bb7c10546e668ca8a7448e51fb84a533..7c360aa63c52df6229968f1d7e2c1db25a9beaf9 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -198,6 +198,7 @@ App.Art.webglInitialize = function() {
 		try {
 			// load model/morphs/textures assets
 			let sceneData = App.Art.sceneGetData();
+			App.Art.sceneGetData = function(){};
 			// load default dictionary containing camera/light/morph/material values
 			let scene = App.Art.sceneGetParams();
 			scene.lockView = false;
@@ -226,12 +227,27 @@ App.Art.webglInitialize = function() {
 	};
 	script.src = "resources/webgl/scene1/scene1.js";
 
+	let settings = document.createElement("script");
+	settings.onload = function() {
+		try {
+			document.head.appendChild(script);
+		} catch (e) {
+			App.Art.errorHandler("engineFailed", 1);
+			LoadScreen.unlock(loadLockID);
+		}
+	};
+	settings.onerror = function() {
+		App.Art.errorHandler("engineFailed", 2);
+		LoadScreen.unlock(loadLockID);
+	};
+	settings.src = "resources/webgl/scene1/settings.json";
+
 	// asynchronously load webgl assets if present
 	let load = document.createElement("script");
 	load.onload = function() {
 		// but only if version is correct
-		if (App.Art.version === "1.10") {
-			document.head.appendChild(script);
+		if (App.Art.version === "1.11") {
+			document.head.appendChild(settings);
 		} else {
 			App.Art.errorHandler("engineFailed", 3);
 			LoadScreen.unlock(loadLockID);
diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js
index cc0efd6538d9f45b7f829e592bd3d581e7a2b7a2..ba23fdb80a5d670bffcc7299cd16c0d640b58a89 100644
--- a/src/art/webgl/art.js
+++ b/src/art/webgl/art.js
@@ -241,7 +241,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "a slutty maid outfit":
 			figures.push("Slutty Maid Dress", "Slutty Maid Bands", "Slutty Maid Neck Bow", "Slutty Maid Shoes", "Slutty Maid Headband");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = false;
 			p.hideHair = false;
 			p.applyPumps = true;
@@ -262,7 +262,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "a biyelgee costume":
 			figures.push("Biyelgee Dress", "Biyelgee Hat");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = false;
 			p.hideHair = false;
 			break;
@@ -370,7 +370,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "an oversized t-shirt":
 			figures.push("Oversized Shirt");
-			p.hideDick = false;
+			p.hideDick = true;
 			p.applyNipples = false;
 			p.hideHair = false;
 			break;
@@ -546,7 +546,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "slutty jewelry":
 			figures.push("Bangles");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = true;
 			p.hideHair = false;
 			break;
@@ -586,8 +586,8 @@ App.Art.applyFigures = function(slave, scene, p) {
 			p.hideHair = false;
 			p.applyPanty = true;
 			break;
-		case "kitty lingerie": // placeholder since bunny is duplicate
-			figures.push("Bunny Outfit");
+		case "kitty lingerie":
+			figures.push("Kitty Choker", "Kitty Top", "Kitty Panty");
 			p.applyPanty = false;
 			p.hideDick = true;
 			p.applyNipples = false;
@@ -599,6 +599,34 @@ App.Art.applyFigures = function(slave, scene, p) {
 			p.applyNipples = true;
 			p.hideHair = false;
 			break;
+		case "a schoolgirl outfit":
+			figures.push("School Girl Belly Piercing", "School Girl Choker", "School Girl Panty", "School Girl Skirt", "School Girl Stockings", "School Girl Shirt");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "slutty business attire":
+			figures.push("Secretary Glasses", "Secretary Skirt", "Secretary Vest", "Secretary Stockings");
+			p.applyPanty = true;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "a monokini":
+			figures.push("Monokini");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "a string bikini":
+			figures.push("String Bikini Bottom", "String Bikini Top");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
 	}
 
 	if (p.underage) {
@@ -608,17 +636,48 @@ App.Art.applyFigures = function(slave, scene, p) {
 		p.hideDick = true;
 	}
 
-	if (slave.chastityAnus && slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes", "Chastity Belt Anal Cap with Hole");
-		p.hideDick = true;
+	/*
+	switch (slave.vaginalAccessory) {
+		case "dildo":
+			figures.push("Dildo 1");
+			break;
 	}
-	if (!slave.chastityAnus && slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes");
-		p.hideDick = true;
+
+	switch (slave.buttplug) {
+		case "plug":
+			figures.push("Anal Plug 1");
+			break;
+		case "large plug":
+			figures.push("Anal Plug 2");
+			break;
+		case "huge plug":
+			figures.push("Anal Plug 3");
+			break;
+		case "long plug":
+			figures.push("Anal Plug 1");
+			break;
+		case "long, large plug":
+			figures.push("Anal Plug 2");
+			break;
+		case "long, huge plug":
+			figures.push("Anal Plug 3");
+			break;
 	}
-	if (slave.chastityAnus && !slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Anal Cap with Hole");
-		p.hideDick = true;
+	*/
+
+	if (slave.chastityAnus || slave.chastityVagina || slave.chastityPenis) {
+		figures.push("Chastity Belt Base");
+		if (slave.chastityAnus) {
+			figures.push("Chastity Belt Anal Cap with Hole");
+		}
+		if (slave.chastityVagina) {
+			figures.push("Chastity Belt Vaginal Cap with Holes");
+			p.hideDick = true;
+		}
+		if (slave.chastityPenis) {
+			figures.push("Chastity Belt Vaginal Cap with Holes");
+			p.hideDick = true;
+		}
 	}
 
 	switch (slave.bellyAccessory) {
@@ -656,10 +715,10 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 	}
 
-	if (!hasLeftArm(slave) && slave.PLimb > 0) {
+	if (!slave.arm.left && slave.PLimb > 0) {
 		figures.push("Amputee Cap Arm Left");
 	}
-	if (!hasRightArm(slave) && slave.PLimb > 0) {
+	if (!slave.arm.right && slave.PLimb > 0) {
 		figures.push("Amputee Cap Arm Right");
 	}
 
@@ -1494,15 +1553,15 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_4.jpg"]);
 	}
 
-	let irisColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.iris) : extractColor("black")));
-	let irisColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.iris) : extractColor("black")));
-	let scleraColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.sclera) : extractColor("black")));
-	let scleraColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.sclera) : extractColor("black")));
+	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, irisColorLeft[1]*1.4, irisColorLeft[2]*1.4]]);
-	materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4, irisColorRight[1]*1.4, irisColorRight[2]*1.4]]);
-	materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.75, scleraColorLeft[1]*0.75, scleraColorLeft[2]*0.75]]);
-	materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.75, scleraColorRight[1]*0.75, scleraColorRight[2]*0.75]]);
+	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]]);
 
 	// expected skin color
 	let O = App.Art.hexToRgb(skinColorCatcher(slave).skinColor);
@@ -1534,20 +1593,23 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	let Ka;
 	let lKa;
 	let r;
+	let rN;
 	let Ni;
 	let skin;
 	let cockSkin;
 
 	if (slave.clothes === "body oil") {
 		r = 0.4;
+		rN = 0.4;
 		Ni = 1.5;
 	} else {
 		r = 0.7;
+		rN = 0.55;
 		Ni = 1.35;
 	}
 
 	if (sqAO < sqBO && sqAO < sqCO && sqAO < sqDO) {
-		Ks = [2, 2, 2];
+		Ks = [2.5, 2.5, 2.5];
 		Ka = mA;
 		lKa = lA;
 		skin = "WhiteTone";
@@ -1560,7 +1622,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso white.jpg"]);
 		materials.push(["nipple_mask", "Ks", [3, 3, 3]]);
 	} else if (sqBO < sqAO && sqBO < sqCO && sqBO < sqDO) {
-		Ks = [1.75, 1.75, 1.75];
+		Ks = [2.5, 2.5, 2.5];
 		Ka = mB;
 		lKa = lB;
 		skin = "LightTone";
@@ -1573,7 +1635,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso light.jpg"]);
 		materials.push(["nipple_mask", "Ks", [2.5, 2.5, 2.5]]);
 	} else if (sqCO < sqBO && sqCO < sqAO && sqCO < sqDO) {
-		Ks = [1.5, 1.5, 1.5];
+		Ks = [2, 2, 2];
 		Ka = mC;
 		lKa = lC;
 		skin = "MidTone";
@@ -1586,7 +1648,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso mid.jpg"]);
 		materials.push(["nipple_mask", "Ks", [2, 2, 2]]);
 	} else if (sqDO < sqBO && sqDO < sqCO && sqDO < sqAO) {
-		Ks = [0.75, 0.75, 0.75];
+		Ks = [1, 1, 1];
 		Ka = mD;
 		lKa = lD;
 		skin = "DarkTone";
@@ -1634,7 +1696,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	materials.push([skin + "Genitalia", "Ks", Ks]);
 	materials.push([skin + "Genitalia", "r", r]);
 	materials.push([skin + "Genitalia", "Ni", Ni]);
-	materials.push(["nipple_mask", "r", r]);
+	materials.push(["nipple_mask", "r", rN]);
 	materials.push(["nipple_mask", "Ni", Ni]);
 
 	let pubicColor = App.Art.hexToRgb(extractColor(slave.pubicHColor));
@@ -1695,7 +1757,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	materials.push(["skindetail_fine_arms", "d", fineDetail]);
 	materials.push(["skindetail_fine_legs", "d", fineDetail]);
 
-	let veins = 1;
+	let veins = 0;
 	materials.push(["skindetail_veins_torso", "d", veins]);
 	materials.push(["skindetail_veins_face", "d", veins]);
 	materials.push(["skindetail_veins_arms", "d", veins]);
@@ -1784,10 +1846,10 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["posesExtremeHeels2", 1]);
 	}
 
-	if (hasBothArms(slave) && hasBothLegs(slave)) {
+	if (slave.arm.right && slave.arm.left && slave.leg.right && slave.leg.left) {
 		if (scene.inspect) {
-			morphs.push(["posesInspect", 1]);
-			morphs.push(["posesInspectGen", 1]);
+			morphs.push(["posesInspect2", 1]);
+			morphs.push(["posesInspectGen2", 1]);
 		} else if (slave.devotion > 50) {
 			morphs.push(["posesHigh", 1]);
 		} else if (slave.devotion > -20) {
@@ -1795,6 +1857,11 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		} else {
 			morphs.push(["posesLow", 1]);
 		}
+
+		if (slave.devotion <= 50) {
+			morphs.push(["posesArmsDown", -slave.weight/300/3.5]);
+			morphs.push(["posesLegsClosed", -slave.weight/300/3.5]);
+		}
 	}
 
 	if (slave.trust < 0) {
@@ -2136,7 +2203,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 	let foreheadShape = ["foreheadShapeNormal", "foreheadShapeRound", "foreheadShapeSmall"];
 	let forehead = Math.floor(App.Art.random() * foreheadShape.length);
 	if (forehead > 0) {
-		morphs.push(foreheadShape[forehead], 1);
+		morphs.push([foreheadShape[forehead], 1]);
 	}
 
 	switch (slave.faceShape) {
@@ -2154,6 +2221,144 @@ App.Art.applyMorphs = function(slave, scene, p) {
 			morphs.push(["faceShapeExotic", 1]); break;
 	}
 
+
+	/*
+	const morphs_eyes = [
+		["", 1],
+		["mEyeShape1",1],
+		["mEyeShape2",1],
+		["mEyeShape3",0.5],
+		["mEyeShape4",1],
+		["mEyeShape5",1],
+		["mEyeShape6",1],
+		["mEyeShape7",1],
+		["mEyeShape8",1],
+	];
+	const morphs_nose = [
+		["", 1],
+		["mNoseShape1",0.5],
+		["mNoseShape2",1],
+		["mNoseShape3",0.5],
+		["mNoseShape4",0.5],
+		["mNoseShape5",0.5],
+		["mNoseShape6",1],
+		["mNoseShape7",1],
+		["mNoseShape8",1],
+		["mNoseShape9",1],
+		["mNoseShape10",1],
+	];
+	const morphs_jaw = [
+		["", 1],
+		["mJawShape1", 0.5],
+		["mJawShape2", 0.5],
+		["mJawShape3", 0.75],
+		["mJawShape4", 0.5],
+		["mJawShape5", 1],
+		["mJawShape6", 0.5],
+	];
+	const morphs_lips = [
+		["", 1],
+		["mLipsShape1",1],
+		["mLipsShape2",1],
+		["mLipsShape3",1],
+		["mLipsShape4",1],
+		["mLipsShape5",1],
+		["mLipsShape6",1],
+		["mLipsShape7",1],
+		["mLipsShape8", 0.5],
+		["mLipsShape9",1],
+		["mLipsShape10", 0.75],
+		["mLipsShape11",1],
+		["mLipsShape12", 0.5],
+	];
+	const morphs_cheeks = [
+		["", 1],
+		["mCheeksShape1",0.5],
+		["mCheeksShape2",0.5],
+		["mCheeksShape3",0.5],
+		["mCheeksShape4",1],
+		["mCheeksShape5",1],
+		["mCheeksShape6",0.5],
+		["mCheeksShape7",1],
+		["mCheeksShape8",1],
+	];
+
+	const morphs_cheeks2 = [
+		["", 1],
+		["mCheeks2Shape1",0.5],
+		["mCheeks2Shape2",0.5],
+		["mCheeks2Shape3",1],
+		["mCheeks2Shape4",0.5],
+	];
+
+	const morphs_chin = [
+		["", 1],
+		["mChinShape1", 0.5],
+		["mChinShape2", 0.5],
+		["mChinShape3", 0.5],
+		["mChinShape4", 0.5],
+	];
+
+	const morphs_unique = [
+		["", 1],
+		["mUniqueShape1", 1],
+		["mUniqueShape2", 0.75],
+		["mUniqueShape3", 0.5],
+		["mUniqueShape4", 0.5],
+		["mUniqueShape5", 0.5],
+	];
+
+	const eyeSize = App.Art.random();
+	const morphs_other = [
+		["mChinTweak1", 0.3*App.Art.random()],
+		["mEyeTweak1", 0.4*eyeSize],
+		["mEyeTweak2", 0.4*eyeSize],
+		["mEyeTweak3", 0.25*eyeSize],
+		["mEyeTweak4", 0.3*(App.Art.random()*2-1)],
+		["mEyeTweak5", 0.5*(App.Art.random()*2-1)],
+	];
+
+	const morphs_forehead = [
+		["", 1],
+		["foreheadShapeRound", 1],
+		["foreheadShapeSmall", 1],
+	];
+
+	morphs.push(morphs_other[0]);
+	morphs.push(morphs_other[1]);
+	morphs.push(morphs_other[2]);
+	morphs.push(morphs_other[3]);
+	morphs.push(morphs_other[4]);
+	morphs.push(morphs_other[5]);
+
+	let forehead = Math.floor(App.Art.random() * morphs_forehead.length);
+	if (forehead > 0) {	morphs.push(morphs_forehead[forehead]); }
+
+	let unqiue = Math.floor(App.Art.random() * morphs_unique.length);
+	if (unqiue > 0) {	morphs.push(morphs_unique[unqiue]); }
+
+	let chin = Math.floor(App.Art.random() * morphs_chin.length);
+	if (chin > 0) {	morphs.push(morphs_chin[chin]); }
+
+	let cheeks2 = Math.floor(App.Art.random() * morphs_cheeks2.length);
+	if (cheeks2 > 0) {	morphs.push(morphs_cheeks2[cheeks2]); }
+
+	let cheeks = Math.floor(App.Art.random() * morphs_cheeks.length);
+	if (cheeks > 0) {	morphs.push(morphs_cheeks[cheeks]); }
+
+	let lips = Math.floor(App.Art.random() * morphs_lips.length);
+	if (lips > 0) {	morphs.push(morphs_lips[lips]); }
+
+	let jaw = Math.floor(App.Art.random() * morphs_jaw.length);
+	if (jaw > 0) {	morphs.push(morphs_jaw[jaw]); }
+
+	let nose = Math.floor(App.Art.random() * morphs_nose.length);
+	if (nose > 0) {	morphs.push(morphs_nose[nose]); }
+
+	let eyes = Math.floor(App.Art.random() * morphs_eyes.length);
+	if (eyes > 0) {	morphs.push(morphs_eyes[eyes]); }
+	*/
+
 	if (slave.boobs < 600) {
 		morphs.push(["boobShapeSmall", -(slave.boobs-600)/600]);
 	} else {
@@ -2169,7 +2374,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 			case "downward-facing":
 				morphs.push(["boobShapeDownward", ((slave.boobs-600)**(1/3)/16) * (175/p.height)]); break;
 			case "wide-set":
-				morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/9) * (175/p.height)]); break;
+				morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/4) * (175/p.height)]); break;
 			case "spherical":
 				// special case to make nipple work
 				if (slave.nipples === "flat" || slave.nipples === "inverted" || !p.applyNipples) {
@@ -2201,9 +2406,12 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		}
 	}
 
-	if (slave.foreskin !== 0 && !scene.inspect) {
-		morphs.push(["foreskin", 1]);
+	let shaftShape = ["shaftShape0", "shaftShape1", "shaftShape2", "shaftShape3", "shaftShape4", "shaftShape5", "shaftShape6"];
+	let shaft = Math.floor(App.Art.random() * shaftShape.length);
+	if (shaft > 0) {
+		morphs.push([shaftShape[shaft], 1]);
 	}
+
 	if (slave.dick === 0 && !(slave.scrotum <= 0 || slave.balls <= 0)) {
 		morphs.push(["dickRemove", 1]);
 	} else if (slave.dick !== 0) {
@@ -2220,7 +2428,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["ballsRemove", 1]);
 	} else {
 		if (slave.balls > 1) {
-			morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * (175/p.height))]);
+			morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * 0.6 *(175/p.height))]);
 		}
 
 		if (slave.scrotum > 0) {
@@ -2237,11 +2445,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["muscles", slave.muscles/33]);
 	}
 
-	if (scene.inspect) {
-		morphs.push(["belly", Math.max(0.7, slave.belly**(1/3)/24.6)]); // fix
-	} else {
-		morphs.push(["belly", slave.belly**(1/3)/24.6]);
-	}
+	morphs.push(["belly2", slave.belly**(1/3)/24.6]);
 
 	morphs.push(["hips", slave.hips/2]);
 
@@ -2258,7 +2462,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 	}
 
 	if (slave.weight >= 0) {
-		morphs.push(["weight", slave.weight/75]);
+		morphs.push(["weight2", slave.weight/300]);
 	} else {
 		morphs.push(["weightThin", -slave.weight/80]);
 	}
@@ -2269,20 +2473,24 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["physicalAgeOld", (slave.visualAge-20)/52]);
 	}
 
-	if (!hasLeftArm(slave)) {
+	if (!slave.arm.left) {
 		morphs.push(["amputeeLeftArm", 1]);
 	}
-	if (!hasRightArm(slave)) {
+	if (!slave.arm.right) {
 		morphs.push(["amputeeRightArm", 1]);
 	}
-	if (!hasLeftLeg(slave)) {
+	if (!slave.leg.left) {
 		morphs.push(["amputeeLeftLeg", 1]);
 	}
-	if (!hasRightLeg(slave)) {
+	if (!slave.leg.right) {
 		morphs.push(["amputeeRightLeg", 1]);
 	}
 
-	morphs.push(["offset", 3]); // only applies to clothes
+	if (slave.dick > 0 || slave.scrotum > 0 || slave.balls > 0) {
+		morphs.push(["bulge", Math.max(slave.dick/4, 0)]);
+	}
+
+	morphs.push(["offset", 2]); // only applies to clothes
 
 	App.Art.resetMorphs(scene);
 
diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js
index 5786cc818315cbaf5266b6255cd4612b3d1f83ec..0e93a67728bd7858a36929fb4aba35154c3b830b 100644
--- a/src/art/webgl/engine.js
+++ b/src/art/webgl/engine.js
@@ -13,22 +13,18 @@ App.Art.Engine = class {
 				in vec3 vertexPosition;
 				in vec2 textureCoordinate;
 
-				in vec3 vertexNormalMorph;
-				in vec3 vertexPositionMorph;
-
 				out vec2 textureCoord;
 				out vec3 normal;
 				out vec3 pos;
 				out vec4 shadowMap;
 
 				void main() {
-					vec4 posM = vec4(vertexPosition + vertexPositionMorph, 1.0);
-					gl_Position = matModelViewProjection * posM;
-					normal = normalize((matNormal * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz);
+					gl_Position = matModelViewProjection * vec4(vertexPosition, 1.0);
+					normal = normalize((matNormal * vec4(vertexNormal, 1.0)).xyz);
 
 					textureCoord = textureCoordinate;
-					pos = (matModelView * posM).xyz;
-					shadowMap = matModelViewProjectionShadow * posM;
+					pos = (matModelView * vec4(vertexPosition, 1.0)).xyz;
+					shadowMap = matModelViewProjectionShadow * vec4(vertexPosition, 1.0);
 				}`;
 	}
 
@@ -62,13 +58,12 @@ App.Art.Engine = class {
 				uniform mat4 matModel;
 
 				in vec3 vertexPosition;
-				in vec3 vertexPositionMorph;
 				in vec2 textureCoordinate;
 
 				out vec2 textureCoord;
 
 				void main() {
-					gl_Position = matModelViewProjectionShadow * vec4(vertexPosition + vertexPositionMorph, 1.0);
+					gl_Position = matModelViewProjectionShadow * vec4(vertexPosition, 1.0);
 					textureCoord = textureCoordinate;
 				}`;
 	}
@@ -188,6 +183,89 @@ App.Art.Engine = class {
 				}`;
 	}
 
+	getFsSourceSSS() {
+		return `#version 300 es
+				precision highp float;
+
+				out float iao;
+
+				in vec2 textureCoord;
+
+				uniform sampler2D gPosition;
+				uniform sampler2D gNormal;
+				uniform sampler2D texNoise;
+
+				uniform vec3 samples[32];
+
+				uniform mat4 projection;
+				uniform mat4 view;
+
+				uniform float radius;
+				uniform float bias;
+				uniform float scale;
+
+				void main() {
+					vec2 resolution = vec2(textureSize(gPosition, 0));
+					vec3 pos = texture(gPosition, textureCoord).xyz;
+					vec3 normal = -normalize(texture(gNormal, textureCoord).rgb);
+					// tile noise texture over screen based on screen dimensions divided by noise size
+					vec3 randomVec = normalize(texture(texNoise, textureCoord * resolution / vec2(3.0, 3.0)).xyz);
+
+					// create TBN
+					vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
+					vec3 bitangent = cross(normal, tangent);
+					mat3 TBN = mat3(tangent, bitangent, normal);
+
+					// iterate over the sample kernel and calculate occlusion factor
+					float occlusion = 0.0;
+					for(int i = 0; i < 32; i++)
+					{
+						// get sample position
+						vec3 samplePos = TBN * samples[i]; // from tangent to view-space
+						samplePos = pos + samplePos * radius; 
+						
+						// project sample position
+						vec4 offset = vec4(samplePos, 1.0);
+						offset = projection * offset; // from view to clip-space
+						offset.xy /= offset.w; // perspective divide
+						offset.xy = offset.xy * 0.5 + 0.5; // transform to range 0.0 - 1.0
+						
+						float sampleDepth = texture(gPosition, offset.xy).z;
+						
+						// range check & accumulate
+						float rangeCheck = smoothstep(0.0, 1.0, radius / abs(pos.z - sampleDepth));
+						occlusion += (sampleDepth <= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;           
+					}
+					iao = 1.0 - (occlusion / scale);
+				}`;
+	}
+
+	getFsSourceSSSBlur() {
+		return `#version 300 es
+				precision highp float;
+
+				out float iao;
+				in vec2 textureCoord;
+
+				uniform sampler2D sssInput;
+				uniform float blur;
+				
+				void main() 
+				{
+					vec2 texelSize = 1.0 / vec2(textureSize(sssInput, 0));
+					float result = 0.0;
+					for (float x = -blur; x <= blur; x++) 
+					{
+						for (float y = -blur; y <= blur; y++) 
+						{
+							vec2 offset = vec2(x, y) * texelSize;
+							result += texture(sssInput, textureCoord + offset).r;
+						}
+					}
+					iao = result / ((blur*2.0+1.0) * (blur*2.0+1.0));
+				}`;
+	}
+
 	getFsSourceForwardPass(dl, pl) {
 		return `#version 300 es
                 precision highp float;
@@ -210,7 +288,7 @@ App.Art.Engine = class {
 				uniform float sssPower;
 				uniform float sssDistortion;
 				uniform float sssIntensity;
-				uniform vec3 sssColor;
+				uniform float sssAmbient;
 
                 uniform float whiteM;
                 uniform float gammaY;
@@ -225,6 +303,7 @@ App.Art.Engine = class {
 				uniform float Ni;
 				uniform float r;
 				uniform float m;
+				uniform float sss;
 
 				uniform vec2 offset;
 				uniform float angle;
@@ -233,6 +312,7 @@ App.Art.Engine = class {
 				uniform float sNormals;
 				uniform float sSSAO;
 				uniform float sAO;
+				uniform float sIAO;
 				uniform float sAmbient;
 				uniform float sAlbedo;
 				uniform float sSpecular;
@@ -250,7 +330,7 @@ App.Art.Engine = class {
                 
                 uniform vec3 cameraPos;
 
-                uniform sampler2D textSampler[9];
+                uniform sampler2D textSampler[10];
 
 				in vec2 textureCoord;
 				in vec3 normal;
@@ -352,6 +432,7 @@ App.Art.Engine = class {
 					vec3 map_Ke = vec3(0.0,0.0,0.0);
 					float map_d = 1.0;
 					float ao = 1.0;
+					float iao = 1.0;
 					float shadow = 1.0;
 					float roughness = 0.0;
 					float metallic = 0.0;
@@ -376,6 +457,9 @@ App.Art.Engine = class {
 					if (sSSAO == 1.0)
 						ao = texture(textSampler[0], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
 
+					if (sSSS == 1.0)
+						iao = texture(textSampler[9], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
+
 					if (sAlbedo == 1.0)
                         map_Ka = Ka * texture(textSampler[2], coord).rgb;
 
@@ -394,15 +478,15 @@ App.Art.Engine = class {
 						float bias = max(shadowBiasMax * (1.0 - dot(new_normal, shadowDir)), shadowBiasMin); 
 
 						vec2 texelSize = 1.0 / vec2(textureSize(textSampler[8], 0));
-						for(int x = -1; x <= 1; ++x)
+						for(int x = -2; x <= 2; ++x)
 						{
-							for(int y = -1; y <= 1; ++y)
+							for(int y = -2; y <= 2; ++y)
 							{
 								float pcfDepth = texture(textSampler[8], projCoords.xy + vec2(x, y) * texelSize).r; 
 								shadow += currentDepth - bias < pcfDepth ? 1.0 : 0.0;
 							}    
 						}
-						shadow /= 9.0;
+						shadow /= 25.0;
 						shadow = min(shadow + (1.0-shadowIntensity), 1.0);
 					}
 
@@ -429,7 +513,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						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;
@@ -441,21 +525,26 @@ App.Art.Engine = class {
 						
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						if (map_d >= 0.85 || overlay == 1.0) {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						}
+						else {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL;
+						}
 
 						// ambient lighting
-						vec3 La = kD * albedo * lightAmb[i] * (1.0-pow(NdotL, 0.5));
+						vec3 La = kD * albedo * lightAmb[i];
 
 						if (map_d >= 0.85 || overlay == 1.0) {
-							// ao
-							Lo += La * ao;
-
 							// sss
-							if (sSSS == 1.0) {
+							if (sSSS == 1.0 && sss == 1.0) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
-								Lo += sssColor * VdotH * kD * albedo * sssIntensity;
+								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
 							}
+
+							// ao
+							Lo += La * ao;
 						} else {
 							Lo += La;
 						}
@@ -473,7 +562,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						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;
@@ -485,21 +574,26 @@ App.Art.Engine = class {
 						
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						if (map_d >= 0.85 || overlay == 1.0) {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						}
+						else {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL;
+						}
 
 						// ambient lighting
-						vec3 La = kD * albedo * pointLightAmb[i] * (1.0-pow(NdotL, 0.5));
+						vec3 La = kD * albedo * pointLightAmb[i];
 
 						if (map_d >= 0.85 || overlay == 1.0) {
-							// ao
-							Lo += La * ao;
-
 							// sss
-							if (sSSS == 1.0) {
+							if (sSSS == 1.0 && sss == 1.0) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
-								Lo += sssColor * VdotH * kD * albedo * sssIntensity;
+								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
 							}
+
+							// ao
+							Lo += La * ao;
 						} else {
 							Lo += La;
 						}
@@ -523,11 +617,14 @@ App.Art.Engine = class {
 						c = pow(c, vec3(1.0/gammaY));
 					
 					if (sNormals == 1.0)
-						c = new_normal;
+						c = new_normal*0.5+0.5;
 
 					if (sAO == 1.0)
 						c = vec3(ao);
 
+					if (sIAO == 1.0)
+						c = vec3(iao);
+
 					outputColor = vec4(c*map_d, map_d);
 				}`;
 	}
@@ -563,42 +660,51 @@ App.Art.Engine = class {
 			modelBuffers.verticesPositionBuffer = [];
 			modelBuffers.verticesNormalBuffer = [];
 			modelBuffers.verticesTextureCoordBuffer = [];
-			modelBuffers.vertexCount = [];
-			modelBuffers.verticesMorphBuffer = [];
-			modelBuffers.verticesNormalMorphBuffer = [];
 
+			modelBuffers.vertexPositionBuffer = [];
 			modelBuffers.verticesIndexBuffer = [];
 			modelBuffers.indexSizes = [];
+			modelBuffers.figureIndices = [];
+			modelBuffers.figureSeemMaps = [];
 			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.vertexCount[i] = this.gl.getBufferParameter(this.gl.ARRAY_BUFFER, this.gl.BUFFER_SIZE)/4;
+				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, this.base64ToFloat(modelData.figures[i].vertsn), this.gl.STATIC_DRAW);
+				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]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].texts), this.gl.STATIC_DRAW);
 
-				// return dummy morph
-				modelBuffers.verticesMorphBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW);
-
-				modelBuffers.verticesNormalMorphBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW);
-
+				modelBuffers.figureIndices[i] = [];
 				for (let j=0; j < modelData.figures[i].surfaces.length; j++, count++) {
 					modelBuffers.verticesIndexBuffer[count] = this.gl.createBuffer();
 					this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
 					let intArray = this.base64ToInt(modelData.figures[i].surfaces[j].vertsi);
+					modelBuffers.figureIndices[i] = [...modelBuffers.figureIndices[i], ...intArray];
 					this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, intArray, this.gl.STATIC_DRAW);
 					modelBuffers.indexSizes[count] = intArray.length;
 				}
+
+				let seems = this.base64ToInt(modelData.figures[i].seems);
+				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 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;
 			}
 
 			this.initMorphs(modelBuffers, modelData, dir);
@@ -627,6 +733,12 @@ App.Art.Engine = class {
 		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBufferBlur);
 		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.sssColorBuffer);
+		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.sssColorBufferBlur);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 	}
 
@@ -709,6 +821,30 @@ App.Art.Engine = class {
 		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.ssaoColorBufferBlur, 0);
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 
+		// SSS color buffer
+		buffers.sssColorBuffer = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBuffer);
+		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.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.sssFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBuffer, 0);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		// SSS blur buffer
+		buffers.sssColorBufferBlur = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBufferBlur);
+		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.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.sssBlurFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssBlurFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBufferBlur, 0);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
 		const random = function(seed) {
 			let x = Math.sin(seed+1) * 10000;
 			return x - Math.floor(x);
@@ -749,15 +885,12 @@ App.Art.Engine = class {
 
 		let promisedMorphs = [];
 		modelBuffers.vertexPositionMorphs = [];
-		modelBuffers.vertexNormalMorphs = [];
 		modelBuffers.vertexIndexMorphs = [];
 		for (let f=0; f < modelData.figures.length; f++) {
 			modelBuffers.vertexPositionMorphs[f] = [];
-			modelBuffers.vertexNormalMorphs[f] = [];
 			modelBuffers.vertexIndexMorphs[f] = [];
 			for (let m=0; m < modelData.morphCount; m++) {
 				modelBuffers.vertexPositionMorphs[f].push(new Float32Array(0));
-				modelBuffers.vertexNormalMorphs[f].push(new Float32Array(0));
 				modelBuffers.vertexIndexMorphs[f].push(new Int32Array(0));
 			}
 
@@ -783,16 +916,16 @@ App.Art.Engine = class {
 		return new Promise(function(resolve, reject) {
 			let script = document.createElement("script");
 			script.onload = function() {
-				let morph = window.sceneBlocks[path.split("/").slice(-1)[0]];
+				let name = path.split("/").slice(-1)[0];
+				let morph = window.sceneBlocks[name];
 
-				for (let i=0; i < morph.length; i+=3) {
-					modelBuffers.vertexPositionMorphs[m][i/3] = engine.base64ToFloat(morph[i+0]);
-					modelBuffers.vertexNormalMorphs[m][i/3] = engine.base64ToFloat(morph[i+1]);
+				for (let i=0; i < morph.length; i+=2) {
+					modelBuffers.vertexPositionMorphs[m][i/2] = engine.base64ToFloat(morph[i+0]);
 					// reconstruct compressed indices
-					modelBuffers.vertexIndexMorphs[m][i/3] = engine.base64ToInt(morph[i+2]).map((sum => value => sum += value)(0));
+					modelBuffers.vertexIndexMorphs[m][i/2] = engine.base64ToInt(morph[i+1]).map((sum => value => sum += value)(0));
 				}
 
-				morph = null; // let garbage collector clean
+				window.sceneBlocks[name] = null; // let garbage collector clean
 				resolve();
 			};
 			script.onerror = function(e) {
@@ -830,7 +963,8 @@ App.Art.Engine = class {
 		return new Promise(function(resolve, reject) {
 			let script = document.createElement("script");
 			script.onload = function() {
-				let url = window.sceneBlocks[path.split("/").slice(-1)[0]][0];
+				let name = path.split("/").slice(-1)[0];
+				let url = window.sceneBlocks[name][0];
 
 				let img = document.createElement("img");
 				img.onload = function() {
@@ -868,7 +1002,7 @@ App.Art.Engine = class {
 					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
 					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
 
-					url = null; // let garbage collector clean
+					window.sceneBlocks[name] = null; // let garbage collector clean
 					resolve();
 				};
 				img.src = url;
@@ -911,6 +1045,14 @@ App.Art.Engine = class {
 		this.gl.shaderSource(fragmentShaderSSAOBlur, this.getFsSourceSSAOBlur());
 		this.gl.compileShader(fragmentShaderSSAOBlur);
 
+		let fragmentShaderSSS = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSS, this.getFsSourceSSS());
+		this.gl.compileShader(fragmentShaderSSS);
+
+		let fragmentShaderSSSBlur = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSSBlur, this.getFsSourceSSSBlur());
+		this.gl.compileShader(fragmentShaderSSSBlur);
+
 		let fragmentShaderForwardPass = this.gl.createShader(this.gl.FRAGMENT_SHADER);
 		this.gl.shaderSource(fragmentShaderForwardPass, this.getFsSourceForwardPass(sceneParams.directionalLights.length, sceneParams.pointLights.length));
 		this.gl.compileShader(fragmentShaderForwardPass);
@@ -935,6 +1077,16 @@ App.Art.Engine = class {
 		this.gl.attachShader(this.shaderProgramSSAOBlur, fragmentShaderSSAOBlur);
 		this.gl.linkProgram(this.shaderProgramSSAOBlur);
 
+		this.shaderProgramSSS = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSS, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSS, fragmentShaderSSS);
+		this.gl.linkProgram(this.shaderProgramSSS);
+
+		this.shaderProgramSSSBlur = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSSBlur, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSSBlur, fragmentShaderSSSBlur);
+		this.gl.linkProgram(this.shaderProgramSSSBlur);
+
 		this.shaderProgramForwardPass = this.gl.createProgram();
 		this.gl.attachShader(this.shaderProgramForwardPass, vertexShaderGeometry);
 		this.gl.attachShader(this.shaderProgramForwardPass, fragmentShaderForwardPass);
@@ -951,18 +1103,9 @@ App.Art.Engine = class {
 		this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormal");
 		this.gl.enableVertexAttribArray(this.vertexNormalAttribute);
 
-		this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormalMorph");
-		this.gl.enableVertexAttribArray(this.vertexNormalMorphAttribute);
-
-		this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexPositionMorph");
-		this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute);
-
 		this.vertexPositionAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPosition");
 		this.gl.enableVertexAttribArray(this.vertexPositionAttribute2);
 
-		this.vertexPositionMorphAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPositionMorph");
-		this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute2);
-
 		this.textureCoordAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "textureCoordinate");
 		this.gl.enableVertexAttribArray(this.textureCoordAttribute2);
 
@@ -971,6 +1114,12 @@ App.Art.Engine = class {
 
 		this.quadTextureAttribute = this.gl.getAttribLocation(this.shaderProgramSSAO, "textureCoordinate");
 		this.gl.enableVertexAttribArray(this.quadTextureAttribute);
+
+		this.quadPositionAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "vertexPosition");
+		this.gl.enableVertexAttribArray(this.quadPositionAttribute3);
+
+		this.quadTextureAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "textureCoordinate");
+		this.gl.enableVertexAttribArray(this.quadTextureAttribute3);
 	}
 
 	bind(sceneData, sceneParams, dir) {
@@ -1036,6 +1185,20 @@ App.Art.Engine = class {
 			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 		}
 
+		if (V.setSSS) {
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssFBO);
+			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+			this.gl.useProgram(this.shaderProgramSSS);
+			this.drawSSS(sceneParams);
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssBlurFBO);
+			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+			this.gl.useProgram(this.shaderProgramSSSBlur);
+			this.drawSSSBlur(sceneParams);
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+		}
+
 		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
 		this.gl.useProgram(this.shaderProgramForwardPass);
 		this.drawForwardPass(sceneParams);
@@ -1146,12 +1309,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
 				// bind materials per surface and set uniforms
 				for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) {
 					if (!modelParams.figures[i].surfaces[j].visible) {
@@ -1202,9 +1359,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexPositionAttribute2, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute2, 3, this.gl.FLOAT, false, 0, 0);
-
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
 				this.gl.vertexAttribPointer(this.textureCoordAttribute2, 2, this.gl.FLOAT, false, 0, 0);
 
@@ -1283,10 +1437,60 @@ App.Art.Engine = class {
 		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
 	}
 
+	drawSSS(sceneParams) {
+		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramSSS, "projection"), false, new Float32Array(this.matrixFlatten(this.buffers.matProj)));
+
+		for (let i = 0; i < 32; ++i) {
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramSSS, "samples[" + i + "]"), this.buffers.ssaoKernel[i]);
+		}
+
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "radius"), sceneParams.sss.radius);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "bias"), sceneParams.sss.bias);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "scale"), sceneParams.sss.scale);
+
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gPosition);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gPosition"), 0);
+
+		this.gl.activeTexture(this.gl.TEXTURE1);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gNormal);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gNormal"), 1);
+
+		this.gl.activeTexture(this.gl.TEXTURE2);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.noiseTexture);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "texNoise"), 2);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
+	drawSSSBlur(sceneParams) {
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBuffer);
+
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSSBlur, "blur"), sceneParams.sss.blur);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
 	drawForwardPass(sceneParams) {
 		// 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);
@@ -1306,6 +1510,10 @@ App.Art.Engine = class {
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "metallic"), sceneParams.pbr.metallic);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "fresnel"), sceneParams.pbr.fresnel);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "ssaoInt"), sceneParams.ssao.intensity);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssPower"), sceneParams.sss.power);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssAmbient"), sceneParams.sss.ambient);
 
 		for (let i = 0; i < sceneParams.directionalLights.length; i++) {
 			let lightVect = this.polarToCart(this.degreeToRad(sceneParams.directionalLights[i].yr), this.degreeToRad(sceneParams.directionalLights[i].xr));
@@ -1360,12 +1568,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
 				// bind materials per surface and set uniforms
 				for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) {
 					if (!modelParams.figures[i].surfaces[j].visible) {
@@ -1423,6 +1625,10 @@ App.Art.Engine = class {
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gShadowDepth);
 							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[8]"), 8);
 
+							this.gl.activeTexture(this.gl.TEXTURE9);
+							this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[9]"), 9);
+
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "d"), mat.d);
 							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ka"), mat.Ka);
 							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ks"), mat.Ks);
@@ -1435,10 +1641,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, "sssPower"), sceneParams.sss.power);
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion);
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssColor"), mat.Ksss);
+							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sss"), mat.sss);
 
 							// draw materials
 							this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
@@ -1457,36 +1660,63 @@ App.Art.Engine = class {
 					continue;
 				}
 
-				let vertexPositionMorph = new Float32Array(modelBuffers.vertexCount[f]);
-				let vertexNormalMorph = new Float32Array(modelBuffers.vertexCount[f]);
+				let vertexPosition = new Float32Array(modelBuffers.vertexPositionBuffer[f]);
+				let vertexNormal = new Float32Array(vertexPosition.length);
 
 				for (let m=0; m < modelParams.morphs.length; m++) {
 					let morphValue = modelParams.morphs[m].value;
 
 					if (morphValue !== 0) {
 						let vp = modelBuffers.vertexPositionMorphs[f][m];
-						let vn = modelBuffers.vertexNormalMorphs[f][m];
 						let vi = modelBuffers.vertexIndexMorphs[f][m];
 
 						if (morphValue === 1) {
 							for (let j = 0; j < vi.length; j++) {
-								vertexPositionMorph[vi[j]] += vp[j];
-								vertexNormalMorph[vi[j]] += vn[j];
+								vertexPosition[vi[j]] += vp[j];
 							}
 						} else {
 							for (let j=0; j < vi.length; j++) {
-								vertexPositionMorph[vi[j]] += vp[j] * morphValue;
-								vertexNormalMorph[vi[j]] += vn[j] * morphValue;
+								vertexPosition[vi[j]] += vp[j] * morphValue;
 							}
 						}
 					}
 				}
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[f]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPositionMorph, this.gl.STATIC_DRAW);
+				// recalculate normals
+				let indices = modelBuffers.figureIndices[f];
+				for (let j=0; j < indices.length; j+=3) {
+					let idx1 = indices[j]*3;
+					let idx2 = indices[j+1]*3;
+					let idx3 = indices[j+2]*3;
+
+					let v1 = [vertexPosition[idx1], vertexPosition[idx1+1], vertexPosition[idx1+2]];
+					let v2 = [vertexPosition[idx2], vertexPosition[idx2+1], vertexPosition[idx2+2]];
+					let v3 = [vertexPosition[idx3], vertexPosition[idx3+1], vertexPosition[idx3+2]];
+
+					let n = this.vectorNormalize(this.vectorCrossProduct(this.vectorSub(v2, v1), this.vectorSub(v1, v3)));
+
+					vertexNormal[idx1] += n[0];
+					vertexNormal[idx1+1] += n[1];
+					vertexNormal[idx1+2] += n[2];
+					vertexNormal[idx2] += n[0];
+					vertexNormal[idx2+1] += n[1];
+					vertexNormal[idx2+2] += n[2];
+					vertexNormal[idx3] += n[0];
+					vertexNormal[idx3+1] += n[1];
+					vertexNormal[idx3+2] += n[2];
+				}
+
+				// fix edge normals
+				let seemMap = modelBuffers.figureSeemMaps[f];
+				for (let j=0; j <= seemMap.length-2; j+=2) {
+					vertexNormal[seemMap[j]] = vertexNormal[seemMap[j+1]];
+				}
+
+				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.verticesNormalMorphBuffer[f]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormalMorph, 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);
diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js
index 23a2290a7e8aad613e332e97320263f694f88960..f600afd7b036379ecbf24df7945d16275e696979 100644
--- a/src/art/webgl/ui.js
+++ b/src/art/webgl/ui.js
@@ -46,6 +46,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	// canvas
 	let cvs = document.createElement("canvas");
 	cvs.setAttribute("style", "position: absolute;");
+	cvs.setAttribute("oncontextmenu", "return false;");
 
 	// btnLockView
 	let btnLockView = document.createElement("input");
@@ -82,11 +83,18 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		btnLockView.setAttribute("src", view.lockView ? lockViewDisabled : lockViewEnabled);
 	}
 
+	let panY;
+	let zoomZ;
+	let zoomY;
+	let oz;
+	let oy;
+	let oxr;
+
 	// events
 	btnInspectView.onclick = function(e){
 		updateLinkedButtons("inspect");
 
-		view.yr = 180;
+		view.yr = 0;
 		view.inspect = true;
 		App.Art.Frame(slave, scene, view, p);
 		render();
@@ -100,6 +108,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	btnFaceView.onclick = function(e){
 		updateLinkedButtons("face");
 
+		view.camera.x = 0;
 		view.camera.y = p.height-5;
 		view.yr = 0;
 		view.camera.xr = -6;
@@ -124,8 +133,25 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 
 		updateLinkedButtons("move");
 
-		view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
-		view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+		if (e.buttons === 1) { // left click
+			panY = panY + e.movementY/7*V.setPanSpeed;
+			view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
+			view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+		}
+		if (e.buttons === 2) { // right click
+			view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+			view.camera.xr = view.camera.xr - e.movementY*1*V.setRotationSpeed;
+			view.camera.xr = Math.clamp(view.camera.xr, -89, 89);
+
+			let r = (-oz-zoomZ)/Math.cos(oxr*Math.PI/180);
+			view.camera.y = oy + panY + zoomY - Math.sin((view.camera.xr)*Math.PI/180) * r + Math.sin(oxr*Math.PI/180)*r;
+			view.camera.z = Math.cos((view.camera.xr)*Math.PI/180) * -r;
+		}
+		if (e.buttons === 4) { // wheel click
+			panY = panY + e.movementY/7*V.setPanSpeed;
+			view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
+			view.camera.x = view.camera.x - e.movementX/7*V.setPanSpeed;
+		}
 		render();
 	};
 
@@ -136,6 +162,12 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 			render();
 			return;
 		}
+		oz = view.camera.z;
+		oy = view.camera.y;
+		oxr = view.camera.xr;
+		panY = 0;
+		zoomZ = 0;
+		zoomY = 0;
 		e.preventDefault();
 		e.stopPropagation();
 		App.Art.isDraggingCanvas=true;
@@ -163,10 +195,15 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		// zoom speed based on distance from origin, and along direction of camera
 		let zOld = view.camera.z;
 		let magnitude = e.deltaY/(10/V.setZoomSpeed) * (-view.camera.z/50 + 0.2);
+
 		let zDistance = Math.cos(-view.camera.xr * (Math.PI/180)) * magnitude;
 		view.camera.z -= zDistance;
 		view.camera.z = Math.clamp(view.camera.z, -900, -10);
-		view.camera.y += Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance;
+		zoomZ -= zDistance;
+
+		let yDistance = Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance;
+		view.camera.y += yDistance;
+		zoomY += yDistance;
 
 		render();
 		return false;
@@ -224,47 +261,6 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		updateLinkedButtons();
 		render();
 	}
-
-	/*
-	if (artSize === 3) {
-		let cvs2 = document.createElement("canvas");
-		cvs2.setAttribute("style", "position: absolute; top: 530px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;");
-
-		let cvs3 = document.createElement("canvas");
-		cvs3.setAttribute("style", "position: absolute; top: 530px; left: 156px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;");
-
-		let oldYr = view.transform.yr;
-		// let oldCamera = view.camera;
-
-		view.camera.y = p.height * 8 / 10;
-		view.camera.z = -65 - (Math.sqrt(slave.boobs)/10);
-
-		cvs2.width = 150;
-		cvs2.height = 150;
-		view.rwidth = cvs2.width * V.setSuperSampling;
-		view.rheight = cvs2.height * V.setSuperSampling;
-		App.Art.engine.render(scene, cvs2);
-
-		view.camera.y = p.height * 5.5 / 10;
-		view.camera.z = -65;
-		view.transform.yr = -180;
-
-		cvs3.width = 150;
-		cvs3.height = 150;
-		view.rwidth = cvs3.width * V.setSuperSampling;
-		view.rheight = cvs3.height * V.setSuperSampling;
-		App.Art.engine.render(scene, cvs3);
-
-		container.appendChild(cvs2);
-		container.appendChild(cvs3);
-
-		App.Art.Frame(slave, scene, view, p);
-
-		view.rwidth = cvs.width * V.setSuperSampling;
-		view.rheight = cvs.height * V.setSuperSampling;
-		view.transform.yr = oldYr;
-		// view.camera = oldCamera;
-	}*/
 };
 
 App.Art.Frame = function(slave, scene, view, p) {
@@ -308,11 +304,13 @@ App.Art.AutoFrame = function(view, slaveHeight, cameraHeight, window, offset) {
 
 	view.camera.z = -h;
 	view.camera.y = cameraHeight;
+	view.camera.x = 0;
 	view.camera.xr = -rot;
 };
 
 App.Art.FixedFrame = function(view) {
 	view.camera.z = -282;
 	view.camera.y = 123;
+	view.camera.x = 0;
 	view.camera.xr = -6;
 };
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index 38f0c8a7d5a50de9e5d74ac9342735b9b0ae3b34..95170f9e8cc052facbf22bf520fe12ca6d9712a4 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -1167,7 +1167,7 @@ App.UI.artOptions = function() {
 				options.addOption("Clothing erection bulges are", "showClothingErection")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 			} else if (V.imageChoice === 4) {
-				options.addComment(`This art is currently (12/12/21) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is.
+				options.addComment(`This art is currently (05/19/22) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is.
 				Then <b>refresh</b> the page.
 				Create the resources folder if it does not exist. <span class="warning">(Android/MacOS not supported)</span>`);
 
@@ -1203,6 +1203,8 @@ App.UI.artOptions = function() {
 					.addComment("Recommended to be between 0.5 and 1.5.");
 				options.addOption("Ambient Occlusion", "setSSAO")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
+				options.addOption("SubSurface Scattering", "setSSS")
+					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 				options.addOption("Shadowmapping", "setShadowMapping")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 				options.addOption("Tonemapping", "setTonemapping")