diff --git a/css/art/art.css b/css/art/art.css index 2610af5ed5c82ca8c8a1a98820ad24740e1022f0..f5d56d578e6036481788736c3f6e76d3cff323e7 100644 --- a/css/art/art.css +++ b/css/art/art.css @@ -50,11 +50,10 @@ img { .lrgRender { height: 531px; - width: 531px; + width: 506px; margin-right: -50px; - margin-left: -50px; + margin-left: 0px; float: right; - z-index: -1; } .lrgVector { @@ -67,7 +66,7 @@ img { } .lrgRender > div.mask { - width: 150px; + width: 180px; height: 100%; background: linear-gradient(90deg, rgba(17, 17, 17, 1), rgba(17, 17, 17, 0.8) 60%, rgba(17, 17, 17, 0)); z-index: 1; diff --git a/src/art/artJS.js b/src/art/artJS.js index 3aaa07506a12874079ead5e3a4e910bc074194f9..b34793724f7d5fce012d1d9381459fec219d7dce 100644 --- a/src/art/artJS.js +++ b/src/art/artJS.js @@ -121,7 +121,9 @@ App.Art.SlaveArtElement = function(artSlave, artSize, UIDisplay) { return App.Art.legacyVectorArtElement(artSlave, UIDisplay); } else if (imageChoice === 3) { /* VECTOR ART REVAMP*/ return App.Art.revampedVectorArtElement(artSlave); - } else { /* RENDERED IMAGES BY SHOKUSHU */ + } else if (imageChoice === 4) { /* Elohiem's Webgl */ + return App.Art.webglArtElement(artSlave, artSize); + } else if (imageChoice === 0) { /* RENDERED IMAGES BY SHOKUSHU */ return App.Art.renderedArtElement(artSlave, artSize); } }; @@ -137,6 +139,7 @@ App.Art.SlaveArtElement = function(artSlave, artSize, UIDisplay) { * @param {number} [UIDisplay] (optional, only used by legacy art): icon UI Display for vector art, 1 for on */ App.Art.refreshSlaveArt = function(artSlave, artSize, elementID, UIDisplay = 0) { + console.log('refresh'); if ($('#' + elementID).length) { if (V.seeImages === 1) { let image = document.createElement('div'); @@ -161,6 +164,95 @@ App.Art.refreshSlaveArt = function(artSlave, artSize, elementID, UIDisplay = 0) } }; +App.Art.webglInitialize = function() { + // asynchronously load webgl assets if present (this can briefly hang the browser) + let loadLockID = LoadScreen.lock(); + let script = document.createElement("script"); + script.onload = function() { + // load model/morphs/textures assets + let sceneData = App.Art.sceneGetData(); + // load default dictionary containing camera/light/morph/material values + let scene = App.Art.sceneGetParams(); + scene.lockView = false; + scene.resetView = false; + scene.faceView = true; + scene.camera.xr = -6; + scene.camera.z = -275; + scene.camera.y = 127; + scene.camera.fov = 40; + + App.Art.scenes = {}; + App.Art.defaultScene = JSON.parse(JSON.stringify(scene)); + + // start Webgl engine, textures are streamed asynchronously + App.Art.engine = new App.Art.Engine(); + App.Art.engine.bind(sceneData); + App.Art.engineReady = true; + LoadScreen.unlock(loadLockID); + + // when ready fires event to art elements to start rendering + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event("engineLoaded")); + } + }; + script.onerror = function() { + App.Art.engineReady = false; + LoadScreen.unlock(loadLockID); + + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event("engineFailed")); + } + }; + script.src = "resources/webgl/scene1.js"; + document.head.appendChild(script); +}(); + +/** + * @param {App.Entity.SlaveState} slave + * @param {number} artSize + * @returns {HTMLElement} + */ +App.Art.webglArtElement = function(slave, artSize) { + let container = document.createElement("div"); + container.setAttribute("class", "artContainer"); + container.innerText = "Loading..."; + + container.addEventListener("engineLoaded", function(e) { + // when engine is ready, attach default scene to new slaves + if (!(slave.ID in App.Art.scenes)) { + App.Art.scenes[slave.ID] = JSON.parse(JSON.stringify(App.Art.defaultScene)); + } + let scene = App.Art.scenes[slave.ID]; + + // apply the model transforms + App.Art.applySurfaces(slave, scene); + App.Art.applyMaterials(slave, scene); + App.Art.applyMorphs(slave, scene); + + // draw on canvas and create UI + container.innerText = ""; + let canvas = App.Art.createWebglUI(container, slave, artSize, scene); + App.Art.engine.render(scene, canvas); + }, true); + + container.addEventListener("engineFailed", function(e) { + container.style.color = "#BB2027"; + container.innerText = "Failed to start WebGL engine."; + }, true); + + // incase engine is loaded, trigger listeners manually + if (App.Art.engineReady === true) { + container.dispatchEvent(new Event("engineLoaded")); + } + if (App.Art.engineReady === false) { + container.dispatchEvent(new Event("engineFailed")); + } + + return container; +}; + /** * @param {App.Entity.SlaveState} slave * @param {number} artSize @@ -324,7 +416,141 @@ globalThis.extractColor = (function() { ["strawberry-blonde", "#e5a88c"], /* these are not actually FreeCities canon, but like to appear in custom descriptions */ ["brunette", "#6d4936"], - ["dark", "#463325"] + ["dark", "#463325"], + + /* these are HTML color names supported by most browsers */ + ["aliceblue", "#f0f8ff"], + ["antiquewhite", "#faebd7"], + ["aqua", "#00ffff"], + ["aquamarine", "#7fffd4"], + ["azure", "#f0ffff"], + ["beige", "#f5f5dc"], + ["bisque", "#ffe4c4"], + ["blanchedalmond", "#ffebcd"], + ["blueviolet", "#8a2be2"], + ["burlywood", "#deb887"], + ["cadetblue", "#5f9ea0"], + ["chartreuse", "#7fff00"], + ["coral", "#ff7f50"], + ["cornflowerblue", "#6495ed"], + ["cornsilk", "#fff8dc"], + ["crimson", "#dc143c"], + ["cyan", "#00ffff"], + ["darkblue", "#00008b"], + ["darkcyan", "#008b8b"], + ["darkgoldenrod", "#b8860b"], + ["darkgray", "#a9a9a9"], + ["darkgreen", "#006400"], + ["darkkhaki", "#bdb76b"], + ["darkmagenta", "#8b008b"], + ["darkolivegreen", "#556b2f"], + ["darkorange", "#ff8c00"], + ["darkorchid", "#9932cc"], + ["darkred", "#8b0000"], + ["darksalmon", "#e9967a"], + ["darkseagreen", "#8fbc8f"], + ["darkslateblue", "#483d8b"], + ["darkslategray", "#2f4f4f"], + ["darkturquoise", "#00ced1"], + ["darkviolet", "#9400d3"], + ["deeppink", "#ff1493"], + ["deepskyblue", "#00bfff"], + ["dimgray", "#696969"], + ["dodgerblue", "#1e90ff"], + ["firebrick", "#b22222"], + ["floralwhite", "#fffaf0"], + ["forestgreen", "#228b22"], + ["fuchsia", "#ff00ff"], + ["gainsboro", "#dcdcdc"], + ["ghostwhite", "#f8f8ff"], + ["gold", "#ffd700"], + ["goldenrod", "#daa520"], + ["gray", "#808080"], + ["greenyellow", "#adff2f"], + ["honeydew", "#f0fff0"], + ["hotpink", "#ff69b4"], + ["indianred ", "#cd5c5c"], + ["indigo", "#4b0082"], + ["ivory", "#fffff0"], + ["khaki", "#f0e68c"], + ["lavender", "#e6e6fa"], + ["lavenderblush", "#fff0f5"], + ["lawngreen", "#7cfc00"], + ["lemonchiffon", "#fffacd"], + ["lightblue", "#add8e6"], + ["lightcoral", "#f08080"], + ["lightcyan", "#e0ffff"], + ["lightgoldenrodyellow", "#fafad2"], + ["lightgrey", "#d3d3d3"], + ["lightgreen", "#90ee90"], + ["lightpink", "#ffb6c1"], + ["lightsalmon", "#ffa07a"], + ["lightseagreen", "#20b2aa"], + ["lightskyblue", "#87cefa"], + ["lightslategray", "#778899"], + ["lightsteelblue", "#b0c4de"], + ["lightyellow", "#ffffe0"], + ["lime", "#00ff00"], + ["limegreen", "#32cd32"], + ["linen", "#faf0e6"], + ["magenta", "#ff00ff"], + ["maroon", "#800000"], + ["mediumaquamarine", "#66cdaa"], + ["mediumblue", "#0000cd"], + ["mediumorchid", "#ba55d3"], + ["mediumpurple", "#9370d8"], + ["mediumseagreen", "#3cb371"], + ["mediumslateblue", "#7b68ee"], + ["mediumspringgreen", "#00fa9a"], + ["mediumturquoise", "#48d1cc"], + ["mediumvioletred", "#c71585"], + ["midnightblue", "#191970"], + ["mintcream", "#f5fffa"], + ["mistyrose", "#ffe4e1"], + ["moccasin", "#ffe4b5"], + ["navajowhite", "#ffdead"], + ["navy", "#000080"], + ["oldlace", "#fdf5e6"], + ["olive", "#808000"], + ["olivedrab", "#6b8e23"], + ["orange", "#ffa500"], + ["orangered", "#ff4500"], + ["orchid", "#da70d6"], + ["palegoldenrod", "#eee8aa"], + ["palegreen", "#98fb98"], + ["paleturquoise", "#afeeee"], + ["palevioletred", "#d87093"], + ["papayawhip", "#ffefd5"], + ["peachpuff", "#ffdab9"], + ["peru", "#cd853f"], + ["plum", "#dda0dd"], + ["powderblue", "#b0e0e6"], + ["rebeccapurple", "#663399"], + ["rosybrown", "#bc8f8f"], + ["royalblue", "#4169e1"], + ["saddlebrown", "#8b4513"], + ["salmon", "#fa8072"], + ["sandybrown", "#f4a460"], + ["seagreen", "#2e8b57"], + ["seashell", "#fff5ee"], + ["sienna", "#a0522d"], + ["skyblue", "#87ceeb"], + ["slateblue", "#6a5acd"], + ["slategray", "#708090"], + ["snow", "#fffafa"], + ["springgreen", "#00ff7f"], + ["steelblue", "#4682b4"], + ["tan", "#d2b48c"], + ["teal", "#008080"], + ["thistle", "#d8bfd8"], + ["tomato", "#ff6347"], + ["turquoise", "#40e0d0"], + ["violet", "#ee82ee"], + ["wheat", "#f5deb3"], + ["white", "#ffffff"], + ["whitesmoke", "#f5f5f5"], + ["yellow", "#ffff00"], + ["yellowgreen", "#9acd32"] ]); /* these are HTML color names supported by most browsers */ @@ -501,6 +727,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#C39696"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#E1B585"; colorSlave.areolaColor = "#C39696"; @@ -752,6 +980,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#7C594B"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#AC7C4A"; colorSlave.areolaColor = "#7C594B"; @@ -873,6 +1103,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#92684C"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#B27554"; colorSlave.areolaColor = "#92684C"; @@ -994,6 +1226,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#AC8074"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#CFB48D"; colorSlave.areolaColor = "#AC8074"; @@ -1111,6 +1345,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#A7624F"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#CC8D53"; colorSlave.areolaColor = "#A7624F"; @@ -1228,6 +1464,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#AC8074"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#CFB48D"; colorSlave.areolaColor = "#AC8074"; @@ -1345,6 +1583,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#BF7577"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#DCA972"; colorSlave.areolaColor = "#BF7577"; @@ -1462,6 +1702,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#A7624F"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#CC8D53"; colorSlave.areolaColor = "#A7624F"; @@ -1579,6 +1821,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#976051"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#BA855E"; colorSlave.areolaColor = "#976051"; @@ -1696,6 +1940,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#C36E45"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#C17848"; colorSlave.areolaColor = "#C36E45"; @@ -1813,6 +2059,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#976051"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#BA855E"; colorSlave.areolaColor = "#976051"; @@ -1930,6 +2178,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#92684C"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#B27554"; colorSlave.areolaColor = "#92684C"; @@ -2051,6 +2301,8 @@ globalThis.skinColorCatcher = function(artSlave) { colorSlave.areolaColor = "#92684C"; colorSlave.labiaColor = "#F977A3"; break; + case "sun tanned": + case "spray tanned": case "tan": colorSlave.skinColor = "#B27554"; colorSlave.areolaColor = "#92684C"; diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js new file mode 100644 index 0000000000000000000000000000000000000000..ae624eb352489827bfe206054222a288b95732c9 --- /dev/null +++ b/src/art/webgl/art.js @@ -0,0 +1,629 @@ +App.Art.getMaterialById = function(scene, id) { + for (let i =0; i < scene.materials.length; i++) { + if(scene.materials[i].matId === id) { + return scene.materials[i]; + } + } + return null; +}; + +App.Art.getMorphById = function(scene, id) { + for (let i =0; i < scene.model.morphs.length; i++) { + if(scene.model.morphs[i].morphId === id) { + return scene.model.morphs[i]; + } + } + return null; +}; + +App.Art.getSurfaceById = function(scene, id) { + for (let i=0, count=0; i < scene.model.figures.length; i++) { + for (let j=0; j < scene.model.figures[i].surfaces.length; j++, count++) { + if(scene.model.figures[i].surfaces[j].surfaceId === id) { + return scene.model.figures[i].surfaces[j]; + } + } + } + return null; +}; + +App.Art.resetMorphs = function(slave, scene) { + for (let i =0; i < scene.model.morphs.length; i++) { + scene.model.morphs[i].value = App.Art.defaultScene.model.morphs[i].value; + } +}; + +App.Art.applySurfaces = function(slave, scene) { + let surfaces = []; + + if (slave.dick !== 0 || (!(slave.scrotum <= 0 || slave.balls <= 0))) { + 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]); + } + + // surfaces.push(["Arms", "visible", hasBothArms(slave)]); + // surfaces.push(["Fingernails", "visible", hasBothArms(slave)]); + // surfaces.push(["Legs", "visible", hasBothLegs(slave)]); + // surfaces.push(["Toenails", "visible", hasBothLegs(slave)]); + + let cockSkin; + let skin; + + switch (slave.skin) { + case "pure white": + case "ivory": + case "white": + cockSkin = "White"; + skin = "Ceridwen"; + break; + case "extremely pale": + case "very pale": + cockSkin = "White"; + skin = "Celinette"; + break; + case "pale": + case "extremely fair": + cockSkin = "White"; + skin = "Kimmy"; + break; + case "very fair": + case "fair": + cockSkin = "Light"; + skin = "Saffron"; + break; + case "light": + case "light olive": + cockSkin = "Light"; + skin = "FemaleBase"; + break; + case "sun tanned": + case "spray tanned": + case "tan": + cockSkin = "Light"; + skin = "Reagan"; + break; + case "olive": + cockSkin = "Mid"; + skin = "Kathy"; + break; + case "bronze": + cockSkin = "Mid"; + skin = "Mylou"; + break; + case "dark olive": + cockSkin = "Mid"; + skin = "Adaline"; + break; + case "dark": + cockSkin = "Mid"; + skin = "Daphne"; + break; + case "light beige": + cockSkin = "Mid"; + skin = "Minami"; + break; + case "beige": + cockSkin = "Mid"; + skin = "Tara"; + break; + case "dark beige": + case "light brown": + cockSkin = "Dark"; + skin = "Topmodel"; + break; + case "brown": + case "dark brown": + cockSkin = "Dark"; + skin = "Angelica"; + break; + case "black": + case "ebony": + case "pure black": + cockSkin = "Dark"; + skin = "DarkSkin"; + break; + } + + surfaces.push(["Futalicious_Genitalia_G8F_Glans_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Shaft_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Testicles_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Torso_Front_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Torso_Middle_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Torso_Back_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Futalicious_Genitalia_G8F_Rectum_Futalicious_Shell", "matId", cockSkin + "Futalicious_Genitalia_G8F_Glans_Futalicious_Shell"]); + surfaces.push(["Torso_Front", "matId", skin + "Torso"]); + surfaces.push(["Torso_Middle", "matId", skin + "Torso"]); + surfaces.push(["Torso_Back", "matId", skin + "Torso"]); + + surfaces.push(["Torso", "matId", skin + "Torso"]); + surfaces.push(["Face", "matId", skin + "Face"]); + surfaces.push(["Lips", "matId", skin + "Lips"]); + surfaces.push(["Ears", "matId", skin + "Ears"]); + surfaces.push(["Legs", "matId", skin + "Legs"]); + surfaces.push(["Arms", "matId", skin + "Arms"]); + surfaces.push(["EyeSocket", "matId", skin + "Face"]); + surfaces.push(["Toenails", "matId", skin + "Toenails"]); + surfaces.push(["Fingernails", "matId", skin + "Fingernails"]); + surfaces.push(["Genitalia", "matId", skin + "Genitalia"]); + surfaces.push(["Anus", "matId", skin + "Anus"]); + + for (let i=0, count=0; i < scene.model.figures.length; i++) { + for (let j=0; j < scene.model.figures[i].surfaces.length; j++, count++) { + for (let h =0; h < surfaces.length; h++) { + if (scene.model.figures[i].surfaces[j].surfaceId === surfaces[h][0]) { + scene.model.figures[i].surfaces[j][surfaces[h][1]] = surfaces[h][2]; + } + } + } + } +}; + +App.Art.applyMaterials = function(slave, scene) { + let materials = []; + + function hexToRgb(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]; + } + + let hairColor = hexToRgb(extractColor(slave.hColor)); + let lipsColor = hexToRgb(skinColorCatcher(slave).lipsColor); + // let lipsColor = hexToRgb("#ffffff"); + + let makeupColor; + let makeupOpacity; + let lipsGloss; + + switch (slave.makeup) { + case 1: + // Nice + makeupColor = "#ff69b4"; + makeupOpacity = 0.5; + lipsGloss = 32; + break; + case 2: + // Gorgeous + makeupColor = "#8b008b"; + makeupOpacity = 0.7; + lipsGloss = 10; + break; + case 3: + // Hair coordinated + makeupColor = extractColor(slave.hColor); + makeupOpacity = 0.3; + lipsGloss = 10; + break; + case 4: + // Slutty + makeupColor = "#B70000"; + makeupOpacity = 0.8; + lipsGloss = 5; + break; + case 5: + // Neon + makeupColor = "#DC143C"; + makeupOpacity = 1; + lipsGloss = 1; + break; + case 6: + // Neon hair coordinated + makeupColor = extractColor(slave.hColor); + makeupOpacity = 1; + lipsGloss = 1; + break; + case 7: + // Metallic + makeupColor = "#b22222"; + makeupOpacity = 0.7; + lipsGloss = 1; + break; + case 8: + // Metallic hair coordinated + makeupColor = extractColor(slave.hColor); + makeupOpacity = 0.7; + lipsGloss = 1; + break; + default: + makeupColor = "#ffffff"; + makeupOpacity = 0; + lipsGloss = 32; + break; + } + + makeupColor = 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 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 = hexToRgb(nailColor); + + + materials.push(["HeadThin", "Kd", hairColor]); + materials.push(["Head", "Kd", hairColor]); + materials.push(["TuckedThin", "Kd", hairColor]); + materials.push(["TuckedR", "Kd", hairColor]); + materials.push(["BangsThin", "Kd", hairColor]); + materials.push(["Bangs", "Kd", hairColor]); + materials.push(["Scalp", "Kd", hairColor]); + + let irisColor; + let scleraColor; + + if (hasAnyEyes(slave)) { + irisColor = hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.iris) : extractColor(slave.eye.right.iris))); + scleraColor = hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.sclera) : extractColor(slave.eye.right.sclera))); + } else { + irisColor = hexToRgb(extractColor("black")); + scleraColor = hexToRgb(extractColor("black")); + } + + materials.push(["Irises", "Kd", [irisColor[0] * 0.8, irisColor[1] * 0.8, irisColor[2] * 0.8]]); + materials.push(["Sclera", "Kd", [scleraColor[0] * 1.2, scleraColor[1] * 1.2, scleraColor[2] * 1.2]]); + materials.push(["Irises", "Ns", 4]); + + + switch (slave.skin) { + case "pure white": + case "ivory": + case "white": + materials.push(["CeridwenFingernails", "Kd", nailColor]); + materials.push(["CeridwenLips", "Kd", lipsColor]); + materials.push(["CeridwenLips", "Ns", lipsGloss]); + materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1.05, 1, 1]]); + break; + case "extremely pale": + case "very pale": + materials.push(["CelinetteFingernails", "Kd", nailColor]); + materials.push(["CelinetteLips", "Kd", lipsColor]); + materials.push(["CelinetteLips", "Ns", lipsGloss]); + materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1.05, 1, 1]]); + break; + case "pale": + case "extremely fair": + materials.push(["KimmyFingernails", "Kd", nailColor]); + materials.push(["KimmyLips", "Kd", lipsColor]); + materials.push(["KimmyLips", "Ns", lipsGloss]); + materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1, 0.95, 0.91]]); + break; + case "very fair": + case "fair": + materials.push(["SaffronFingernails", "Kd", nailColor]); + materials.push(["SaffronLips", "Kd", lipsColor]); + materials.push(["SaffronLips", "Ns", lipsGloss]); + materials.push(["LightFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1.1, 1.1, 1.1]]); + break; + case "light": + case "light olive": + materials.push(["FemaleBaseFingernails", "Kd", nailColor]); + materials.push(["FemaleBaseLips", "Kd", lipsColor]); + materials.push(["FemaleBaseLips", "Ns", lipsGloss]); + materials.push(["LightFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1.0, 1.0, 1.0]]); + break; + case "sun tanned": + case "spray tanned": + case "tan": + materials.push(["ReaganFingernails", "Kd", nailColor]); + materials.push(["ReaganLips", "Kd", lipsColor]); + materials.push(["ReaganLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.97, 0.95, 0.95]]); + break; + case "olive": + materials.push(["KathyFingernails", "Kd", nailColor]); + materials.push(["KathyLips", "Kd", lipsColor]); + materials.push(["KathyLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.95, 0.92, 0.92]]); + break; + case "bronze": + materials.push(["MylouFingernails", "Kd", nailColor]); + materials.push(["MylouLips", "Kd", lipsColor]); + materials.push(["MylouLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.91, 0.95, 0.98]]); + break; + case "dark olive": + materials.push(["AdalineFingernails", "Kd", nailColor]); + materials.push(["AdalineLips", "Kd", lipsColor]); + materials.push(["AdalineLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.90, 0.90, 0.90]]); + break; + case "dark": + materials.push(["DaphneFingernails", "Kd", nailColor]); + materials.push(["DaphneLips", "Kd", lipsColor]); + materials.push(["DaphneLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.88, 0.93, 0.96]]); + break; + case "light beige": + materials.push(["MinamiFingernails", "Kd", nailColor]); + materials.push(["MinamiLips", "Kd", lipsColor]); + materials.push(["MinamiLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.68, 0.74, 0.8]]); + break; + case "beige": + materials.push(["TaraFingernails", "Kd", nailColor]); + materials.push(["TaraLips", "Kd", lipsColor]); + materials.push(["TaraLips", "Ns", lipsGloss]); + materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.77, 0.77, 0.77]]); + break; + case "dark beige": + case "light brown": + materials.push(["TopmodelFingernails", "Kd", nailColor]); + materials.push(["TopmodelLips", "Kd", lipsColor]); + materials.push(["TopmodelLips", "Ns", lipsGloss]); + materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [1.7, 1.75, 1.75]]); + break; + case "brown": + case "dark brown": + materials.push(["AngelicaFingernails", "Kd", nailColor]); + materials.push(["AngelicaLips", "Kd", lipsColor]); + materials.push(["AngelicaLips", "Ns", lipsGloss]); + materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.85, 0.85, 0.85]]); + break; + case "black": + case "ebony": + case "pure black": + materials.push(["DarkSkinFingernails", "Kd", nailColor]); + materials.push(["DarkSkinLips", "Kd", lipsColor]); + materials.push(["DarkSkinLips", "Ns", lipsGloss]); + materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [0.7, 0.7, 0.77]]); + break; + } + + 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.applyMorphs = function(slave, scene) { + let morphs = []; + + function convertRange(sourceMin, sourceMax, targetMin, targetMax, value) { + return (targetMax-targetMin)/(sourceMax-sourceMin)*(value-sourceMin)+targetMin; + } + + if(hasBothArms(slave) && hasBothLegs(slave)) { + if (slave.devotion > 50) { + morphs.push(["posesHigh", 1]); + } else if (slave.trust >= -20) { + if (slave.devotion <= 20) { + morphs.push(["posesLow", 1]); + } else { + morphs.push(["posesMid", 1]); + } + } else { + morphs.push(["posesMid", 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 "mixed race": + /* + let races = ["raceWhite" , "raceAsian", "raceLatina", "raceBlack", "racePacific", "raceEuropean" ,"raceAmerindian", "raceSemitic", "raceEastern", "raceAryan", "raceLatina"]; + let rand = Math.random(); + let index1 = Math.floor(Math.random() * races.length); + let index2 = Math.floor(Math.random() * races.length-1); + morphs.push([races[index1], rand]); + races.splice(index1, index1); + morphs.push([races[index2], 1-rand]);*/ + break; + } + + switch (slave.faceShape) { + case "normal": + break; + case "masculine": + morphs.push(["faceShapeMasculine", 0.8]); break; + case "androgynous": + morphs.push(["faceShapeAndrogynous", 1]); break; + case "cute": + morphs.push(["faceShapeCute", 1]); break; + case "sensual": + morphs.push(["faceShapeSensual", 0.8]); break; + case "exotic": + morphs.push(["faceShapeExotic", 1]); break; + } + + if (slave.boobs < 500) { + morphs.push(["boobsSmall", 1-slave.boobs/500]); + } else { + switch (slave.boobShape) { + case "normal": + morphs.push(["boobShapeNormal", slave.boobs/2800]); break; + case "perky": + morphs.push(["boobShapePerky", slave.boobs/3500]); break; + case "saggy": + morphs.push(["boobShapeSaggy", slave.boobs/3500]); break; + case "torpedo-shaped": + morphs.push(["boobShapeTorpedo", slave.boobs/2000]); break; + case "downward-facing": + morphs.push(["boobShapeDownward", slave.boobs/3000]); break; + case "wide-set": + morphs.push(["boobShapeWide", slave.boobs/2500]); break; + } + } + + switch (slave.nipples) { + case "huge": + morphs.push(["nipplesHuge", 1]); break; + case "tiny": + morphs.push(["nipplesTiny", 1]); break; + case "cute": + morphs.push(["nipplesCute", 1]); break; + case "puffy": + morphs.push(["nipplesPuffy", 1]); break; + case "inverted": + morphs.push(["nipplesInverted", 1]); break; + case "partially inverted": + morphs.push(["nipplesPartiallyInverted", 1]); break; + case "fuckable": + morphs.push(["nipplesFuckable", 1]); break; + } + + if (slave.foreskin !== 0) { + morphs.push(["foreskin", 1]); + } + if (slave.dick === 0 && !(slave.scrotum <= 0 || slave.balls <= 0)) { + morphs.push(["dickRemove", 1]); + } else if (slave.dick !== 0) { + morphs.push(["dick", (slave.dick / 8) -1]); + } + if (slave.vagina === -1) { + morphs.push(["vaginaRemove", 1]); + } + if (slave.scrotum <= 0 || slave.balls <= 0) { + morphs.push(["ballsRemove", 1]); + } else { + if (slave.balls <= 2) { + morphs.push(["balls", convertRange(0, 2, -1, 0, slave.balls)]); + } else { + morphs.push(["balls", convertRange(2, 10, 0, 1.5, slave.balls)]); + } + if (slave.scrotum > 2) { + morphs.push(["scrotum", convertRange(2, 10, 0, 0.75, slave.scrotum)]); + } + } + + morphs.push(["areolae", convertRange(0, 4, 0, 5, slave.areolae)]); + morphs.push(["shoulders", convertRange(-2, 2, -1.5, 1.5, slave.shoulders)]); + morphs.push(["lips", convertRange(0, 100, -1, 3, slave.lips)]); + scene.transform.scale = slave.height/175; // height by object transform + if (slave.muscles > 0) { + morphs.push(["muscles", slave.muscles/50]); + } + if (slave.belly <= 15000) { + morphs.push(["belly", slave.belly/15000]); + } else if (slave.belly <= 150000) { + morphs.push(["belly", 1 + convertRange(15000, 150000, 0, 2, slave.belly)]); + } else { + morphs.push(["belly", 3 + (slave.belly-150000)/300000]); + } + + morphs.push(["hips", slave.hips/2]); + if (slave.butt<1) { + morphs.push(["butt", slave.butt-1]); + } else { + morphs.push(["butt", convertRange(1, 20, 0, 3, slave.butt)]); + } + + if (slave.waist > 75) { + morphs.push(["waist", -75/50]); + } else { + morphs.push(["waist", -slave.waist/50]); + } + + morphs.push(["weight", slave.weight/50]); + + if (slave.visualAge < 20) { + morphs.push(["physicalAgeYoung", -(slave.visualAge-20)/15]); + } else { + morphs.push(["physicalAgeOld", (slave.visualAge-20)/100]); + } + + if (!hasLeftArm(slave)) { + morphs.push(["amputeeLeftArm", 1]); + } + if (!hasRightArm(slave)) { + morphs.push(["amputeeRightArm", 1]); + } + if (!hasLeftLeg(slave)) { + morphs.push(["amputeeLeftLeg", 1]); + } + if (!hasRightLeg(slave)) { + morphs.push(["amputeeRightLeg", 1]); + } + + App.Art.resetMorphs(slave, scene); + + for (let i =0; i < scene.model.morphs.length; i++) { + for (let j =0; j < morphs.length; j++) { + if (scene.model.morphs[i].morphId === morphs[j][0]) { + scene.model.morphs[i].value = morphs[j][1]; + } + } + } +}; diff --git a/src/art/webgl/contents.txt b/src/art/webgl/contents.txt new file mode 100644 index 0000000000000000000000000000000000000000..2110aff9d4803faab3d50a0243884281561fdb2c --- /dev/null +++ b/src/art/webgl/contents.txt @@ -0,0 +1,148 @@ +///////////////// MORPHS ///////////////// +General + physicalAgeYoung + physicalAgeOld + weight + muscles + height - not an actual morph, scaled with model transform + waist + belly + hips + butt + lips + shoulders + boobsSmall + areolae + dick + balls + scrotum + foreskin + ballsRemove + dickRemove + vaginaRemove - just closes the vagina + +amputee + leftArm + rightArm + leftLeg + rightLeg + +boobShape + normal + perky + saggy + torpedo-shaped (torpedo) + downward-facing (downward) + wide-set (wide) + +nipples + huge + tiny + cute + puffy + inverted + partially inverted (partially) + fuckable + +faceShape + normal + masculine + androgynous + cute + sensual + exotic + +areolaeShape + heart + star + circle + +race + white + asian + latina + black + pacific islander (pacific) + southern european (european) + amerindian + semitic + middle eastern (eastern) + indo-aryan (aryan) + mixed race + +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 + bobHair diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js new file mode 100644 index 0000000000000000000000000000000000000000..d15de10c69f890e36df7df4bb315e71ce34d06b6 --- /dev/null +++ b/src/art/webgl/engine.js @@ -0,0 +1,701 @@ +'use strict'; + +App.Art.Engine = class { + constructor() { + this.vsSourceBg = `#version 300 es + precision highp float; + + in vec2 vertexPosition; + out vec2 vertexPos; + + void main() { + vertexPos = vertexPosition; + gl_Position = vec4(vertexPosition, 0.0, 1.0); + }`; + + this.fsSourceBg = `#version 300 es + precision highp float; + precision highp sampler2D; + + uniform sampler2D textSampler; + uniform vec4 backgroundColor; + + in vec2 vertexPos; + out vec4 outputColor; + + void main() { + vec2 textureCoord = vec2(vertexPos.s, -vertexPos.t) * 0.5 + 0.5; + vec3 c = backgroundColor.rgb * texture(textSampler, textureCoord.st).rgb; + outputColor = vec4(c.rgb * backgroundColor.a, backgroundColor.a); + }`; + + this.vsSource = `#version 300 es + precision highp float; + + uniform mat4 matView; + uniform mat4 matProj; + uniform mat4 matRot; + uniform mat4 matTrans; + uniform mat4 matScale; + + in vec3 vertexNormal; + in vec3 vertexPosition; + in vec2 textureCoordinate; + in vec3 vertexTangent; + + in vec3 vertexNormalMorph; + in vec3 vertexPositionMorph; + + out vec2 textureCoord; + out vec3 normal; + out mat3 TBN; + + void main() { + gl_Position = matProj * matView * matTrans * matScale * matRot * vec4(vertexPosition + vertexPositionMorph, 1.0) + 0.01; + normal = normalize((matRot * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz); + + vec3 T = normalize(vec3(matTrans * matRot * vec4(vertexTangent, 0.0))); + vec3 N = normalize(vec3(matTrans * matRot * vec4(vertexNormal + vertexNormalMorph, 0.0))); + T = normalize(T - dot(T, N) * N); + vec3 B = cross(N, T); + TBN = mat3(T, B, N); + + textureCoord = textureCoordinate; + }`; + + this.fsSource = `#version 300 es + precision highp float; + precision highp sampler2D; + + uniform float lightInt; + uniform float lightAmb; + uniform vec3 lightColor; + uniform float whiteM; + uniform float gammaY; + + uniform vec3 Ka; + uniform vec3 Kd; + uniform vec3 Ks; + uniform float d; + uniform float Ns; + + uniform float sNormals; + uniform float sAmbient; + uniform float sDiffuse; + uniform float sSpecular; + uniform float sAlpha; + uniform float sGamma; + uniform float sReinhard; + uniform float sNormal; + + uniform vec3 lightVect; + uniform vec3 lookDir; + + uniform sampler2D textSampler[6]; + + in vec2 textureCoord; + in vec3 normal; + in mat3 TBN; + + out vec4 outputColor; + + void main() { + vec3 new_normal = normal; + vec3 map_Ka = vec3(0.0,0.0,0.0); + vec3 map_Kd = vec3(0.0,0.0,0.0); + vec3 map_Ks = vec3(0.0,0.0,0.0); + float map_Ns = 0.0; + float map_d = 1.0; + float specular = 1.0; + + if (sNormal == 1.0) { + vec3 map_Kn = texture(textSampler[5], textureCoord.st).rgb *2.0-1.0; + if (map_Kn != vec3(-1.0,-1.0,-1.0)) + new_normal = normalize(TBN * map_Kn); + } + + float angle = max(dot(-lightVect, new_normal),0.0); + vec3 reflection = reflect(-lightVect, new_normal); + + if (sAmbient == 1.0) + map_Ka = Ka * texture(textSampler[0], textureCoord.st).rgb; + + if (sDiffuse == 1.0) + map_Kd = Kd * texture(textSampler[1], textureCoord.st).rgb; + + if (sSpecular == 1.0) { + map_Ks = Ks * texture(textSampler[2], textureCoord.st).rgb; + map_Ns = Ns * texture(textSampler[3], textureCoord.st).r; + specular = pow(max(dot(reflection, lookDir),0.0), (0.0001+map_Ns)); + } + + if (sAlpha == 1.0) + map_d = d * texture(textSampler[4], textureCoord.st).r; + + vec3 Ld = map_Kd * lightInt * angle * lightColor; + vec3 Ls = map_Ks * specular * lightColor; + vec3 La = map_Ka * lightAmb * lightColor; + + vec3 vLighting = Ld + Ls + La; + vec3 c = map_Kd * vLighting; + + if (sReinhard == 1.0) { + float l_old = 0.2126*c.r+0.7152*c.g+0.0722*c.b; + float numerator = l_old * (1.0 + (l_old / (whiteM*whiteM))); + float l_new = numerator / (1.0 + l_old); + c = c * (l_new / l_old); + } + + if (sGamma == 1.0) { + c.r = pow(c.r, (1.0/gammaY)); + c.g = pow(c.g, (1.0/gammaY)); + c.b = pow(c.b, (1.0/gammaY)); + } + + if (sNormals == 1.0) { + c = new_normal; + } + + outputColor = vec4(c*map_d, map_d); + }`; + } + + initBuffers() { + this.backgroundPositionBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.backgroundPositionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]), this.gl.STATIC_DRAW); + + this.backgroundIndexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.backgroundIndexBuffer); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), this.gl.STATIC_DRAW); + + this.verticesPositionBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesPositionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.base64ToFloat(this.sceneData.model.verts)), this.gl.STATIC_DRAW); + this.vertexCount = this.gl.getBufferParameter(this.gl.ARRAY_BUFFER, this.gl.BUFFER_SIZE)/4; + + this.verticesNormalBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.base64ToFloat(this.sceneData.model.vertsn)), this.gl.STATIC_DRAW); + + this.verticesTextureCoordBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesTextureCoordBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.base64ToFloat(this.sceneData.model.texts)), this.gl.STATIC_DRAW); + + this.verticesTangentBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesTangentBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.base64ToFloat(this.sceneData.model.tans)), this.gl.STATIC_DRAW); + + this.vertexPositionMorphs = []; + this.vertexNormalMorphs = []; + this.vertexIndexMorphs = []; + for(let i=0; i < this.sceneData.model.mverts.length; i++) { + this.vertexPositionMorphs[i] = new Float32Array(this.base64ToFloat(this.sceneData.model.mverts[i])); + this.vertexNormalMorphs[i] = new Float32Array(this.base64ToFloat(this.sceneData.model.mvertsn[i])); + let vertexIndexMorph = new Int32Array(this.base64ToInt(this.sceneData.model.mvertsi[i])); + this.vertexIndexMorphs[i] = vertexIndexMorph.map((sum => value => sum += value)(0)); + } + + this.verticesMorphBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesMorphBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); + + this.verticesNormalMorphBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalMorphBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); + + this.verticesIndexBuffer = []; + this.indexSizes = []; + for (let i=0, count=0; i < this.sceneData.model.figures.length; i++) { + for (let j=0; j < this.sceneData.model.figures[i].surfaces.length; j++, count++) { + this.verticesIndexBuffer[count] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer[count]); + let intArray = this.base64ToInt(this.sceneData.model.figures[i].surfaces[j].vertsi); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(intArray), this.gl.STATIC_DRAW); + this.indexSizes[count] = intArray.length; + } + } + } + + loadTexture(gl, url, engine) { + // return dummy texture + let texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255])); + + // stream real textures + let image = new Image(); + image.onload = function() { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + + // hack to update canvas again after streaming is done + engine.loadCount += 1; + if (engine.loadCount == engine.sceneData.textures.length) { + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event("engineLoaded")); + } + } + }; + image.src = url; + + return texture; + } + + initTextures() { + // load model textures + this.modelTextures = []; + this.loadCount = 0; + for (let i=0; i < this.sceneData.textures.length; i++) { + this.modelTextures[i] = this.loadTexture(this.gl, this.sceneData.textures[i], this); + } + } + + initShaders() { + // compile shaders + let vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); + this.gl.shaderSource(vertexShader, this.vsSource); + this.gl.compileShader(vertexShader); + + let fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.shaderSource(fragmentShader, this.fsSource); + this.gl.compileShader(fragmentShader); + + this.shaderProgram = this.gl.createProgram(); + this.gl.attachShader(this.shaderProgram, vertexShader); + this.gl.attachShader(this.shaderProgram, fragmentShader); + this.gl.linkProgram(this.shaderProgram); + + let vertexShaderBg = this.gl.createShader(this.gl.VERTEX_SHADER); + this.gl.shaderSource(vertexShaderBg, this.vsSourceBg); + this.gl.compileShader(vertexShaderBg); + + let fragmentShaderBg = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.shaderSource(fragmentShaderBg, this.fsSourceBg); + this.gl.compileShader(fragmentShaderBg); + + this.shaderProgramBg = this.gl.createProgram(); + this.gl.attachShader(this.shaderProgramBg, vertexShaderBg); + this.gl.attachShader(this.shaderProgramBg, fragmentShaderBg); + this.gl.linkProgram(this.shaderProgramBg); + + this.gl.useProgram(this.shaderProgram); + + // enable vertex attributes + this.backgroundPositionAttribute = this.gl.getAttribLocation(this.shaderProgramBg, "vertexPosition"); + this.gl.enableVertexAttribArray(this.backgroundPositionAttribute); + + this.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexPosition"); + this.gl.enableVertexAttribArray(this.vertexPositionAttribute); + + this.textureCoordAttribute = this.gl.getAttribLocation(this.shaderProgram, "textureCoordinate"); + this.gl.enableVertexAttribArray(this.textureCoordAttribute); + + this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexNormal"); + this.gl.enableVertexAttribArray(this.vertexNormalAttribute); + + this.vertexTangentAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexTangent"); + this.gl.enableVertexAttribArray(this.vertexTangentAttribute); + + this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexNormalMorph"); + this.gl.enableVertexAttribArray(this.vertexNormalMorphAttribute); + + this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexPositionMorph"); + this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute); + } + + bind(sceneData) { + this.sceneData = sceneData; + + this.offscreenCanvas = document.createElement("canvas"); + this.gl = this.offscreenCanvas.getContext("webgl2", {alpha:true, premultipliedAlpha: true}); + + this.gl.enable(this.gl.CULL_FACE); + this.gl.cullFace(this.gl.BACK); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.depthFunc(this.gl.LEQUAL); + this.gl.enable(this.gl.BLEND); + this.gl.blendEquation( this.gl.FUNC_ADD ); + this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); + + this.initBuffers(); + this.initTextures(); + this.initShaders(); + } + + render(sceneParams, canvas) { + // set render resolution + this.offscreenCanvas.width = sceneParams.settings.rwidth; + this.offscreenCanvas.height = sceneParams.settings.rheight; + this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight); + + // draw background + this.gl.clearColor(0, 0, 0, 0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + this.gl.useProgram(this.shaderProgramBg); + if (sceneParams.background.visible) { + this.drawBackground(sceneParams); + } + + // draw model + this.gl.clear(this.gl.DEPTH_BUFFER_BIT); + this.gl.useProgram(this.shaderProgram); + this.drawModel(sceneParams); + + // clone from offscreen to real canvas + let ctx = canvas.getContext('2d', {alpha:true}); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(this.gl.canvas, 0, 0, canvas.width, canvas.height); + } + + drawBackground(sceneParams) { + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramBg, "textSampler"), 0); + this.gl.uniform4fv(this.gl.getUniformLocation(this.shaderProgramBg, "backgroundColor"), sceneParams.background.color); + + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[sceneParams.background.filename]); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.backgroundPositionBuffer); + this.gl.vertexAttribPointer(this.backgroundPositionAttribute, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.backgroundIndexBuffer); + this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0); + } + + drawModel(sceneParams) { + // create camera + let camRotX = this.degreeToRad(-sceneParams.camera.xr); + let camRotY = this.degreeToRad(-sceneParams.camera.yr); + let camRotZ = this.degreeToRad(sceneParams.camera.zr); + + let up = [Math.sin(camRotZ), Math.cos(camRotZ), Math.sin(camRotZ)]; + let camera = [sceneParams.camera.x, sceneParams.camera.y, sceneParams.camera.z]; + + let matCameraRot = this.matrixMulMatrix(this.matrixMakeRotationX(camRotX), this.matrixMakeRotationY(camRotY)); + let lookDir = this.matrixMulVector(matCameraRot, [0, 0, 1]); + let target = this.vectorAdd(lookDir, camera); + let matCamera = this.matrixPointAt(camera, target, up); + + // create transforms + this.applyMorphs(sceneParams); + let matProj = this.matrixMakeProjection(sceneParams.camera.fov, sceneParams.settings.rheight/sceneParams.settings.rwidth, sceneParams.camera.fnear, sceneParams.camera.ffar); + let matView = this.matrixInverse(matCamera); + let matRot = this.matrixMakeRotation(this.degreeToRad(sceneParams.transform.xr), this.degreeToRad(sceneParams.transform.yr), this.degreeToRad(sceneParams.transform.zr)); + let matTrans = this.matrixMakeTranslation(sceneParams.transform.x, sceneParams.transform.y, sceneParams.transform.z); + let matScale = this.matrixMakeScaling( sceneParams.transform.scale); + + let lightVect = this.polarToCart(this.degreeToRad(sceneParams.light.yr), this.degreeToRad(sceneParams.light.xr)); + let lightAmb = sceneParams.light.ambient; + let lightInt = sceneParams.light.intensity; + let lightColor = sceneParams.light.color; + let whiteM = sceneParams.settings.whiteM; + let gammaY = sceneParams.settings.gammaY; + + // set uniforms + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sNormals"), sceneParams.settings.normals); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sAmbient"), sceneParams.settings.ambient); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sDiffuse"), sceneParams.settings.diffuse); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sSpecular"), sceneParams.settings.specular); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sNormal"), sceneParams.settings.normal); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sAlpha"), sceneParams.settings.alpha); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sReinhard"), sceneParams.settings.reinhard); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sGamma"), sceneParams.settings.gamma); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "whiteM"), whiteM); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "gammaY"), gammaY); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "lightAmb"), lightAmb); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "lightInt"), lightInt); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lightColor"), lightColor); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lightVect"), lightVect); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lookDir"), lookDir); + + for (let i = 0; i < sceneParams.model.morphs.length; i++) { + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, sceneParams.model.morphs[i].morphId), sceneParams.model.morphs[i].value); + } + + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matTrans"), false, new Float32Array(this.matrixFlatten(matTrans))); + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matScale"), false, new Float32Array(this.matrixFlatten(matScale))); + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matRot"), false, new Float32Array(this.matrixFlatten(matRot))); + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matProj"), false, new Float32Array(this.matrixFlatten(matProj))); + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matView"), false, new Float32Array(this.matrixFlatten(matView))); + + // bind vertex buffers + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesPositionBuffer); + this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesTextureCoordBuffer); + this.gl.vertexAttribPointer(this.textureCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalBuffer); + this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesTangentBuffer); + this.gl.vertexAttribPointer(this.vertexTangentAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesMorphBuffer); + this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalMorphBuffer); + this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + + // bind materials per surface + for (let i=0, count=0; i < this.sceneData.model.figures.length; i++) { + for (let j=0; j < this.sceneData.model.figures[i].surfaces.length; j++, count++) { + if(!sceneParams.model.figures[i].visible) { + continue; + } + + let visible = sceneParams.model.figures[i].surfaces[j].visible; + let matId = sceneParams.model.figures[i].surfaces[j].matId; + let matIdx = sceneParams.materials.map(e => e.matId).indexOf(matId); + if (matIdx === -1) { + continue; + } + let mat = sceneParams.materials[matIdx]; + + if (mat.d > 0 && visible) { + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_Ka)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[0]"), 0); + + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_Kd)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[1]"), 1); + + this.gl.activeTexture(this.gl.TEXTURE2); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_Ks)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[2]"), 2); + + this.gl.activeTexture(this.gl.TEXTURE3); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_Ns)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[3]"), 3); + + this.gl.activeTexture(this.gl.TEXTURE4); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_D)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[4]"), 4); + + this.gl.activeTexture(this.gl.TEXTURE5); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.modelTextures[parseInt(mat.map_Kn)]); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[5]"), 5); + + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "d"), mat.d); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Ka"), mat.Ka); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Kd"), mat.Kd); + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Ks"), mat.Ks); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "Ns"), mat.Ns); + + // draw materials + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer[count]); + this.gl.drawElements(this.gl.TRIANGLES, this.indexSizes[count], this.gl.UNSIGNED_INT, 0); + } + } + } + } + + applyMorphs(sceneParams) { + if(this.oldMorphValues !== JSON.stringify(sceneParams.model.morphs)) { + let vertexPositionMorph = new Float32Array(this.vertexCount); + let vertexNormalMorph = new Float32Array(this.vertexCount); + + for(let i=0; i < this.vertexPositionMorphs.length; i++) { + let morphValue = sceneParams.model.morphs[i].value; + + if (morphValue !== 0) { + let vp = this.vertexPositionMorphs[i]; + let vn = this.vertexNormalMorphs[i]; + let vi = this.vertexIndexMorphs[i]; + + if (morphValue === 1) { + for(let j = 0; j < vi.length; j++) { + vertexPositionMorph[vi[j]] += vp[j]; + vertexNormalMorph[vi[j]] += vn[j]; + } + } else { + for(let j=0; j < vi.length; j++) { + vertexPositionMorph[vi[j]] += vp[j] * morphValue; + vertexNormalMorph[vi[j]] += vn[j] * morphValue; + } + } + } + } + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesMorphBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPositionMorph, this.gl.STATIC_DRAW); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalMorphBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormalMorph, this.gl.STATIC_DRAW); + + this.oldMorphValues = JSON.stringify(sceneParams.model.morphs); + } + } + + base64ToFloat(array) { + let b = window.atob(array), + fLen = b.length / Float32Array.BYTES_PER_ELEMENT, + dView = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT)), + fAry = new Float32Array(fLen), + p = 0; + + for(let j=0; j < fLen; j++){ + p = j * 4; + dView.setUint8(0, b.charCodeAt(p)); + dView.setUint8(1, b.charCodeAt(p+1)); + dView.setUint8(2, b.charCodeAt(p+2)); + dView.setUint8(3, b.charCodeAt(p+3)); + fAry[j] = dView.getFloat32(0, true); + } + return fAry; + } + + base64ToInt(array) { + let b = window.atob(array), + fLen = b.length / Int32Array.BYTES_PER_ELEMENT, + dView = new DataView(new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT)), + fAry = new Int32Array(fLen), + p = 0; + + for(let j=0; j < fLen; j++){ + p = j * 4; + dView.setUint8(0, b.charCodeAt(p)); + dView.setUint8(1, b.charCodeAt(p+1)); + dView.setUint8(2, b.charCodeAt(p+2)); + dView.setUint8(3, b.charCodeAt(p+3)); + fAry[j] = dView.getInt32(0, true); + } + return fAry; + } + + base64ToByte(array) { + let b = window.atob(array), + fLen = b.length / Uint8Array.BYTES_PER_ELEMENT, + dView = new DataView(new ArrayBuffer(Uint8Array.BYTES_PER_ELEMENT)), + fAry = new Uint8Array(fLen); + + for(let j=0; j < fLen; j++){ + dView.setUint8(0, b.charCodeAt(j)); + fAry[j] = dView.getUint8(0); + } + return fAry; + } + + degreeToRad(d) { return d * (Math.PI / 180); } + + polarToCart(y, p) { return [Math.sin(p) * Math.cos(y), Math.sin(p) * Math.sin(y), Math.cos(p)]; } + + vectorAdd(v1, v2) { return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]]; } + + vectorSub(v1, v2) { return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]]; } + + vectorMul(v, k) { return [v[0] * k, v[1] * k, v[2] * k]; } + + vectorDotProduct(v1, v2) { return [v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]]; } + + vectorCrossProduct(v1, v2) { return [v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0]]; } + + vectorNormalize(v) { + let l = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + return [v[0]/l, v[1]/l, v[2]/l]; + } + + matrixMakeProjection(fov, aspect, near, far) { + return [[aspect * (1/Math.tan(fov*0.5/180*Math.PI)), 0, 0, 0], + [0, (1/Math.tan(fov*0.5/180*Math.PI)), 0, 0], + [0, 0, far/(far-near), 1], + [0, 0, (-far*near)/(far-near), 0]]; + } + + matrixPointAt(pos, target, up) { + let newForward = this.vectorNormalize(this.vectorSub(target, pos)); + let a = this.vectorMul(newForward, this.vectorDotProduct(up, newForward)); + let newUp = this.vectorNormalize(this.vectorSub(up, a)); + let newRight = this.vectorCrossProduct(newUp, newForward); + + return [[newRight[0], newRight[1], newRight[2], 0], + [newUp[0], newUp[1], newUp[2], 0], + [newForward[0], newForward[1], newForward[2], 0], + [pos[0], pos[1], pos[2], 1]]; + } + + matrixMakeRotation(xr, yr, zr) { + let cosA = Math.cos(xr); + let cosB = Math.cos(yr); + let cosC = Math.cos(zr); + let sinA = Math.sin(xr); + let sinB = Math.sin(yr); + let sinC = Math.sin(zr); + + return([[cosB*cosC, -cosB*sinC, sinB, 0], + [sinA*sinB*cosC+cosA*sinC, -sinA*sinB*sinC+cosA*cosC, -sinA*cosB, 0], + [-cosA*sinB*cosC+sinA*sinC, cosA*sinB*sinC+sinA*cosC, cosA*cosB, 0], + [0, 0, 0, 1]]); + } + + matrixMakeRotationX(r) { + return [[1, 0, 0], + [0, Math.cos(r), Math.sin(r)], + [0, -Math.sin(r), Math.cos(r)]]; + } + + matrixMakeRotationY(r) { + return [[Math.cos(r), 0, Math.sin(r)], + [0, 1, 0], + [-Math.sin(r), 0, Math.cos(r)]]; + } + + matrixMakeRotationZ(r) { + return [[Math.cos(r), Math.sin(r), 0], + [-Math.sin(r), Math.cos(r), 0], + [0, 0, 1]]; + } + + matrixMakeTranslation(x, y, z) { + return [[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [x, y, z, 1]]; + } + + matrixMakeScaling(s) { + return [[s, 0, 0, 0], + [0, s, 0, 0], + [0, 0, s, 0], + [0, 0, 0, 1]]; + } + + matrixMulMatrix(m1, m2) { + return [[m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0], + m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] + m1[0][2] * m2[2][1], + m1[0][0] * m2[0][2] + m1[0][1] * m2[1][2] + m1[0][2] * m2[2][2]], + [m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0], + m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] + m1[1][2] * m2[2][1], + m1[1][0] * m2[0][2] + m1[1][1] * m2[1][2] + m1[1][2] * m2[2][2]], + [m1[2][0] * m2[0][0] + m1[2][1] * m2[1][0] + m1[2][2] * m2[2][0], + m1[2][0] * m2[0][1] + m1[2][1] * m2[1][1] + m1[2][2] * m2[2][1], + m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2]]]; + } + + matrixMulVector(m, v) { + return [v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0], + v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1], + v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2]]; + } + + matrixInverse(m) { + return [[m[0][0], m[1][0], m[2][0], 0], + [m[0][1], m[1][1], m[2][1], 0], + [m[0][2], m[1][2], m[2][2], 0], + [-(m[3][0]*m[0][0]+m[3][1]*m[0][1]+m[3][2]*m[0][2]), -(m[3][0]*m[1][0]+m[3][1]*m[1][1]+m[3][2]*m[1][2]), -(m[3][0]*m[2][0]+m[3][1]*m[2][1]+m[3][2]*m[2][2]), 1]]; + } + + matrixFlatten(m) { + return [m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]]; + } +}; + diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js new file mode 100644 index 0000000000000000000000000000000000000000..6b02540deab7cfef1fd8196e7de721638085f510 --- /dev/null +++ b/src/art/webgl/ui.js @@ -0,0 +1,159 @@ +App.Art.isDraggingCanvas = false; + +App.Art.createWebglUI = function(container, slave, artSize, scene) { + let lockViewDisabled = "resources/webgl/ui/lockViewDisabled.png"; + let lockViewEnabled = "resources/webgl/ui/lockViewEnabled.png"; + let faceViewDisabled = "resources/webgl/ui/faceViewDisabled.png"; + let faceViewEnabled = "resources/webgl/ui/faceViewEnabled.png"; + let resetViewDisabled = "resources/webgl/ui/resetViewDisabled.png"; + let resetViewEnabled = "resources/webgl/ui/resetViewEnabled.png"; + + let uicontainer = document.createElement("div"); + uicontainer.setAttribute("style", "left: 82.5%; top: 5%; position: absolute; width: 15%; border: 0px; padding: 0px;"); + + // canvas + let cvs = document.createElement("canvas"); + cvs.setAttribute("style", "position: absolute;"); + + // btnLockView + let btnLockView = document.createElement("input"); + btnLockView.setAttribute("style", "display: flex; width: 100%; position: relative; border: 0px; padding: 0px; background-color: transparent;"); + btnLockView.setAttribute("type", "image"); + btnLockView.setAttribute("src", scene.lockView ? lockViewDisabled : lockViewEnabled); + + // btnFaceView + let btnFaceView = document.createElement("input"); + btnFaceView.setAttribute("style", "display: flex; width: 100%; position: relative; border: 0px; padding: 0px; background-color: transparent;"); + btnFaceView.setAttribute("type", "image"); + btnFaceView.setAttribute("src", scene.faceView ? faceViewEnabled : faceViewDisabled); + + // btnResetView + let btnResetView = document.createElement("input"); + btnResetView.setAttribute("style", "display: flex; width: 100%; position: relative; border: 0px; padding: 0px; background-color: transparent;"); + btnResetView.setAttribute("type", "image"); + btnResetView.setAttribute("src", scene.resetView ? resetViewEnabled : resetViewDisabled); + + // events + btnLockView.onclick = function(e){ + scene.lockView = !scene.lockView; + + btnLockView.src = scene.lockView ? lockViewDisabled : lockViewEnabled; + }; + + btnFaceView.onclick = function(e){ + scene.resetView = true; + scene.faceView = false; + btnFaceView.src = faceViewDisabled; + btnResetView.src = resetViewEnabled; + + scene.camera.y = slave.height-5; + scene.transform.yr = 0; + scene.camera.z = -40 - slave.height/20; + App.Art.engine.render(scene, cvs); + }; + + btnResetView.onclick = function(e){ + scene.resetView = false; + scene.faceView = true; + btnResetView.src = resetViewDisabled; + btnFaceView.src = faceViewEnabled; + + scene.camera.y = App.Art.defaultScene.camera.y; + scene.transform.yr = App.Art.defaultScene.transform.yr; + scene.camera.z = App.Art.defaultScene.camera.z; + App.Art.engine.render(scene, cvs); + }; + + cvs.onmousemove = function(e){ + if(scene.lockView){ return; } + if(!App.Art.isDraggingCanvas){ return; } + e.preventDefault(); + e.stopPropagation(); + + scene.resetView = true; + scene.faceView = true; + btnResetView.src = resetViewEnabled; + btnFaceView.src = faceViewEnabled; + + scene.camera.y = scene.camera.y + e.movementY/10; + scene.transform.yr = scene.transform.yr + e.movementX*5; + App.Art.engine.render(scene, cvs); + }; + + cvs.onmousedown = function(e){ + if(scene.lockView){ return; } + e.preventDefault(); + e.stopPropagation(); + App.Art.isDraggingCanvas=true; + }; + + cvs.onmouseup = function(e){ + if(scene.lockView){ return; } + if(!App.Art.isDraggingCanvas){ return; } + e.preventDefault(); + e.stopPropagation(); + App.Art.isDraggingCanvas=false; + }; + + cvs.onmouseout = function(e){ + if(scene.lockView){ return; } + if(!App.Art.isDraggingCanvas){ return; } + e.preventDefault(); + e.stopPropagation(); + App.Art.isDraggingCanvas=false; + }; + + cvs.onwheel = function(e){ + if(scene.lockView){ return; } + + scene.resetView = true; + scene.faceView = true; + btnResetView.src = resetViewEnabled; + btnFaceView.src = faceViewEnabled; + + scene.camera.z = scene.camera.z - e.deltaY/7; + + if (scene.camera.z < -900) { + scene.camera.z = -900; + } + if (scene.camera.z > -10) { + scene.camera.z = -10; + } + + App.Art.engine.render(scene, cvs); + return false; + }; + + container.appendChild(cvs); + uicontainer.appendChild(btnLockView); + uicontainer.appendChild(btnFaceView); + uicontainer.appendChild(btnResetView); + container.appendChild(uicontainer); + + if (artSize) { + let sz; + switch (artSize) { + case 3: + sz = [300, 530]; + break; + case 2: + sz = [300, 300]; + break; + case 1: + sz = [150, 150]; + break; + default: + sz = [120, 120]; + break; + } + + cvs.width = sz[0]; + cvs.height = sz[1]; + container.setAttribute("style", "position: relative; width: " + sz[0] + "px; height: " + sz[1] + "px;"); + } + + scene.settings.rwidth = cvs.width*2; + scene.settings.rheight = cvs.height*2; + + return cvs; +}; diff --git a/src/gui/options/options.js b/src/gui/options/options.js index 2ba22b920def06d9a4b6de422eaf07f391090009..8397da521b96ceaa578f425df8517e28dda756f6 100644 --- a/src/gui/options/options.js +++ b/src/gui/options/options.js @@ -1038,7 +1038,7 @@ App.UI.artOptions = function() { if (V.seeImages > 0) { options.addOption("Image style is", "imageChoice") - .addValueList([["Revamped embedded vector art", 3], ["Non-embedded vector art", 2], ["NoX/Deepmurk's vector art", 1], ["Shokushu's rendered imagepack", 0]]); + .addValueList([["Revamped embedded vector art", 3], ["Non-embedded vector art", 2], ["NoX/Deepmurk's vector art", 1], ["Shokushu's rendered imagepack", 0], ["Elohiem's interactive WebGL", 4]]); if (V.imageChoice === 1) { options.addComment('<span class="warning">Git compiled only, no exceptions.</span>'); @@ -1066,6 +1066,10 @@ App.UI.artOptions = function() { options.addOption("Clothing erection bulges are", "showClothingErection") .addValue("Enabled", true).on().addValue("Disabled", false).off(); + } else if (V.imageChoice === 4) { + options.addComment("You need to" + + " <a href='https://mega.nz/folder/DogSxTiD#boO9kcbIhpXKCogjXMVwxQ'>download the WebGL art assets</a>" + + " and put the 'webgl' folder into the resources/ folder where this html file is. Then refresh the page."); } options.addOption("PA avatar art is", "seeAvatar")