code:
App.Art.hexToRgb = function(hex) {
	hex = hex.replace('#', '');
	let r = parseInt(hex.substring(0, 2), 16);
	let g = parseInt(hex.substring(2, 4), 16);
	let b = parseInt(hex.substring(4, 6), 16);
	return [r/255, g/255, b/255];
};

App.Art.seed = 0;

App.Art.random = function() {
	App.Art.seed += 1;
	let x = Math.sin(App.Art.seed) * 10000;
	return x - Math.floor(x);
};

App.Art.getMaterialById = function(scene, id) {
	for (const material of scene.materials) {
		if (material.matId === id) {
			return material;
		}
	}
	return null;
};

App.Art.getMorphById = function(scene, id) {
	for (const morph of scene.models[0].morphs) {
		if (morph.morphId === id) {
			return morph;
		}
	}
	return null;
};

App.Art.getSurfaceById = function(scene, id) {
	for (const figure of scene.models[0].figures) {
		for (const surface of figure.surfaces) {
			if (surface.surfaceId === id) {
				return surface;
			}
		}
	}
	return null;
};

App.Art.getMatIdsBySurface = function(scene, id) {
	for (const figure of scene.models[0].figures) {
		for (const surface of figure.surfaces) {
			if (surface .surfaceId === id) {
				return surface.matIds;
			}
		}
	}
};

App.Art.resetMorphs = function(scene) {
	for (let i =0; i < scene.models[0].morphs.length; i++) {
		scene.models[0].morphs[i].value = App.Art.defaultScene.models[0].morphs[i].value;
	}
};

App.Art.getArtParams = function(slave) {
	let p = {};

	p.hideDick = false;
	p.hideVagina = false;
	p.hideHair = false;
	p.applyNipples = true;
	p.applyPumps = false;
	p.applyExtremeHeels = false;
	p.applyExtremeHeels2 = false;
	p.applyPanty = true;
	p.animVars = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], ["", "", "", "", "", ""], [0, 0, 0, 0, 0, 0], jsEither([true, false])];

	p.underage = slave.age < 18 || slave.visualAge < 18;
	p.height = Math.max(slave.height, 140); // clamp underage

	return p;
};

App.Art.applyFigures = function(slave, scene, p) {
	App.Art.seed = slave.ID;
	let figures = [];

	switch (slave.clothes) {
		case "attractive lingerie":
			figures.push("AS2_Body1", "AS2_Socks");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			break;
		case "attractive lingerie for a pregnant woman":
			figures.push("AS2_Babydoll2", "AS2_Pantie", "AS2_Socks_5506");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			break;
		case "a comfortable bodysuit":
			figures.push("Dark Princess Bodysuit", "Dark Princess Boots");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.applyPumps = true;
			break;
		case "a slutty klan robe":
			figures.push("Slutty Klan Robe");
			p.applyPanty = false;
			p.hideDick = true;
			p.hideHair = true;
			p.applyNipples = false;
			break;
		case "a klan robe":
			figures.push("Klan Robe");
			p.applyPanty = false;
			p.hideDick = true;
			p.hideHair = true;
			p.applyNipples = false;
			break;
		case "a bunny outfit":
			figures.push("Bunny Suit", "Bunny Gloves", "Bunny Chocker", "Bunny Shoes", "Bunny Stocking", "Bunny Ears");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.applyPumps = true;
			break;
		case "a penitent nuns habit":
			figures.push("Nun_Outfit");
			p.applyPanty = false;
			p.hideDick = true;
			p.hideHair = true;
			p.applyNipples = false;
			break;
		case "a fallen nuns habit":
			figures.push("NUN OUTFIT", "NUN HAT", "NUN PANTIES", "NUN STOCKINGS");
			p.hideDick = true;
			p.hideHair = true;
			p.applyNipples = false;
			break;
		case "an apron":
			figures.push("Apron");
			p.hideDick = true;
			p.applyNipples = false;
			break;
		case "a burkini":
			figures.push("Burkini");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "a burqa":
			figures.push("Burqa");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "harem gauze":
			figures.push("Harem Gauze Shocks", "Harem Gauze Slutty Blouse", "Harem Gauze Underpant", "Harem Gauze Vail");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			break;
		case "a mounty outfit":
			figures.push("Mounty Hat", "Mounty Jacket", "Mounty Pants");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "a hijab and abaya":
			figures.push("Hijab And Abaya");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "uncomfortable straps":
			figures.push("Uncomfortable Straps", "Uncomfortable Bracelets", "Uncomfortable LegsBracelets");
			p.hideDick = false;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "a toga":
			figures.push("Toga");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a schutzstaffel uniform":
			figures.push("Schutzstaffel Uniform");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a slutty schutzstaffel uniform":
			figures.push("Slutty Schutzstaffel Uniform");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a slave gown":
			figures.push("Slave Gown");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a long qipao":
			figures.push("Long Qipao");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a maternity dress":
			figures.push("Maternity Dress");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a nice nurse outfit":
			figures.push("Nurse Uniform", "Nurse Hat");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a slutty nurse outfit":
			figures.push("Slutty Nurse Bra", "Slutty Nurse Dress", "Slutty Nurse Necklace", "Slutty Nurse Underwear", "Slutty Nurse Boots", "Slutty Nurse Cap");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			p.applyPumps = true;
			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 = false;
			p.applyNipples = false;
			p.hideHair = false;
			p.applyPumps = true;
			break;
		case "a nice maid outfit":
			figures.push("Maid Band", "Maid Corset", "Maid Collar", "Maid Panty", "Maid Skirt", "Maid Sleeve Left", "Maid Sleeve Right", "Maid Stocking Left", "Maid Stocking Right", "Maid Top");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a police uniform":
			figures.push("Police Cap", "Police Outfit", "Police Shoes", "Police Belt", "Police Gun");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a biyelgee costume":
			figures.push("Biyelgee Dress", "Biyelgee Hat");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a one-piece swimsuit":
			figures.push("Swimsuit");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a sports bra":
			figures.push("Sports Bra");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a striped bra":
			figures.push("Bra");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a sweater":
			figures.push("Sweater");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a sweater and cutoffs":
			figures.push("Sweater", "Cutoffs");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a sweater and panties":
			figures.push("Sweater", "Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a t-shirt":
			figures.push("Shirt");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a t-shirt and jeans":
			figures.push("Shirt", "Jeans");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a t-shirt and panties":
			figures.push("Shirt", "Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a t-shirt and thong":
			if (!p.underage) {
				figures.push("Shirt", "Thong");
				p.hideDick = true;
				p.applyNipples = false;
				p.hideHair = false;
			}
			break;
		case "a tank-top":
			figures.push("Tank Top");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a tank-top and panties":
			figures.push("Tank Top", "Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a thong":
			if (!p.underage) {
				figures.push("Thong");
				p.hideDick = true;
				p.applyNipples = true;
				p.hideHair = false;
			}
			break;
		case "a tube top":
			figures.push("Tube Top");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a tube top and thong":
			if (!p.underage) {
				figures.push("Tube Top", "Thong");
				p.hideDick = true;
				p.applyNipples = false;
				p.hideHair = false;
			}
			break;
		case "an oversized t-shirt":
			figures.push("Oversized Shirt");
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "an oversized t-shirt and boyshorts":
			figures.push("Oversized Shirt", "Boy Shorts");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "boyshorts":
			figures.push("Boy Shorts");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "panties":
			figures.push("Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "pasties":
			figures.push("Pasties Left", "Pasties Right");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "panties and pasties":
			figures.push("Pasties Left", "Pasties Right", "Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "spats and a tank top":
			figures.push("Spats", "Tank Top");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "sport shorts":
			figures.push("Sports Shorts");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "sport shorts and a sports bra":
			figures.push("Sports Shorts", "Sports Bra");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "sport shorts and a t-shirt":
			figures.push("Sports Shorts", "Shirt");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "stretch pants and a crop-top":
			figures.push("Stretch Pants", "Crop Top");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "leather pants and a tube top":
			figures.push("Leather Pants", "Tube Top");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "leather pants and pasties":
			figures.push("Leather Pants", "Pasties Left", "Pasties Right");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "leather pants":
			figures.push("Leather Pants");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "striped underwear":
			figures.push("Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "cutoffs":
			figures.push("Cutoffs");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "cutoffs and a t-shirt":
			figures.push("Cutoffs", "Shirt");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "jeans":
			figures.push("Jeans");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "a button-up shirt":
			figures.push("Buttonup Shirt");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a button-up shirt and panties":
			figures.push("Buttonup Shirt", "Panties");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a bra":
			figures.push("Bra");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a skimpy loincloth":
			figures.push("Loincloth Skirt");
			p.hideDick = true;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "battlearmor":
			figures.push("Battle Armor");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "a scalemail bikini":
			figures.push("Scalemail Bikini");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a mini dress":
			figures.push("Mini Dress");
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "a cybersuit":
			figures.push("Cybersuit");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = true;
			break;
		case "slutty jewelry":
			figures.push("Bangles");
			p.hideDick = false;
			p.applyNipples = true;
			p.hideHair = false;
			break;
		case "a slutty outfit":
			figures.push("Slutty Outfit");
			p.hideDick = false;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "overalls":
			figures.push("Overalls");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "restrictive latex":
			if (!p.underage) {
				figures.push("Restrictive Latex");
				p.hideDick = false;
				p.applyNipples = true;
				p.hideHair = true;
			}
			break;
		case "clubslut netting":
			if (!p.underage) {
				figures.push("Clubslut Netting");
				p.hideDick = false;
				p.applyNipples = false;
				p.hideHair = false;
			}
			break;
		case "a cheerleader outfit":
			figures.push("Cheerleader");
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			p.applyPanty = true;
			break;
		case "kitty lingerie":
			figures.push("Kitty Choker", "Kitty Top", "Kitty Panty");
			p.applyPanty = false;
			p.hideDick = true;
			p.applyNipples = false;
			p.hideHair = false;
			break;
		case "striped panties": // placeholder
			figures.push("Simple Panty");
			p.hideDick = true;
			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) {
		if (p.applyPanty) {
			figures.push("Simple Panty");
		}
		p.hideDick = true;
	}

	/*
	switch (slave.vaginalAccessory) {
		case "dildo":
			figures.push("Dildo 1");
			break;
	}

	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 || 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) {
		// case "a corset": figures.push("Corset A_80240"); break;
		// case "an extreme corset": figures.push("corset1_112951"); break;
		default: break;
	}

	switch (slave.faceAccessory) {
		case "porcelain mask":
			figures.push("Porcelain Mask");
			break;
	}

	switch (slave.collar) {
		case "preg biometrics":
			figures.push("Pregnancy Collar");
			break;
	}

	switch (slave.shoes) {
		case "extreme heels":
			figures.push("Extreme Heels"); p.applyExtremeHeels = true;
			break;
		case "extreme platform heels":
			figures.push("Extreme Heels 2"); p.applyExtremeHeels2 = true;
			break;
	}

	switch (slave.eyewear) {
		case "blurring glasses":
		case "corrective glasses":
		case "glasses":
			figures.push("Glasses");
			break;
	}

	if (!slave.arm.left && slave.PLimb > 0) {
		figures.push("Amputee Cap Arm Left");
	}
	if (!slave.arm.right && slave.PLimb > 0) {
		figures.push("Amputee Cap Arm Right");
	}

	figures.push("Genesis 8 Female");
	figures.push("Genesis 8 Female Eyelashes");

	if (!p.hideVagina) {
		figures.push("New Genitalia For Victoria 8 - Color Layer");
	}
	if (!p.hideDick) {
		figures.push("Futalicious Shell");
	}

	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;
		}
	}

	for (let i=0; i < scene.models[0].figures.length; i++) {
		scene.models[0].figures[i].visible = false;
		for (let j =0; j < figures.length; j++) {
			if (scene.models[0].figures[i].figId === figures[j]) {
				scene.models[0].figures[i].visible = true;
			}
		}
	}
};

App.Art.applySurfaces = function(slave, scene, p) {
	App.Art.seed = slave.ID + 1000;

	let glansFutaliciousShellLayers = [];
	let shaftFutaliciousShellLayers = [];
	let testiclesFutaliciousShellLayers = [];
	let torsoFrontFutaliciousShellLayers = [];
	let torsoMiddleFutaliciousShellLayers = [];
	let torsoBackFutaliciousShellLayers = [];
	let rectumFutaliciousShellLayers = [];
	let torsoFrontLayers = [];
	let torsoMiddleLayers = [];
	let torsoBackLayers = [];
	let genitaliaLayers = [];
	let anusLayers = [];
	let torsoLayers = [];
	let faceLayers = [];
	let lipsLayers = [];
	let earsLayers = [];
	let legsLayers = [];
	let armsLayers = [];
	let eyesocketLayers = [];
	let toenailsLayers = [];
	let fingernailsLayers = [];

	let surfaces = [];

	if ((slave.dick !== 0 || (!(slave.scrotum <= 0 || slave.balls <= 0))) && !p.hideDick) {
		surfaces.push(["Futalicious_Genitalia_G8F_Shaft_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Glans_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Testicles_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Front_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Middle_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Back_Futalicious_Shell", "visible", true]);
		surfaces.push(["Futalicious_Genitalia_G8F_Rectum_Futalicious_Shell", "visible", false]);
		surfaces.push(["Torso_Front", "visible", true]);
		surfaces.push(["Torso_Middle", "visible", true]);
		surfaces.push(["Torso_Back", "visible", true]);

		surfaces.push(["Genitalia", "visible", true]);
		surfaces.push(["Anus", "visible", true]);
		surfaces.push(["new_gens_V8_1840_Genitalia", "visible", true]);
		surfaces.push(["new_gens_V8_1840_Anus", "visible", true]);
	} else {
		surfaces.push(["Futalicious_Genitalia_G8F_Shaft_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Glans_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Testicles_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Front_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Middle_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Torso_Back_Futalicious_Shell", "visible", false]);
		surfaces.push(["Futalicious_Genitalia_G8F_Rectum_Futalicious_Shell", "visible", false]);
		surfaces.push(["Torso_Front", "visible", false]);
		surfaces.push(["Torso_Middle", "visible", false]);
		surfaces.push(["Torso_Back", "visible", false]);

		surfaces.push(["Genitalia", "visible", true]);
		surfaces.push(["Anus", "visible", true]);
		surfaces.push(["new_gens_V8_1840_Genitalia", "visible", true]);
		surfaces.push(["new_gens_V8_1840_Anus", "visible", true]);
	}

	glansFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	shaftFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	testiclesFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	torsoFrontFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	torsoMiddleFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	torsoBackFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	rectumFutaliciousShellLayers.push("ao_surface", "TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell");
	torsoFrontLayers.push("ao_surface", "TemplateTorso", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	torsoMiddleLayers.push("ao_surface", "TemplateTorso", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	torsoBackLayers.push("ao_surface", "TemplateTorso", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	genitaliaLayers.push("ao_surface", "TemplateGenitalia", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	anusLayers.push("ao_surface", "TemplateAnus", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	torsoLayers.push("TemplateTorso", "skindetail_blotches_torso", "skindetail_pores_torso", "skindetail_fine_torso", "skindetail_veins_torso");
	faceLayers.push("TemplateFace", "skindetail_blotches_face", "skindetail_pores_face", "skindetail_fine_face", "skindetail_veins_face");
	lipsLayers.push("TemplateLips", "lips_mask");
	earsLayers.push("TemplateEars", "skindetail_blotches_face", "skindetail_pores_face", "skindetail_fine_face", "skindetail_veins_face");
	legsLayers.push("TemplateLegs", "skindetail_blotches_legs", "skindetail_pores_legs", "skindetail_fine_legs", "skindetail_veins_legs");
	armsLayers.push("TemplateArms", "skindetail_blotches_arms", "skindetail_pores_arms", "skindetail_fine_arms", "skindetail_veins_arms");
	eyesocketLayers.push("TemplateFace");
	toenailsLayers.push("TemplateToenails");
	fingernailsLayers.push("TemplateFingernails");

	switch (slave.hStyle) {
		case "pixie cut":
		case "bob cut":
		case "buzzcut":
		case "trimmed":
			torsoLayers.push("shaved_torso");
			faceLayers.push("shaved_face");
			break;
		case "bald":
		case "shaved":
		default:
			break;
	}

	switch (slave.eyebrowFullness) {
		case "bald":
		case "shaved":
			break;
		case "pencil-thin":
			faceLayers.push("eyebrow_pencil");
			break;
		case "thin":
			faceLayers.push("eyebrow_thin");
			break;
		case "threaded":
			faceLayers.push("eyebrow_threaded");
			break;
		case "natural":
			faceLayers.push("eyebrow_natural");
			break;
		case "tapered":
			faceLayers.push("eyebrow_tapered");
			break;
		case "thick":
			faceLayers.push("eyebrow_thick");
			break;
		case "bushy":
			faceLayers.push("eyebrow_bushy");
			break;
	}

	switch (slave.makeup) {
		case 1:
			// Nice
			faceLayers.push("makeup_nice_eyes");
			break;
		case 2:
			// Gorgeous
			faceLayers.push("makeup_gorgeous_eyes");
			faceLayers.push("makeup_gorgeous_blush");
			break;
		case 3:
			// Hair coordinated
			faceLayers.push("makeup_nice_eyes");
			break;
		case 4:
			// Slutty
			faceLayers.push("makeup_slutty_eyes");
			faceLayers.push("makeup_slutty_blush");
			break;
		case 5:
			// Neon
			faceLayers.push("makeup_neon_eyes");
			break;
		case 6:
			// Neon hair coordinated
			faceLayers.push("makeup_neon_eyes");
			break;
		case 7:
			// Metallic
			faceLayers.push("makeup_metallic_eyes");
			break;
		case 8:
			// Metallic hair coordinated
			faceLayers.push("makeup_metallic_eyes");
			break;
	}

	let pubicStyle = "";
	switch (slave.pubicHStyle) {
		case "hairless":
		case "waxed":
		case "bald":
			break;
		case "neat":
			pubicStyle = "PubicNeat";
			break;
		case "in a strip":
			pubicStyle = "PubicStrip";
			break;
		case "bushy":
			pubicStyle = "PubicBushy";
			break;
		case "very bushy":
			pubicStyle = "PubicVeryBushy";
			break;
		case "bushy in the front and neat in the rear":
			pubicStyle = "PubicBushyFront";
			break;
		default:
			break;
	}

	if (pubicStyle !== "") {
		torsoLayers.push(pubicStyle);
		genitaliaLayers.push(pubicStyle);
		torsoFrontLayers.push(pubicStyle);
		torsoMiddleLayers.push(pubicStyle);
		torsoFrontFutaliciousShellLayers.push(pubicStyle);
		torsoMiddleFutaliciousShellLayers.push(pubicStyle);
	}

	switch (slave.markings) {
		case "beauty mark":
			torsoLayers.push("skindetail_beauty_marks_torso");
			genitaliaLayers.push("skindetail_beauty_marks_torso");
			faceLayers.push("skindetail_beauty_marks_face");
			armsLayers.push("skindetail_beauty_marks_arms");
			legsLayers.push("skindetail_beauty_marks_legs");
			break;
		case "freckles":
			torsoLayers.push("skindetail_freckles_torso");
			genitaliaLayers.push("skindetail_freckles_torso");
			faceLayers.push("skindetail_freckles_face");
			armsLayers.push("skindetail_freckles_arms");
			legsLayers.push("skindetail_freckles_legs");
			break;
		case "heavily freckled":
			torsoLayers.push("skindetail_heavy_freckles_torso");
			genitaliaLayers.push("skindetail_heavy_freckles_torso");
			faceLayers.push("skindetail_heavy_freckles_face");
			armsLayers.push("skindetail_heavy_freckles_arms");
			legsLayers.push("skindetail_heavy_freckles_legs");
			break;
		case "birthmark":
			torsoLayers.push("skindetail_birthmarks_torso");
	}

	torsoLayers.push("nipple_mask");

	function getDecalArea(location) {
		switch (location) {
			case "back": return [[[-0.35, -0.15, 0.1, 0.3], [0.35, -0.15, -0.1, 0.3]], [-0.28, 0.0], torsoLayers];
			case "chest": return [[[-0.1, -0.15, 0.2, 0.08]], [0.0, -0.11], torsoLayers];
			case "left breast": return [[[0.02, -0.07, 0.1, 0.09]], [0.07, -0.05], torsoLayers];
			case "right breast": return [[[-0.12, -0.07, 0.1, 0.09]], [-0.07, -0.05], torsoLayers];
			case "left buttock": return [[[0.25, 0.25, 0.2, 0.1]], [0.32, 0.29], torsoLayers];
			case "right buttock": return [[[-0.45, 0.25, 0.2, 0.1]], [-0.32, 0.29], torsoLayers];
			case "neck": return [[[-0.4, -0.27, 0.8, 0.05]], [0.0, -0.22], torsoLayers];
			case "lower back": return [[[-0.35, 0.1, 0.1, 0.1], [0.35, 0.1, -0.1, 0.1]], [-0.27, 0.15], torsoLayers];
			case "left shoulder": return [[[0.13, -0.2, 0.1, 0.1]], [0.14, -0.17], torsoLayers];
			case "right shoulder": return [[[-0.23, -0.2, 0.1, 0.1]], [-0.14, -0.17], torsoLayers];
			case "left abdomen": return [[[0.00, 0.2, 0.1, 0.1]], [0.05, 0.22], torsoLayers];
			case "right abdomen": return [[[-0.1, 0.2, 0.1, 0.1]], [-0.05, 0.22], torsoLayers];

			case "pubic mound": return [[[-0.05, 0.33, 0.1, 0.04]], [0.0, 0.35], genitaliaLayers];

			case "left cheek": return [[[0.1, 0.1, 0.1, 0.1]], [0.15, 0.15], faceLayers];
			case "right cheek": return [[[-0.2, 0.1, 0.1, 0.1]], [-0.15, 0.15], faceLayers];

			case "left ears": return [[[0.3, -0.45, 0.15, 0.15]], [0.375, -0.375], earsLayers];
			case "right ears": return [[[-0.45, -0.45, 0.15, 0.15]], [-0.375, -0.375], earsLayers];

			case "left hand": return [[[0.28, -0.4, 0.1, 0.3]], [0.32, -0.33], armsLayers];
			case "right hand": return [[[-0.38, 0.1, 0.1, 0.3]], [-0.32, 0.16], armsLayers];
			case "left wrist": return [[[0.18, -0.35, 0.1, 0.2]], [0.22, -0.3], armsLayers];
			case "right wrist": return [[[-0.28, 0.15, 0.1, 0.2]], [-0.22, 0.19], armsLayers];
			case "left lower arm": return [[[-0.05, -0.4, 0.2, 0.25]], [0.05, -0.275], armsLayers];
			case "right lower arm": return [[[-0.15, 0.1, 0.2, 0.25]], [-0.05, 0.225], armsLayers];
			case "left upper arm": return [[[-0.4, -0.45, 0.3, 0.3]], [-0.25, -0.3], armsLayers];
			case "right upper arm": return [[[0.1, 0.05, 0.3, 0.3]], [0.25, 0.2], armsLayers];

			case "left ankle": return [[[0.2, 0.2, 0.25, 0.07]], [0.325, 0.235], legsLayers];
			case "right ankle": return [[[-0.45, 0.2, 0.25, 0.07]], [-0.325, 0.235], legsLayers];
			case "left calf": return [[[0.35, -0.12, 0.1, 0.25]], [0.38, -0.04], legsLayers];
			case "right calf": return [[[-0.45, -0.12, 0.1, 0.25]], [-0.38, -0.04], legsLayers];
			case "left thigh": return [[[0.08, -0.42, 0.32, 0.25]], [0.18, -0.4], legsLayers];
			case "right thigh": return [[[-0.4, -0.42, 0.32, 0.25]], [-0.18, -0.4], legsLayers];
			case "left foot": return [[[0.26, 0.25, 0.12, 0.1]], [0.32, 0.33], legsLayers];
			case "right foot": return [[[-0.38, 0.25, 0.12, 0.1]], [-0.32, 0.33], legsLayers];

			default: return [null, null, null];
		}
	}

	function generateDecalScar(location, area, matId, kind) {
		let mat = App.Art.getMaterialById(scene, "scars_" + kind);
		if (mat !== null) {
			let r1 = App.Art.random();
			let r2 = App.Art.random();
			let r3 = App.Art.random();
			let r4 = App.Art.random();
			let a = area[Math.floor(area.length * r4)];

			let scale;
			switch (location) {
				case "left cheek": scale = 1; break;
				case "right cheek": scale = 1; break;
				default: scale = 0.4;
			}

			mat = JSON.parse(JSON.stringify(mat));
			mat.matId = matId;
			mat.transform = [r1 * a[2] + a[0], r2 * a[3] + a[1], r3*6.28, scale];
			scene.materials.push(mat);
		}
	}

	function applyScars(slave, location, area, layers) {
		const scars = App.Medicine.Modification.scarRecord(slave);
		for (let kind in scars[location]) {
			for (let i = 0; i < scars[location][kind]; i++) {
				let matId = location + kind + i;
				if (App.Art.getMaterialById(scene, matId) == null)	{
					generateDecalScar(location, area, matId, kind);
				}
				layers.push(matId);
			}
		}
	}

	function generateDecalText(pos, matId, letter, scale) {
		let mat = App.Art.getMaterialById(scene, "letters_" + letter);
		if (mat !== null) {
			mat = JSON.parse(JSON.stringify(mat));
			mat.matId = matId;
			mat.transform = [pos[0], pos[1], 0, scale];
			scene.materials.push(mat);
		}
	}

	function generateText(text, location, center, layers) {
		for (let i = 0; i < text.length; i++) {
			let letter = text.charAt(i).toLowerCase();
			let matId = "text" + location + text + i;

			if (App.Art.getMaterialById(scene, matId) == null)	{
				let scale = 0.02;
				let width = scale * text.length;
				let height = scale;

				let pos = [center[0] - (width/2) + (scale/2) + scale * i, center[1] - (height/2) + (scale/2)];
				generateDecalText(pos, matId, letter, scale);
			}
			layers.push(matId);
		}
	}

	function applyBrands(slave, location, center, layers) {
		const brands = App.Medicine.Modification.brandRecord(slave);
		let text = brands[location];

		switch (text) {
			case "your initials":
				text = V.PC.birthName.charAt(0).toLowerCase() + (V.PC.birthSurname + " ").charAt(0).toLowerCase(); break;
			case "the number of children $he has birthed":
				text = "" + slave.counter.birthsTotal; break;
			case "$his virginity status":
				text = slave.vagina === 0 ? "virgin": ""; break;
			case "$his current height":
				text = "" + slave.height; break;
			default:
				if (App.Medicine.Modification.Brands.personal.hasOwnProperty(text) ||
					App.Medicine.Modification.Brands.genitalSymbol.hasOwnProperty(text) ||
					App.Medicine.Modification.Brands.silhouettes.hasOwnProperty(text) ||
					App.Medicine.Modification.Brands.FS.hasOwnProperty(text)) {
						  return;
				}
		}
		generateText(text, location, center, layers);
	}

	if (slave.breedingMark === 1) {
		genitaliaLayers.push("tattoo_breeding");
		torsoLayers.push("tattoo_breeding");
	}

	if (slave.birthsTat > 0  && slave.birthsTat >= 100) {
		let location = "left abdomen";
		let [, center, layer] = getDecalArea(location);
		if (layer !== null) {
			generateText(String(slave.birthsTat), location, center, layer);
		}
	}

	if (slave.birthsTat > 0 && slave.birthsTat < 100) { // let's make sure we don't generate too many materials
		let location = "left thigh";
		let [, center, layer] = getDecalArea(location);
		if (layer !== null) {
			for (let i = 0; i < slave.birthsTat; i++) {
				let matId = "birthcount" + location + i;

				if (App.Art.getMaterialById(scene, matId) == null)	{
					let scale = 0.015;
					let width = scale * 5;
					let height = scale;
					let row = Math.trunc(i / 5);
					let col = i % 5;

					let mat = App.Art.getMaterialById(scene, "tattoo_birthcount");
					if (mat !== null) {
						mat = JSON.parse(JSON.stringify(mat));
						mat.matId = matId;
						mat.transform = [center[0] + 0.06 - (width/2) + (scale/2) + scale * col, center[1] - scale * 2.5 + height * row + scale * 0.1 * row, 0, scale];
						scene.materials.push(mat);
					}
				}
				layer.push(matId);
			}
		}
	}

	if (slave.abortionTat > 0) {
		let location = "right abdomen";
		let [, center, layer] = getDecalArea("right abdomen");
		if (layer !== null) {
			generateText(String(slave.abortionTat), location, center, layer);
		}
	}

	for (let key in slave) {
		if (key.endsWith("Tat")) {
			let tatLocation = key.split("Tat")[0];
			let tatDesign = slave[key];

			switch (tatDesign) {
				case "bleached":
					if (tatLocation === "anus") {
						surfaces.push(["new_gens_V8_1840_Anus", "visible", false]);
					}
					break;
				case "permanent makeup":
					faceLayers.push("tattoo_makeup");
					lipsLayers.push("tattoo_makeup");
					break;
				case "lewd crest":
					genitaliaLayers.push("tattoo_lewdcrest");
					torsoLayers.push("tattoo_lewdcrest");
					break;

				// navel
				case "a heart": torsoLayers.push("tattoo_heart"); break;
				case "a butterfly": torsoLayers.push("tattoo_butterfly"); break;
				case "a star": torsoLayers.push("tattoo_star"); break;

				case "flowers":
					if (tatLocation === "shoulders") {
						armsLayers.push("tattoo_flowers_arm");
					} else if (tatLocation === "boobs") {
						torsoLayers.push("tattoo_flowers_body");
					} else if (tatLocation === "legs") {
						legsLayers.push("tattoo_flowers_leg");
					} break;

				case "tribal patterns":
				case "counting":
				case "advertisements":
				case "rude words":
				case "degradation":
				case "Asian art":
				case "scenes":
				case "bovine patterns":
				case "sacrilege":
				case "sacrament":
				case "possessive":
				case "paternalist":
				default:
					break;
			}
		}
	}

	for (let location in App.Medicine.Modification.scarRecord(slave)) {
		let [area,, layer] = getDecalArea(location);
		if (layer !== null) {
			applyScars(slave, location, area, layer);
		}
	}

	const brands = App.Medicine.Modification.brandRecord(slave);
	for (let location in brands) {
		let [, center, layer] = getDecalArea(location);
		if (layer !== null) {
			applyBrands(slave, location, center, layer);
		}
	}

	surfaces.push(["Futalicious_Genitalia_G8F_Shaft_Futalicious_Shell", "matIds", shaftFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Glans_Futalicious_Shell", "matIds", glansFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Testicles_Futalicious_Shell", "matIds", testiclesFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Torso_Front_Futalicious_Shell", "matIds", torsoFrontFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Torso_Middle_Futalicious_Shell", "matIds", torsoMiddleFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Torso_Back_Futalicious_Shell", "matIds", torsoBackFutaliciousShellLayers]);
	surfaces.push(["Futalicious_Genitalia_G8F_Rectum_Futalicious_Shell", "matIds", rectumFutaliciousShellLayers]);
	surfaces.push(["Torso_Front", "matIds", torsoFrontLayers]);
	surfaces.push(["Torso_Middle", "matIds", torsoMiddleLayers]);
	surfaces.push(["Torso_Back", "matIds", torsoBackLayers]);
	surfaces.push(["Genitalia",	"matIds", genitaliaLayers]);
	surfaces.push(["Anus", "matIds", anusLayers]);
	surfaces.push(["Torso", "matIds", torsoLayers]);
	surfaces.push(["Face", "matIds", faceLayers]);
	surfaces.push(["Lips", "matIds", lipsLayers]);
	surfaces.push(["Ears", "matIds", earsLayers]);
	surfaces.push(["Legs", "matIds", legsLayers]);
	surfaces.push(["Arms", "matIds", armsLayers]);
	surfaces.push(["EyeSocket", "matIds", eyesocketLayers]);
	surfaces.push(["Toenails", "matIds", toenailsLayers]);
	surfaces.push(["Fingernails", "matIds", fingernailsLayers]);

	for (let i=0, count=0; i < scene.models[0].figures.length; i++) {
		for (let j=0; j < scene.models[0].figures[i].surfaces.length; j++, count++) {
			for (let h =0; h < surfaces.length; h++) {
				if (scene.models[0].figures[i].surfaces[j].surfaceId === surfaces[h][0]) {
					scene.models[0].figures[i].surfaces[j][surfaces[h][1]] = surfaces[h][2];
				}
			}
		}
	}
};

App.Art.applyMaterials = function(slave, scene, p) {
	App.Art.seed = slave.ID + 2000;

	let materials = [];

	let hairColor = App.Art.hexToRgb(extractColor(slave.hColor));
	let lipsColor = App.Art.hexToRgb(skinColorCatcher(slave).areolaColor); // Nox
	let areolaColor = App.Art.hexToRgb(skinColorCatcher(slave).areolaColor);

	hairColor = [hairColor[0], hairColor[1], hairColor[2]];

	let makeupColor;
	let makeupOpacity;
	let lipsGloss = 1.49;
	let lipsRough = 0.45;
	let lipsMetal = 0;

	switch (slave.makeup) {
		case 1:
			// Nice
			makeupColor = "#ff69b4";
			makeupOpacity = 0.5;
			lipsRough = 0.35;
			break;
		case 2:
			// Gorgeous
			makeupColor = "#8b008b";
			makeupOpacity = 0.7;
			lipsRough = 0.3;
			break;
		case 3:
			// Hair coordinated
			makeupColor = extractColor(slave.hColor);
			makeupOpacity = 0.3;
			break;
		case 4:
			// Slutty
			makeupColor = "#B70000";
			makeupOpacity = 0.8;
			lipsRough = 0.2;
			break;
		case 5:
			// Neon
			makeupColor = "#DC143C";
			makeupOpacity = 1;
			lipsMetal = 0.5;
			break;
		case 6:
			// Neon hair coordinated
			makeupColor = extractColor(slave.hColor);
			makeupOpacity = 1;
			lipsMetal = 0.5;
			break;
		case 7:
			// Metallic
			makeupColor = "#b22222";
			makeupOpacity = 0.7;
			lipsMetal = 1;
			break;
		case 8:
			// Metallic hair coordinated
			makeupColor = extractColor(slave.hColor);
			makeupOpacity = 0.7;
			lipsMetal = 1;
			break;
		default:
			makeupColor = "#ffffff";
			makeupOpacity = 0;
			break;
	}

	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) {
		case 2:
			// color-coordinated with hair
			nailColor = extractColor(slave.hColor);
			break;
		case 4:
			// bright and glittery
			nailColor = "#ff0000";
			break;
		case 5:
			// very long and garish
			nailColor = "#ff0000";
			break;
		case 6:
			// neon
			nailColor = "#DC143C";
			break;
		case 7:
			// color-coordinated neon
			nailColor = extractColor(slave.hColor);
			break;
		case 8:
			// metallic
			nailColor = "#b22222";
			break;
		case 9:
			// color-coordinated metallic
			nailColor = extractColor(slave.hColor);
			break;
		default:
			nailColor = "#ffffff";
			break;
	}

	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;
	}

	let eyebrowColor = App.Art.hexToRgb(extractColor(slave.eyebrowHColor));

	switch (slave.eyebrowFullness) {
		case "bald":
		case "shaved":
			break;
		case "pencil-thin":
			materials.push(["eyebrow_pencil", "Ka", eyebrowColor]);
			break;
		case "thin":
			materials.push(["eyebrow_thin", "Ka", eyebrowColor]);
			break;
		case "threaded":
			materials.push(["eyebrow_threaded", "Ka", eyebrowColor]);
			break;
		case "natural":
			materials.push(["eyebrow_natural", "Ka", eyebrowColor]);
			break;
		case "tapered":
			materials.push(["eyebrow_tapered", "Ka", eyebrowColor]);
			break;
		case "thick":
			materials.push(["eyebrow_thick", "Ka", eyebrowColor]);
			break;
		case "bushy":
			materials.push(["eyebrow_bushy", "Ka", eyebrowColor]);
			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 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]]);

	// expected skin color
	let O = App.Art.hexToRgb(skinColorCatcher(slave).skinColor);

	// calculate lips and areola color based on skin color and brightness
	let lbrf = Math.min((O[0] + O[1] + O[2])/3+0.4, 1);
	lipsColor = [O[0]*0.76*lbrf, O[1]*0.55*lbrf, O[2]*0.6*lbrf];
	areolaColor = lipsColor;

	// average skin texture color
	let oSkinColors = {
		"Tara": 		[194/255, 148/255, 124/255],
		"Saffron": 		[246/255, 209/255, 193/255],
		"Reagan": 		[224/255, 180/255, 151/255],
		"Mylou": 		[212/255, 166/255, 140/255],
		"Minami": 		[191/255, 164/255, 143/255],
		"Kimmy": 		[216/255, 185/255, 167/255],
		"Kathy": 		[219/255, 154/255, 132/255],
		// "DarkSkin": 	[92/255, 68/255, 58/255],
		"Daphne": 		[199/255, 157/255, 133/255],
		"Ceridwen": 	[238/255, 216/255, 203/255],
		"Celinette": 	[223/255, 210/255, 204/255],
		"Base": 		[215/255, 181/255, 156/255],
		// "Angelica": 	[117/255, 70/255, 54/255],
		"Adaline": 		[208/255, 154/255, 126/255],
		// "Topmodel": 	[198/255, 153/255, 122/255]
		"Kelly":		[207/255, 149/255, 103/255],
		"Zalena":		[136/255, 103/255, 86/255],
		"Mowad":		[207/255, 169/255, 148/255],
	};

	let oCockColors = {
		"White": 	[180/255, 170/255, 164/255],
		"Light": 	[185/255, 160/255, 143/255],
		"Mid": 		[200/255, 155/255, 124/255],
		"Dark": 	[85/255, 56/255, 42/255],
	};

	// find nearest skin texture
	let distance = [];
	for (const [, value] of Object.entries(oSkinColors)) {
		let noise = App.Art.random()*(60/255); // add random error term to sample similar skins
		distance.push((value[0] - O[0])**2 + (value[1] - O[1])**2 + (value[2] - O[2])**2 + noise**2);
	}
	let skin = Object.keys(oSkinColors)[distance.indexOf(Math.min(...distance))];

	// find multiplication factor skin texture color -> skin color
	let Oc = oSkinColors[skin];
	let mO = [O[0]/Oc[0], O[1]/Oc[1], O[2]/Oc[2]];

	// find nearest cock texture
	distance = [];
	for (const [, value] of Object.entries(oCockColors)) {
		distance.push((value[0] - O[0])**2 + (value[1] - O[1])**2 + (value[2] - O[2])**2);
	}
	let cockSkin = Object.keys(oCockColors)[distance.indexOf(Math.min(...distance))];

	// find multiplication factor cock texture color -> skin color
	let Occ = oCockColors[cockSkin];
	let cbrf = Math.min((O[0] + O[1] + O[2])/3+0.5, 1);
	let mOc = [O[0]/Occ[0]*0.85*cbrf, O[1]/Occ[1]*0.85*cbrf, O[2]/Occ[2]*0.85*cbrf];

	// skin specularity related values
	let Ks;
	let r;
	let rN;
	let Ni;

	if (slave.clothes === "body oil") {
		r = 0.4;
		rN = 0.4;
		Ni = 1.5;
	} else {
		r = 0.65;
		rN = 0.55;
		Ni = 1.35;
	}

	let Obr = (O[0] + O[1] + O[2])/3;
	Ks = [Obr*2+0.5, Obr*2+0.5, Obr*2+0.5];

	// apply
	materials.push(["TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "map_Ka", "dick/" + cockSkin + "Cock.jpg"]);
	materials.push(["TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ka", mOc]);
	materials.push(["TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ks", Ks]);
	materials.push(["TemplateFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ni", Ni]);

	materials.push(["nipple_mask", "Ka", areolaColor]);
	materials.push(["nipple_mask", "Ks", Ks]);
	materials.push(["nipple_mask", "r", rN]);
	materials.push(["nipple_mask", "Ni", Ni]);
	materials.push(["nipple_mask", "d", 0.6]);

	materials.push(["lips_mask", "Ka", lipsColor]);
	materials.push(["lips_mask", "Ni", lipsGloss]);
	materials.push(["lips_mask", "m", lipsMetal]);
	materials.push(["lips_mask", "r", lipsRough]);
	materials.push(["lips_mask", "d", 1.0]);

	materials.push(["TemplateTorso", "Ka", mO]);
	materials.push(["TemplateTorso", "Ks", Ks]);
	materials.push(["TemplateTorso", "r", r]);
	materials.push(["TemplateTorso", "Ni", Ni]);
	materials.push(["TemplateTorso", "map_Ka", "base/skin/" + skin + "Torso.jpg"]);
	materials.push(["TemplateArms", "Ka", mO]);
	materials.push(["TemplateArms", "Ks", Ks]);
	materials.push(["TemplateArms", "r", r]);
	materials.push(["TemplateArms", "Ni", Ni]);
	materials.push(["TemplateArms", "map_Ka", "base/skin/" + skin + "Arms.jpg"]);
	materials.push(["TemplateLegs", "Ka", mO]);
	materials.push(["TemplateLegs", "Ks", Ks]);
	materials.push(["TemplateLegs", "r", r]);
	materials.push(["TemplateLegs", "Ni", Ni]);
	materials.push(["TemplateLegs", "map_Ka", "base/skin/" + skin + "Legs.jpg"]);
	materials.push(["TemplateFace", "Ka", mO]);
	materials.push(["TemplateFace", "Ks", Ks]);
	materials.push(["TemplateFace", "r", r]);
	materials.push(["TemplateFace", "Ni", Ni]);
	materials.push(["TemplateFace", "map_Ka", "base/skin/" + skin + "Face.jpg"]);
	materials.push(["TemplateEars", "Ka", mO]);
	materials.push(["TemplateEars", "Ks", Ks]);
	materials.push(["TemplateEars", "r", r]);
	materials.push(["TemplateEars", "Ni", Ni]);
	materials.push(["TemplateEars", "map_Ka", "base/skin/" + skin + "Face.jpg"]);
	materials.push(["TemplateLips", "Ni", lipsGloss]);
	materials.push(["TemplateLips", "m", lipsMetal]);
	materials.push(["TemplateLips", "r", lipsRough]);
	materials.push(["TemplateLips", "map_Ka", "base/skin/" + skin + "Face.jpg"]);
	materials.push(["TemplateFingernails", "Ka", nailColor]);
	materials.push(["TemplateFingernails", "map_Ka", "base/skin/" + skin + "Arms.jpg"]);
	materials.push(["TemplateToenails", "Ka", nailColor]);
	materials.push(["TemplateToenails", "map_Ka", "base/skin/" + skin + "Legs.jpg"]);
	materials.push(["TemplateAnus", "Ka", mO]);
	materials.push(["TemplateAnus", "Ks", Ks]);
	materials.push(["TemplateAnus", "r", r]);
	materials.push(["TemplateAnus", "Ni", Ni]);
	materials.push(["TemplateAnus", "map_Ka", "base/skin/" + skin + "Torso.jpg"]);
	materials.push(["TemplateGenitalia", "Ka", mO]);
	materials.push(["TemplateGenitalia", "Ks", Ks]);
	materials.push(["TemplateGenitalia", "r", r]);
	materials.push(["TemplateGenitalia", "Ni", Ni]);
	materials.push(["TemplateGenitalia", "map_Ka", "base/skin/" + skin + "Torso.jpg"]);

	let pubicColor = App.Art.hexToRgb(extractColor(slave.pubicHColor));
	switch (slave.pubicHStyle) {
		case "hairless":
		case "waxed":
		case "bald":
			break;
		case "neat":
			materials.push(["PubicNeat", "Ka", pubicColor]);
			break;
		case "in a strip":
			materials.push(["PubicStrip", "Ka", pubicColor]);
			break;
		case "bushy":
			materials.push(["PubicBushy", "Ka", pubicColor]);
			break;
		case "very bushy":
			materials.push(["PubicVeryBushy", "Ka", pubicColor]);
			break;
		case "bushy in the front and neat in the rear":
			materials.push(["PubicBushyFront", "Ka", pubicColor]);
			break;
		default:
			break;
	}

	switch (slave.vaginaLube) {
		case 0:
			materials.push(["new_gens_V8_1840_Genitalia", "Ks", [5, 5, 5]]);
			materials.push(["new_gens_V8_1840_Anus", "Ks", [5, 5, 5]]);
			break;
		case 1:
			materials.push(["new_gens_V8_1840_Genitalia", "Ks", [10, 10, 10]]);
			materials.push(["new_gens_V8_1840_Anus", "Ks", [10, 10, 10]]);
			break;
		case 2:
			materials.push(["new_gens_V8_1840_Genitalia", "Ks", [15, 15, 15]]);
			materials.push(["new_gens_V8_1840_Anus", "Ks", [15, 15, 15]]);
			break;
	}

	let blotchesDetail = 0;
	materials.push(["skindetail_blotches_torso", "d", blotchesDetail]);
	materials.push(["skindetail_blotches_face", "d", blotchesDetail]);
	materials.push(["skindetail_blotches_arms", "d", blotchesDetail]);
	materials.push(["skindetail_blotches_legs", "d", blotchesDetail]);

	let poresDetail = 0;
	materials.push(["skindetail_pores_torso", "d", poresDetail]);
	materials.push(["skindetail_pores_face", "d", poresDetail]);
	materials.push(["skindetail_pores_arms", "d", poresDetail]);
	materials.push(["skindetail_pores_legs", "d", poresDetail]);

	let fineDetail = 0;
	materials.push(["skindetail_fine_torso", "d", fineDetail]);
	materials.push(["skindetail_fine_face", "d", fineDetail]);
	materials.push(["skindetail_fine_arms", "d", fineDetail]);
	materials.push(["skindetail_fine_legs", "d", fineDetail]);

	let veins = 0;
	materials.push(["skindetail_veins_torso", "d", veins]);
	materials.push(["skindetail_veins_face", "d", veins]);
	materials.push(["skindetail_veins_arms", "d", veins]);
	materials.push(["skindetail_veins_legs", "d", veins]);

	switch (slave.markings) {
		case "beauty mark":
			materials.push(["skindetail_beauty_marks_torso", "d", 1]);
			materials.push(["skindetail_beauty_marks_face", "d", 1]);
			materials.push(["skindetail_beauty_marks_arms", "d", 1]);
			materials.push(["skindetail_beauty_marks_legs", "d", 1]);
			break;
		case "freckles":
			materials.push(["skindetail_freckles_torso", "d", 0.7]);
			materials.push(["skindetail_freckles_face", "d", 0.8]);
			materials.push(["skindetail_freckles_arms", "d", 0.7]);
			materials.push(["skindetail_freckles_legs", "d", 0.7]);
			break;
		case "heavily freckled":
			materials.push(["skindetail_heavy_freckles_torso", "d", 0.7]);
			materials.push(["skindetail_heavy_freckles_face", "d", 0.8]);
			materials.push(["skindetail_heavy_freckles_arms", "d", 0.7]);
			materials.push(["skindetail_heavy_freckles_legs", "d", 0.7]);
			break;
		case "birthmark":
			materials.push(["skindetail_birthmarks_torso", "d", 1]);
			materials.push(["skindetail_birthmarks_torso", "Ka", [O[0]+0.2, O[1]+0.2, O[2]+0.2]]);
	}

	let torso = App.Art.getMatIdsBySurface(scene, "Torso")[0];

	const scars = App.Medicine.Modification.scarRecord(slave);
	if (scars.hasOwnProperty("belly") && scars.belly["c-section"] > 0) {
		materials.push([torso, "map_Kn", "base/Victoria8_Torso_CNM_1002_3.jpg"]);
	} else {
		materials.push([torso, "map_Kn", "base/Victoria8_Torso_NM_1002_3.jpg"]);
	}

	if (slave.collar === "preg biometrics") {
		if (slave.preg > 0) {
			materials.push(["pregnancy_collar_screen", "map_Ke", "dummy1"]);
		} else {
			materials.push(["pregnancy_collar_screen", "map_Ke", "dummy0"]);
		}
	}

	if ("glassesColor" in slave) {
		materials.push(["glasses_frame", "Ka", App.Art.hexToRgb(slave.glassesColor)]);
		materials.push(["porcelain_mask_mask", "Ka", App.Art.hexToRgb(slave.glassesColor)]);
	}

	for (let i =0; i < scene.materials.length; i++) {
		for (let j =0; j < materials.length; j++) {
			if (scene.materials[i].matId === materials[j][0]) {
				scene.materials[i][materials[j][1]] = materials[j][2];
			}
		}
	}
};

App.Art.artSize = 0; // capture the artSize variable from ui.js

App.Art.getAnimState = function(slave, scene, p, morphs, isAnimTick) {
	// variable shortcuts (array variables) - pose || face || breathing || blink || look X || look Y
	let animLength = p.animVars[0]; // how many seconds the anim will play over
	let animPower = p.animVars[1]; // morph strength
	let animDelay = p.animVars[2];  // how long to wait until playing the next anim
	let currentAnim = p.animVars[3];
	let animFrame = p.animVars[4]; // tracking how many frames into the anim
	let eyeMoveReciprocal = p.animVars[5]; // randomly decides whether eye movements follow head movements

	const poseAnimList = ["idle1", "idle2", "idle3", "idle4"];
	const faceAnimList = [];
	if (Math.random() < (slave.trust + 100) / 200) {
		// trusting slave - happy animations
		faceAnimList.push("expressionsHappy", "expressionsHappyCheerful", "expressionsHappyDelighted", "expressionsHappyFriendly", "expressionsHappySweet");
	}
	else {
		// fearful slave - fear/sad animations
		faceAnimList.push("expressionsFear", "expressionsFearAlarm", "expressionsFearAnticipation", "expressionsFearFrightened", "expressionsSadConfused", "expressionsSadOffended", "expressionsSadDejected", "expressionsSadIll");
	}
	if (Math.random() < (slave.devotion + 100) / 200) {
		// devoted slave - seductive animations
		faceAnimList.push("expressionsSeductiveDesire", "expressionsSeductiveLoving");
	}
	else {
		// hateful slave - angry animations
		faceAnimList.push("expressionsAngerFierce", "expressionsAngerGrumpy", "expressionsAngerSnarl");
	}
	const animList = [poseAnimList, faceAnimList];

	const animState = [0, 0, 0, 0, 0, 0];

	// run animations from here
	for (let i = 0; i < animFrame.length; i++) {
		if (App.Art.artSize < 3 && !(i === 0 || i === 4 || i === 5)) {
			currentAnim[i] = "";
			continue; // skip face | blink | breath anims if small frames
		}

		// reset anim
		if (currentAnim[i] === "") {
			switch (i) {
				case 0: // pose
					currentAnim[i] = animList[i][Math.floor(Math.random() * animList[i].length)];
					if (App.Art.artSize === 1) {
						animLength[i] = (Math.random() * 0.5) + 0.5;
						animDelay[i] = (Math.floor(Math.random() * 4) + 1) * V.animFPS;
					}
					else {
						animLength[i] = (Math.random() * 4) + 2;
						animDelay[i] = (Math.floor(Math.random() * 25) + 5) * V.animFPS;
					}
					break;
				case 1: // facial exp
					currentAnim[i] = animList[i][Math.floor(Math.random() * animList[i].length)];
					animLength[i] = (Math.random() * 4) + 1;
					animDelay[i] = (Math.floor(Math.random() * 20) + 10) * V.animFPS;
					animPower[i] = (Math.random() * 0.75) + 0.25;
					break;
				case 2: // breathing
					currentAnim[i] = "breathAnim";
					animLength[i] = (Math.random() * 2) + 2;
					animDelay[i] = (Math.floor(Math.random() * 2) + 1) * V.animFPS;
					animPower[i] = (Math.random() * 0.025) + 0.025;
					break;
				case 3: // blink
					currentAnim[i] = "blinkAnim";
					animLength[i] = (Math.random() * 0.25) + 0.25;
					animDelay[i] = (Math.floor(Math.random() * 8) + 2) * V.animFPS;
					break;
				case 4: // look X
				case 5: // look Y
					if (currentAnim[4] === "" && currentAnim[5] === "") {
						currentAnim[4] = jsEither(["headBendLeft", "headTwistLeft", "headBendRight", "headTwistRight"]);
						animLength[4] = (Math.random() * 2) + 0.5;
						animDelay[4] = (Math.floor(Math.random() * 10) + 5) * V.animFPS;
						animPower[4] = Math.random();

						currentAnim[5] = ((Math.random() < (slave.trust + 100) / 200) ? "down" : "up");
						eyeMoveReciprocal = (Math.random() > (slave.trust + 100) / 200);
						animLength[5] = (Math.random() * 2) + 0.5;
						animDelay[5] = (Math.floor(Math.random() * 10) + 5) * V.animFPS;
						animPower[5] = Math.random();
					}
					break;
			}
			animFrame[i] = 0;
		}

		animState[i] = Math.sin((Math.PI / (Math.floor(animLength[i] * V.animFPS))) * animFrame[i] - (Math.PI / 2));

		function getAnimState(morphPower) {
			return (animState[i] / (2 / Math.abs(morphPower))) + (1 / (2 / morphPower));
		}

		// animation picker
		switch (i) {
			case 0: // pose
				// lots of things change arms down and legs closed states, so these need to be summated and pushed as one morph below
				let slaveArmsDown = 0;
				let slaveLegsClosed = 0;

				if (!scene.inspect) {
					if (slave.scrotum > 20) {
						if (slave.devotion <= 50) {
							slaveArmsDown += Math.max(Math.min(-slave.weight/300/3.5, -slave.scrotum/200), -0.5);
						}

						if (p.applyExtremeHeels || p.applyExtremeHeels2) {
							slaveLegsClosed += Math.max(Math.min(-slave.weight/300/3.5 - 0.5, -slave.scrotum/20), -1.5);
						} else {
							slaveLegsClosed += Math.max(Math.min(-slave.weight/300/3.5, -slave.scrotum/20), -1.5);
						}
					} else {
						if (slave.devotion <= 50) {
							slaveArmsDown += Math.max(-slave.weight/300/3.5, -0.5);
						}

						if (p.applyExtremeHeels || p.applyExtremeHeels2 && !scene.inspect) {
							slaveLegsClosed += Math.max(-slave.weight/300/3.5 - 0.5, -1.5);
						} else {
							slaveLegsClosed += Math.max(-slave.weight/300/3.5, -1.5);
						}
					}
				}

				switch (currentAnim[i]) {
					case "idle1":
						if (slave.devotion > 50) {
							morphs.push(["posesHigh", 1 - getAnimState(0.5)]);
							slaveArmsDown += getAnimState(0.4);
							if (p.applyExtremeHeels || p.applyExtremeHeels2) {
								slaveLegsClosed -= getAnimState(0.5);
							}
						} else if (slave.devotion > -20) {
							morphs.push(["posesMid", 1 - getAnimState(0.5)]);
						} else {
							morphs.push(["posesLow", 1 - getAnimState(0.5)]);
							slaveArmsDown += getAnimState(0.2);
						}
						morphs.push(["posesInspect2", getAnimState(0.5)]);
						break;
					case "idle2":
						if (slave.devotion > 50) {
							morphs.push(["posesHigh", 1 - getAnimState(0.4)]);
							morphs.push(["posesLow", getAnimState(0.6)]);
							slaveArmsDown += getAnimState(0.1);
							if (p.applyExtremeHeels || p.applyExtremeHeels2) {
								slaveLegsClosed -= getAnimState(0.5);
							}
						} else if (slave.devotion > -20) {
							morphs.push(["posesMid", 1 - getAnimState(0.5)]);
							morphs.push(["posesHigh", getAnimState(0.3)]);
							slaveArmsDown += getAnimState(0.1);
						} else {
							morphs.push(["posesLow", 1 - getAnimState(0.6)]);
							morphs.push(["posesHigh", getAnimState(0.4)]);
							slaveArmsDown += getAnimState(0.1);
						}
						break;
					case "idle3":
						if (slave.devotion > 50) {
							morphs.push(["posesHigh", 1]);
							morphs.push(["posesInspect2", getAnimState(-0.25)]);
							slaveArmsDown += getAnimState(0.2);
							if (p.applyExtremeHeels || p.applyExtremeHeels2) {
								slaveLegsClosed -= getAnimState(0.5);
							}
						} else if (slave.devotion > -20) {
							morphs.push(["posesMid", 1]);
							morphs.push(["posesInspect2", getAnimState(-0.2)]);
						} else {
							morphs.push(["posesLow", getAnimState(1.2)]);
							morphs.push(["posesInspect2", getAnimState(-0.2)]);
						}
						break;
					case "idle4":
						if (slave.devotion > 50) {
							morphs.push(["posesHigh", 1 - getAnimState(0.8)]);
							morphs.push(["posesLow", getAnimState(0.2)]);
							morphs.push(["posesMid", getAnimState(0.6)]);
							if (p.applyExtremeHeels || p.applyExtremeHeels2) {
								slaveLegsClosed -= getAnimState(0.5);
							}
						} else if (slave.devotion > -20) {
							morphs.push(["posesMid", 1 - getAnimState(0.4)]);
							morphs.push(["posesLow", getAnimState(0.2)]);
							morphs.push(["posesHigh", getAnimState(0.2)]);
						} else {
							morphs.push(["posesLow", 1 - getAnimState(0.8)]);
							morphs.push(["posesMid", getAnimState(0.6)]);
							morphs.push(["posesHigh", getAnimState(0.2)]);
						}
						break;
				}

				if (slaveArmsDown > 0) {
					morphs.push(["poseArmsDown", Math.min(Math.max(slaveArmsDown, 0), 1)]);
				}
				if (slaveLegsClosed !== 0) {
					morphs.push(["posesLegsClosed", Math.min(Math.max(slaveLegsClosed, -1), 1)]);
				}
				break;
			case 1: // facial exp
				morphs.push([currentAnim[i], getAnimState(animPower[i])]);
				break;
			case 2: // breathing
				morphs.push(["posesInspect", getAnimState(animPower[i]) - (animPower[i] / 2)]);
				morphs.push(["bodyShape2", getAnimState(animPower[i]) - (animPower[i] / 2)]); // note, may need to change this morph if Elohiem decides to use it for something later; see commented code ln 2321
				break;
			case 3: // blink
				morphs.push(["eyesClosed", Math.min(Math.max(getAnimState(1.0), 0), 1)]);
				break;
			case 4: // look X
				if (currentAnim[i] === "") {
					break; // little catch code if it's waiting for look Y to reset
				}
				morphs.push([currentAnim[i], getAnimState(animPower[i] * 0.5)]);
				if (currentAnim[i] === "headBendLeft" || currentAnim[i] === "headTwistLeft") {
					if (eyeMoveReciprocal) {
						morphs.push(["eyesLookRight", getAnimState(animPower[i] * 0.75)]);
					}
					else {
						morphs.push(["eyesLookLeft", getAnimState(animPower[i] * 0.75)]);
					}
				}
				else {
					if (eyeMoveReciprocal) {
						morphs.push(["eyesLookLeft", getAnimState(animPower[i] * 0.75)]);
					}
					else {
						morphs.push(["eyesLookRight", getAnimState(animPower[i] * 0.75)]);
					}
				}
				break;
			case 5: // look Y
				switch (currentAnim[i]) {
					case "up":
						morphs.push(["headBendBackwards", getAnimState(animPower[i] * 0.15)]);
						if (eyeMoveReciprocal) {
							morphs.push(["eyesLookDown", getAnimState(animPower[i] * 0.5)]);
						}
						else {
							morphs.push(["eyesLookUp", getAnimState(animPower[i] * 0.5)]);
						}
						break;
					case "down":
						morphs.push(["headBendForward", getAnimState(animPower[i] * 0.25)]);
						if (eyeMoveReciprocal) {
							morphs.push(["eyesLookUp", getAnimState(animPower[i] * 0.5)]);
						}
						else {
							morphs.push(["eyesLookDown", getAnimState(animPower[i] * 0.5)]);
						}
						break;
				}
				break;
		}

		// do animation tick
		if (isAnimTick) { // this is needed so that mouse movement frame updates don't break animation state
			if (animState[i] === 1.0 || animFrame[i] === 0) { // pause during start and apogee of anim track
				animDelay[i]--;
				if (animDelay[i] <= 0) {
					switch (i) {
						case 0: // pose
							if (App.Art.artSize === 1) {
								animDelay[i] = (Math.floor(Math.random() * 4) + 1) * V.animFPS;
							}
							else {
								animDelay[i] = (Math.floor(Math.random() * 10) + 10) * V.animFPS;
							}
							break;
						case 1: // facial exp
							animDelay[i] = (Math.floor(Math.random() * 20) + 10) * V.animFPS;
							break;
						case 2: // breathing
							animDelay[i] = (Math.floor(Math.random() * 2) + 1) * V.animFPS;
							break;
						case 3: // blink
							animDelay[i] = 0;
							break;
						case 4: // look X
						case 5: // look Y
							animDelay[i] = (Math.floor(Math.random() * 10) + 5) * V.animFPS;
							break;
					}
					animFrame[i]++;
				}
			}
			else if (animState[i] === -1.0 && animFrame[i] > 0) { // end of anim track
				currentAnim[i] = "";
			}
			else {
				animFrame[i]++;
			}
		}
	}
};

App.Art.applyMorphs = function(slave, scene, p, isAnimating) {
	App.Art.seed = slave.ID + 3000;

	let morphs = [];

	function convertRange(sourceMin, sourceMax, targetMin, targetMax, value) {
		let out = (targetMax-targetMin);
		out /= (sourceMax-sourceMin);
		out *= (value-sourceMin);
		return out +targetMin;
	}

	if (slave.dick !== 0 || (!(slave.scrotum <= 0 || slave.balls <= 0))) {
		morphs.push(["futaGenFix", 1]);
	}

	if (p.applyPumps) {
		morphs.push(["posesHeels", 1]);
	}

	if (p.applyExtremeHeels) {
		morphs.push(["posesExtremeHeels", 1]);
	}

	if (p.applyExtremeHeels2) {
		morphs.push(["posesExtremeHeels2", 1]);
	}

	if (!isAnimating) { // animation code now handles these morphs directly
		if (slave.arm.right && slave.arm.left && slave.leg.right && slave.leg.left) {
			if (scene.inspect) {
				morphs.push(["posesInspect2", 1]);
				morphs.push(["posesInspectGen2", 1]);
			} else {
				if (slave.devotion > 50) {
					morphs.push(["posesHigh", 1]);
				} else if (slave.devotion > -20) {
					morphs.push(["posesMid", 1]);
				} else {
					morphs.push(["posesLow", 1]);
				}

				if (slave.scrotum > 20) {
					if (slave.devotion <= 50) {
						morphs.push(["posesArmsDown", Math.max(Math.min(-slave.weight/300/3.5, -slave.scrotum/200), -0.5)]);
					}

					if (p.applyExtremeHeels || p.applyExtremeHeels2) {
						morphs.push(["posesLegsClosed", Math.max(Math.min(-slave.weight/300/3.5 - 0.5, -slave.scrotum/20), -1.5)]);
					} else {
						morphs.push(["posesLegsClosed", Math.max(Math.min(-slave.weight/300/3.5, -slave.scrotum/20), -1.5)]);
					}
				} else {
					if (slave.devotion <= 50) {
						morphs.push(["posesArmsDown", Math.max(-slave.weight/300/3.5, -0.5)]);
					}

					if (p.applyExtremeHeels || p.applyExtremeHeels2) {
						morphs.push(["posesLegsClosed", Math.max(-slave.weight/300/3.5 - 0.5, -1.5)]);
					} else {
						morphs.push(["posesLegsClosed", Math.max(-slave.weight/300/3.5, -1.5)]);
					}
				}
			}
		}

		if (slave.trust < 0) {
			morphs.push(["expressionsFear", Math.abs(slave.trust)/100]);
		} else {
			morphs.push(["expressionsHappy", slave.trust/100]);
		}
	}

	// used for interpolating mixed race based on slave ID
	// let races = ["raceWhite", "raceAsian", "raceLatina", "raceBlack", "racePacific", "raceEuropean", "raceAmerindian", "raceSemitic", "raceEastern", "raceAryan", "raceLatina", "raceMalay"];
	// let index1 = Math.floor(App.Art.random() * races.length);
	// let index2 = Math.floor(App.Art.random() * (races.length-1));
	/*
	switch (slave.race) {
		case "white":
			morphs.push(["raceWhite", 1]); break;
		case "asian":
			morphs.push(["raceAsian", 1]); break;
		case "latina":
			morphs.push(["raceLatina", 1]); break;
		case "black":
			morphs.push(["raceBlack", 1]); break;
		case "pacific islander":
			morphs.push(["racePacific", 1]); break;
		case "southern european":
			morphs.push(["raceEuropean", 1]); break;
		case "amerindian":
			morphs.push(["raceAmerindian", 1]); break;
		case "semitic":
			morphs.push(["raceSemitic", 1]); break;
		case "middle eastern":
			morphs.push(["raceEastern", 1]); break;
		case "indo-aryan":
			morphs.push(["raceAryan", 1]); break;
		case "malay":
			morphs.push(["raceMalay", 1]); break;
		case "mixed race":
			morphs.push([races[index1], 0.5]);
			races.splice(index1, index1);
			morphs.push([races[index2], 0.5]);
			break;
	}

	switch (slave.race) {
		case "white":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceEurope1", 1]); break;
				case "masculine":
					morphs.push(["raceEurope10", 1]); break;
				case "androgynous":
					morphs.push(["raceEurope15", 1]); break;
				case "cute":
					morphs.push(["raceEurope16", 1]); break;
				case "sensual":
					morphs.push(["raceEurope17", 1]); break;
				case "exotic":
					morphs.push(["raceEurope14", 1]); break;
			} break;
		case "asian":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAsia1", 1]); break;
				case "masculine":
					morphs.push(["raceAsia17", 1]); break;
				case "androgynous":
					morphs.push(["raceAsia10", 1]); break;
				case "cute":
					morphs.push(["raceAsia14", 1]); break;
				case "sensual":
					morphs.push(["raceAsia15", 1]); break;
				case "exotic":
					morphs.push(["raceAsia16", 1]); break;
			} break;
		case "latina":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAfrica4", 1]); break;
				case "masculine":
					morphs.push(["raceAfrica6", 1]); break;
				case "androgynous":
					morphs.push(["raceAfrica1", 1]); break;
				case "cute":
					morphs.push(["raceAfrica5", 1]); break;
				case "sensual":
					morphs.push(["raceAfrica10", 1]); break;
				case "exotic":
					morphs.push(["raceAfrica2", 1]); break;
			} break;
		case "black":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAfrica3", 1]); break;
				case "masculine":
					morphs.push(["raceAfrica6", 1]); break;
				case "androgynous":
					morphs.push(["raceAfrica1", 1]); break;
				case "cute":
					morphs.push(["raceAfrica7", 1]); break;
				case "sensual":
					morphs.push(["raceAfrica8", 1]); break;
				case "exotic":
					morphs.push(["raceAfrica9", 1]); break;
			} break;
		case "pacific islander":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAsia8", 1]); break;
				case "masculine":
					morphs.push(["raceAsia9", 1]); break;
				case "androgynous":
					morphs.push(["raceAsia6", 1]); break;
				case "cute":
					morphs.push(["raceAsia7", 1]); break;
				case "sensual":
					morphs.push(["raceAsia19", 1]); break;
				case "exotic":
					morphs.push(["raceAsia5", 1]); break;
			} break;
		case "southern european":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceEurope11", 1]); break;
				case "masculine":
					morphs.push(["raceEurope12", 1]); break;
				case "androgynous":
					morphs.push(["raceEurope3", 1]); break;
				case "cute":
					morphs.push(["raceEurope2", 1]); break;
				case "sensual":
					morphs.push(["raceEurope18", 1]); break;
				case "exotic":
					morphs.push(["raceEurope13", 1]); break;
			} break;
		case "amerindian":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAfrica4", 1]); break;
				case "masculine":
					morphs.push(["raceAfrica6", 1]); break;
				case "androgynous":
					morphs.push(["raceAfrica1", 1]); break;
				case "cute":
					morphs.push(["raceAfrica5", 1]); break;
				case "sensual":
					morphs.push(["raceAfrica10", 1]); break;
				case "exotic":
					morphs.push(["raceAfrica2", 1]); break;
			} break;
		case "semitic":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAfrica3", 1]); break;
				case "masculine":
					morphs.push(["raceAfrica6", 1]); break;
				case "androgynous":
					morphs.push(["raceAfrica1", 1]); break;
				case "cute":
					morphs.push(["raceAfrica7", 1]); break;
				case "sensual":
					morphs.push(["raceAfrica8", 1]); break;
				case "exotic":
					morphs.push(["raceAfrica9", 1]); break;
			} break;
		case "middle eastern":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceEurope6", 1]); break;
				case "masculine":
					morphs.push(["raceEurope4", 1]); break;
				case "androgynous":
					morphs.push(["raceEurope8", 1]); break;
				case "cute":
					morphs.push(["raceEurope5", 1]); break;
				case "sensual":
					morphs.push(["raceEurope9", 1]); break;
				case "exotic":
					morphs.push(["raceEurope7", 1]); break;
			} break;
		case "indo-aryan":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAsia3", 1]); break;
				case "masculine":
					morphs.push(["raceAsia20", 1]); break;
				case "androgynous":
					morphs.push(["raceAsia18", 1]); break;
				case "cute":
					morphs.push(["raceAsia4", 1]); break;
				case "sensual":
					morphs.push(["raceAsia12", 1]); break;
				case "exotic":
					morphs.push(["raceAsia11", 1]); break;
			} break;
		case "malay":
			switch (slave.faceShape) {
				case "normal":
					morphs.push(["raceAsia2", 1]); break;
				case "masculine":
					morphs.push(["raceAsia13", 1]); break;
				case "androgynous":
					morphs.push(["raceAsia6", 1]); break;
				case "cute":
					morphs.push(["raceAsia7", 1]); break;
				case "sensual":
					morphs.push(["raceAsia19", 1]); break;
				case "exotic":
					morphs.push(["raceAsia5", 1]); break;
			} break;
		case "mixed race":
			//morphs.push([races[index1], 0.5]);
			//races.splice(index1, index1);
			//morphs.push([races[index2], 0.5]);
			break;
	}*/

	morphs.push(["obliqueLower", App.Art.random()]);
	morphs.push(["iliacLower", App.Art.random()]);
	morphs.push(["monsPubis", App.Art.random()*2-1]);
	morphs.push(["scapulaFold", App.Art.random()]);
	morphs.push(["lumbarAngle", App.Art.random()]);
	morphs.push(["chestDepression", App.Art.random()]);
	morphs.push(["adductorGap", convertRange(-1, 1, -0.5, 1, App.Art.random()*2-1)]);
	morphs.push(["lowerShape1", App.Art.random()]);

	switch (slave.ears) {
		case "pointy" :
			morphs.push(["earShapePointy", 1]);
			break;
		default:
			break;
	}

	let ribShape = ["ribShape0", "ribShape1", "ribShape2", "ribShape3", "ribShape4", "ribShape5"];
	let rib = Math.floor(App.Art.random() * ribShape.length);
	if (rib > 0) {
		morphs.push([ribShape[rib], 1]);
	}

	/* Elohiem, if you decide to reactivate this bit, note that I'm using "bodyShape2" for breathing animations --BI
	let bodyShape = ["bodyShape0", "bodyShape1", "bodyShape2", "bodyShape3", "bodyShape4", "bodyShape5"];
	let body = Math.floor(App.Art.random() * bodyShape.length);
	if (body > 0) {
		morphs.push([bodyShape[body], 1]);
	}*/

	/*
	if (slave.lips < 10) {
		morphs.push(["lipsShapeThin", 1]);
	} else if (slave.lips < 20) {
		morphs.push(["lipsShapeNormal", 1]);
	} else if (slave.lips < 40) {
		morphs.push(["lipsShapePretty", 1]);
	} else if (slave.lips < 70) {
		morphs.push(["lipsShapePlush", 1]);
	} else if (slave.lips < 95) {
		morphs.push(["lipsShapeHuge", 1]);
	} else {
		morphs.push(["lipsShapeFacepussy", slave.lips/100]);
	}*/

	if (slave.lips < 15) {
		morphs.push(["lips", (slave.lips-15)/30]);
	} else {
		morphs.push(["lips", (slave.lips-15)/42]);
	}

	let eye = Math.floor(App.Art.random() * 8);
	switch (eye) {
		case 0: break;
		case 1: morphs.push(["eyeShapeWide", 1]); break;
		case 2: morphs.push(["eyeShapeRound", 0.5]); break;
		case 3: morphs.push(["eyeShapeSmall", 1]); break;
		case 4: morphs.push(["eyeShapeSlit", 1]); break;
		case 5: morphs.push(["eyeShapeCute", 1]); break;
		case 6: morphs.push(["eyeShapeAlmond", 0.5]); break;
		case 7: morphs.push(["eyeShapeOpen", 0.5]); break;
	}

	// eyeShapeInward2
	// eyeShapeInward1

	// morphs.push(["eyeShapeInner" + Math.ceil(App.Art.random() * 3), App.Art.random()]);
	// morphs.push(["eyeShapeOuter" + Math.ceil(App.Art.random() * 3), App.Art.random()]);

	let race = slave.race;
	if (race === "mixed race") {
		let raceChoice = ["southern european", "indo-aryan", "semitic"];
		race = raceChoice[Math.floor(App.Art.random() * raceChoice.length)];
	}

	switch (race) {
		case "white":
		case "southern european":
			switch (Math.floor(App.Art.random() * 6)) {
				case 0: break;
				case 1: morphs.push(["noseShapeWide", 1]); break;
				case 2: morphs.push(["noseShapeForward", 0.5]); break;
				case 3: morphs.push(["noseShapeFlat", 1]); break;
				case 4: morphs.push(["noseShapeTriangular", 1]); break;
				case 5: morphs.push(["noseShapeSmall", 1]); break;
			}
			// morphs.push(["noseShapeEurope"+ Math.ceil(App.Art.random() * 8), 1]);
			morphs.push(["lipsShapeEurope"+ Math.ceil(App.Art.random() * 8) + 1, 1]);
			// morphs.push(["eyeShapeUndereye"+ Math.ceil(App.Art.random() * 2), 1]);
			break;

		case "asian":
		case "malay":
		case "pacific islander":
		case "middle eastern":
		case "indo-aryan":
			morphs.push(["noseShapeAsia"+ Math.ceil(App.Art.random() * 9), 1]);
			morphs.push(["lipsShapeAsia"+ Math.ceil(App.Art.random() * 5), 1]);
			// morphs.push(["eyeShapeFold"+ Math.ceil(AApp.Art.random() * 3), 1]);
			morphs.push(["eyeShapeUndereye"+ Math.ceil(App.Art.random() * 3)+2, 1]);
			break;

		case "black":
		case "latina":
		case "amerindian":
		case "semitic":
			morphs.push(["noseShapeAfrica"+ Math.ceil(App.Art.random() * 12), 1]);
			morphs.push(["lipsShapeAfrica"+ Math.ceil(App.Art.random() * 7), 1]);
			// morphs.push(["eyeShapeUndereye"+ Math.ceil(App.Art.random(slave.ID+42) * 2), 1]);
			break;

		case "mixed race":
			// morphs.push([races[index1], 0.5]);
			// races.splice(index1, index1);
			// morphs.push([races[index2], 0.5]);
			break;
	}

	let foreheadShape = ["foreheadShapeNormal", "foreheadShapeRound", "foreheadShapeSmall"];
	let forehead = Math.floor(App.Art.random() * foreheadShape.length);
	if (forehead > 0) {
		morphs.push([foreheadShape[forehead], 1]);
	}

	switch (slave.faceShape) {
		case "normal":
			break;
		case "masculine":
			morphs.push(["faceShapeMasculine", 0.6]); break;
		case "androgynous":
			morphs.push(["faceShapeAndrogynous", 0.8]); break;
		case "cute":
			morphs.push(["faceShapeCute", 1]); break;
		case "sensual":
			morphs.push(["faceShapeSensual", 0.8]); break;
		case "exotic":
			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", Math.min(-(slave.boobs-700)/600, 1)]);
	} else {
		switch (slave.boobShape) {
			case "normal":
				morphs.push(["boobShapeNormal", ((slave.boobs-600)**(1/3)/17) * (175/p.height)]); break;
			case "perky":
				morphs.push(["boobShapePerky", ((slave.boobs-600)**(1/3)/15) * (175/p.height)]); break;
			case "saggy":
				morphs.push(["boobShapeSaggy", ((slave.boobs-600)**(1/3)/28) * (175/p.height)]); break;
			case "torpedo-shaped":
				morphs.push(["boobShapeTorpedo", ((slave.boobs-600)**(1/3)/8) * (175/p.height)]); break;
			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)/4) * (175/p.height)]); break;
			case "spherical":
				// special case to make nipple work
				if (slave.nipples === "flat" || slave.nipples === "inverted" || !p.applyNipples) {
					morphs.push(["boobShapeSpherical", ((slave.boobs-600)**(1/3)/15) * (175/p.height)]); break;
				} else {
					morphs.push(["boobShapeSphericalNippleFix", ((slave.boobs-600)**(1/3)/15) * (175/p.height)]); break;
				}
		}
	}

	if (p.applyNipples) {
		switch (slave.nipples) {
			case "flat":
				break;
			case "huge":
				morphs.push(["nipplesHuge", (slave.boobs**(1/3)/10 + 0.5) * (175/p.height)]); break;
			case "tiny":
				morphs.push(["nipplesHuge", (slave.boobs**(1/3)/30 + 0.1) * (175/p.height)]); break;
			case "cute":
				morphs.push(["nipplesHuge", (slave.boobs**(1/3)/20 + 0.25) * (175/p.height)]); break;
			case "puffy":
				morphs.push(["nipplesPuffy", (slave.boobs**(1/3)/10 + 0.5) * (175/p.height)]); break;
			case "inverted":
				break;
			case "partially inverted":
				morphs.push(["nipplesPuffy", (slave.boobs**(1/3)/20 + 0.25) * (175/p.height)]); break;
			case "fuckable":
				morphs.push(["nipplesHuge", (slave.boobs**(1/3)/5 + 0.6) * (175/p.height)]); break;
		}
	}

	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) {
		if (slave.dick <= 8) {
			morphs.push(["dick", (slave.dick * (175/p.height) / 5.2) -1.05]);
		} else {
			morphs.push(["dick", ((slave.dick - 8) * (175/p.height) / 2.2) + 0.55]);
		}
	}
	if (slave.vagina === -1) {
		morphs.push(["vaginaRemove", 1]);
	}
	if (slave.scrotum <= 0 || slave.balls <= 1) {
		morphs.push(["ballsRemove", 1]);
	} else {
		if (slave.scrotum > 0) {
			let scr = Math.min(slave.scrotum, 40);
			morphs.push(["balls", convertRange(2, 10, 0, 0.75, scr * 2.5 * 0.6 *(175/p.height))]);
			morphs.push(["scrotum", convertRange(1, 10, 0, 2.0, scr * (175/p.height))]);
		}
	}

	morphs.push(["areolae", convertRange(0, 4, -1, 5, slave.areolae)]);
	morphs.push(["shoulders", slave.shoulders/1.2]);

	scene.models[0].transform.scale = p.height/175;

	if (slave.muscles > 0) {
		morphs.push(["muscles", slave.muscles/33]);
	}

	morphs.push(["belly2", slave.belly**(1/3)/24.6]);

	morphs.push(["hips", slave.hips/2]);

	if (slave.butt<=1) {
		morphs.push(["butt", convertRange(0, 1, -1.5, -0.75, slave.butt)]);
	} else {
		morphs.push(["butt", convertRange(2, 20, 0, 3.5, slave.butt)]);
	}

	if (slave.waist > 0) {
		morphs.push(["waist", -slave.waist/100]);
	} else {
		morphs.push(["waist", -slave.waist/50]);
	}

	if (slave.weight >= 0) {
		morphs.push(["weight2", slave.weight/300]);
	} else {
		morphs.push(["weightThin", -slave.weight/80]);
	}

	if (slave.visualAge < 20) {
		morphs.push(["physicalAgeYoung", -(Math.max(slave.visualAge, 18)-20)/10]); // clamp underage
	} else {
		morphs.push(["physicalAgeOld", (slave.visualAge-20)/52]);
	}

	if (!slave.arm.left) {
		morphs.push(["amputeeLeftArm", 1]);
	}
	if (!slave.arm.right) {
		morphs.push(["amputeeRightArm", 1]);
	}
	if (!slave.leg.left) {
		morphs.push(["amputeeLeftLeg", 1]);
	}
	if (!slave.leg.right) {
		morphs.push(["amputeeRightLeg", 1]);
	}

	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

	if (V.seeAnimation) {
		App.Art.getAnimState(slave, scene, p, morphs, isAnimating);
	}

	App.Art.resetMorphs(scene);

	for (let i =0; i < scene.models[0].morphs.length; i++) {
		for (let j =0; j < morphs.length; j++) {
			if (scene.models[0].morphs[i].morphId === morphs[j][0]) {
				scene.models[0].morphs[i].value = morphs[j][1];
			}
		}
	}
};

repo_name: Fanatey/fc

license: mit


code:
OUTDATED!!!

///////////////// MORPHS /////////////////
General
	physicalAgeYoung
	physicalAgeOld
	weight
	weightThin
	muscles
	waist
	belly
	hips
	butt
	lips
	shoulders
	areolae
	dick
	balls
	scrotum
	foreskin
	ballsRemove
	dickRemove
	vaginaRemove            - just closes the vagina

amputee
	leftArm
	rightArm
	leftLeg
	rightLeg

boobShape
	small
	normal
	perky
	saggy
	torpedo-shaped          (torpedo)
	downward-facing         (downward)
	wide-set                (wide)
	spherical

nipples
	huge
	puffy

faceShape
	normal
	masculine
	androgynous
	cute
	sensual
	exotic

lips
	thin
	normal
	pretty
	plush
	huge
	facepussy

eyes
	normal
	wide
	round
	cute
	slit
	small
	open

nose
	normal
	wide
	forward
	flat
	triangular
	small

forehead
	normal
	round
	small

expressions
	happy
	fear

race
	white
	asian
	latina
	black
	pacific islander        (pacific)
	southern european       (european)
	amerindian
	semitic
	middle eastern          (eastern)
	indo-aryan              (aryan)
	malay

poses
	high
	mid
	low

///////////////// MATERIALS /////////////////

general
    eye color
    hColor
    makeup
    fingernails

skin color
    pure white		        (Ceridwen)
    ivory		            (Ceridwen)
    white		            (Ceridwen)
    extremely pale		    (Celinette)
    very pale		        (Celinette)
    pale		            (Kimmy)
    extremely fair		    (Kimmy)
    very fair		        (Saffron)
    fair		            (Saffron)
    light		            (FemaleBase)
    light olive		        (FemaleBase)
    tan		                (Reagan)
    olive		            (Kathy)
    bronze		            (Mylou)
    dark olive		        (Adaline)
    dark		            (Daphne)
    light beige		        (Minami)
    beige		            (Tara)
    dark beige		        (Topmodel)
    light brown		        (Topmodel)
    brown		            (Angelica)
    dark brown		        (Angelica)
    black		            (DarkSkin)
    ebony		            (DarkSkin)
    pure black		        (DarkSkin)

dick color
    pure white	            (WhiteFuta)
    ivory	                (WhiteFuta)
    white	                (WhiteFuta)
    extremely pale	        (WhiteFuta)
    very pale	            (WhiteFuta)
    pale	                (WhiteFuta)
    extremely fair      	(WhiteFuta)
    very fair	            (LightFuta)
    fair	                (LightFuta)
    light	                (LightFuta)
    light olive	            (LightFuta)
    tan	                    (MidFuta)
    olive	                (MidFuta)
    bronze	                (MidFuta)
    dark olive          	(MidFuta)
    dark	                (MidFuta)
    light beige	            (MidFuta)
    beige	                (MidFuta)
    dark beige	            (DarkFuta)
    light brown	            (DarkFuta)
    brown	                (DarkFuta)
    dark brown	            (DarkFuta)
    black	                (DarkFuta)
    ebony	                (DarkFuta)
    pure black	            (DarkFuta)

///////////////// MESH /////////////////

General
    genesis8
    vagina
    dick

Hairstyle
	Neat					(Samira)
	Afro					(Yara)
	Braided					(Mishka)
	Cornrows				(Tiger)
	Curled					(Havana)
	Dreadlocks				(Dreads)
	Eary					(Georgina)
	In a bun				(Adia)
	In a messy bun			(Krayon)
	In a ponytail			(Ponytail)
	In tails				(Kinley)
	Luxurious				(Baroness)
	Messy					(Messy)
	Permed					(Ichigo)
	Shaved sides			(Rebel)
	Up						(Pina)
	Half-Shaved				(Edit)
	Bangs					(Neko)
	Hime					(Nyo)
	Drills					(Bunny)