diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6877e05162e89f79d3501ff60fa312dbb33b88db..7d2eca82670aeb015a7ba01cc388643135a1db8e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ variables: # precompiled / assets # the URLs are the same as inside the game, change both if updating WEBGL_NAME: "WebGL art assets" - WEBGL_URL: "https://mega.nz/folder/ulIX2CAR#_g6wAcOLSCwIeGqrH7oXkA" + WEBGL_URL: "https://mega.nz/folder/P45nRALC#JkdALlE_w_cHDitz4Xhjeg" RENDER_NAME: "Rendered imagepack (outdated)" RENDER_URL: "https://mega.nz/file/upoAlBaZ#EbZ5wCixxZxBhMN_ireJTXt0SIPOywO2JW9XzTIPhe0" diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index 0e20f0d364566ec3db6130a6b7a6467d19c2b0ce..ae39a285bce96aafb93f09e39c65ab5b16bdc379 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -137,6 +137,7 @@ App.Data.defaultGameStateVariables = { seeSummaryImages: 1, seeVectorArtHighlights: 1, setSuperSampling: 2, + setZoomSpeed: 1, showAgeDetail: 1, showAppraisal: 1, showAssignToScenes: 1, diff --git a/src/art/artJS.js b/src/art/artJS.js index 8536de1254de7a353625b932b61f5c049fef066c..7c543c6db663b453bfb41cd25b1485d87570e2ab 100644 --- a/src/art/artJS.js +++ b/src/art/artJS.js @@ -168,9 +168,40 @@ App.Art.refreshSlaveArt = function(artSlave, artSize, elementID, UIDisplay = 0) } }; +/** + * @param {string} event + * @param {number} message + * @returns {void} + */ +App.Art.errorHandler = function(event, message) { + switch(message) { + case 0: + App.Art.webglErrorMessage = "Success"; + break; + case 1: + App.Art.webglErrorMessage = "Failed to start WebGL engine."; + break; + case 2: + App.Art.webglErrorMessage = "Could not find art assets."; + break; + case 3: + App.Art.webglErrorMessage = "Version mismatch.\nUpdate the assets using the link in Game Options."; + break; + case 4: // temporary option + App.Art.webglErrorMessage = "Could not find art assets.\nOlder versions need to be updated using\nthe download link in Game Options."; + break; + } + + // fire event to art elements + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event(event)); + } +}; + 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() { try { @@ -181,10 +212,7 @@ App.Art.webglInitialize = function() { 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; + scene.inspectView = false; App.Art.scenes = {}; App.Art.defaultScene = JSON.parse(JSON.stringify(scene)); @@ -193,34 +221,38 @@ App.Art.webglInitialize = function() { App.Art.engine = new App.Art.Engine(); App.Art.engine.bind(sceneData, scene); 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")); - } + // when ready, fire event to art elements to start rendering + App.Art.errorHandler("engineLoaded", 0); + LoadScreen.unlock(loadLockID); } catch(e) { - App.Art.engineReady = false; + App.Art.errorHandler("engineFailed", 1); LoadScreen.unlock(loadLockID); - - let containers = document.getElementsByClassName("artContainer"); - for (let i = 0; i < containers.length; i++) { - containers[i].dispatchEvent(new Event("engineFailed2")); - } } }; script.onerror = function() { - App.Art.engineReady = false; + App.Art.errorHandler("engineFailed", 2); LoadScreen.unlock(loadLockID); + }; + script.src = "resources/webgl/scene1/scene1.js"; - let containers = document.getElementsByClassName("artContainer"); - for (let i = 0; i < containers.length; i++) { - containers[i].dispatchEvent(new Event("engineFailed")); + // asynchronously load webgl assets if present + let load = document.createElement("script"); + load.onload = function() { + // but only if version is correct + if (App.Art.version === "1.2") { + document.head.appendChild(script); + } else { + App.Art.errorHandler("engineFailed", 3); + LoadScreen.unlock(loadLockID); } }; - script.src = "resources/webgl/scene1.js"; - document.head.appendChild(script); + load.onerror = function() { + App.Art.errorHandler("engineFailed", 4); // temporary display different message until next update + LoadScreen.unlock(loadLockID); + }; + load.src = "resources/webgl/load.js"; + document.head.appendChild(load); }(); /** @@ -231,9 +263,10 @@ App.Art.webglInitialize = function() { App.Art.webglArtElement = function(slave, artSize) { let container = document.createElement("div"); container.setAttribute("class", "artContainer"); + container.style.fontSize = "large"; container.innerText = "Loading..."; - container.addEventListener("engineLoaded", function(e) { + container.addEventListener("engineLoaded", function() { // 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)); @@ -241,31 +274,28 @@ App.Art.webglArtElement = function(slave, artSize) { let scene = App.Art.scenes[slave.ID]; // apply the model transforms + App.Art.applyFigures(slave, scene); App.Art.applySurfaces(slave, scene); App.Art.applyMaterials(slave, scene); App.Art.applyMorphs(slave, scene); + App.Art.Frame(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); + }); - container.addEventListener("engineFailed2", function(e) { + container.addEventListener("engineFailed", function() { + container.style.fontSize = "small"; container.style.color = "#BB2027"; - container.innerText = "Art asset out of date."; - }, true); + container.innerText = App.Art.webglErrorMessage; + }); // incase engine is loaded, trigger listeners manually if (App.Art.engineReady === true) { container.dispatchEvent(new Event("engineLoaded")); - } - if (App.Art.engineReady === false) { + } else { container.dispatchEvent(new Event("engineFailed")); } diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js index c696278575ee2d5f927ad1a2e66e4479cf9c723a..bc071085384ac5a3f50fb39ba773093d1ec6e08b 100644 --- a/src/art/webgl/art.js +++ b/src/art/webgl/art.js @@ -1,35 +1,88 @@ 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]; + for (const material of scene.materials) { + if(material.matId === id) { + return material; } } 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]; + for (const morph of scene.models[0].morphs) { + if(morph.morphId === id) { + return morph; } } 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]; + for (const figure of scene.models[0].figures) { + for (const surface of figure.surfaces) { + if (surface.surfaceId === id) { + return surface; } } } 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.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.applyFigures = function(slave, scene) { + let figures = []; + + figures.push("Genesis8Female"); + figures.push("Geometry"); + figures.push("Futalicious"); + figures.push("Genesis8FemaleEyelashes"); + + switch(slave.hStyle) { + case "afro": figures.push("prae-yarahair_174011"); break; + case "cornrows": figures.push("TIGER"); break; + case "bun": figures.push("Adia"); break; + case "neat": figures.push("SamiraHair_103927"); break; + case "strip": figures.push("ValRebelH_12512"); break; + case "tails": figures.push("LLF-DazStudioFemaleHair_758"); break; + case "up": figures.push("Pina"); break; + case "ponytail": figures.push("ElitePonytail"); break; + case "braided": figures.push("LLF-MishkaGeBase1_31774"); break; + case "dreadlocks": figures.push("Dreads_197696"); break; + case "permed": figures.push("IchigoHair_77918"); break; + case "curled": figures.push("aprilyshHavanaHair_32519"); break; + case "luxurious": figures.push("SW_BaronessHR_33121"); break; + case "messy bun": figures.push("KrayonHair_47547"); break; + case "messy": figures.push("MessyHair_35245"); break; + case "eary": figures.push("GeorginaHair_72382"); break; + case "undercut": figures.push("EditHairGN2Female_112247"); break; + case "bald": break; + case "shaved": break; + case "buzzcut": break; + case "trimmed": 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; + } + } } }; @@ -167,8 +220,20 @@ App.Art.applySurfaces = function(slave, scene) { surfaces.push(["Torso_Middle", "matIds", [skin + "Torso"]]); surfaces.push(["Torso_Back", "matIds", [skin + "Torso"]]); - surfaces.push(["Torso", "matIds", [skin + "Torso"]]); - surfaces.push(["Face", "matIds", [skin + "Face"]]); + switch(slave.hStyle) { + case "buzzcut": + case "trimmed": + surfaces.push(["Torso", "matIds", [skin + "Torso", "shaved_torso"]]); + surfaces.push(["Face", "matIds", [skin + "Face", "shaved_face"]]); + break; + case "bald": + case "shaved": + default: + surfaces.push(["Torso", "matIds", [skin + "Torso"]]); + surfaces.push(["Face", "matIds", [skin + "Face"]]); + break; + } + surfaces.push(["Lips", "matIds", [skin + "Lips"]]); surfaces.push(["Ears", "matIds", [skin + "Ears"]]); surfaces.push(["Legs", "matIds", [skin + "Legs"]]); @@ -220,11 +285,11 @@ App.Art.applySurfaces = function(slave, scene) { break; } - 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 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.model.figures[i].surfaces[j].surfaceId === surfaces[h][0]) { - scene.model.figures[i].surfaces[j][surfaces[h][1]] = surfaces[h][2]; + 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]; } } } @@ -344,14 +409,98 @@ App.Art.applyMaterials = function(slave, scene) { 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]); + switch(slave.hStyle) { + case "afro": + materials.push(["yara_scalp", "Kd", hairColor]); + materials.push(["yara_hair", "Kd", hairColor]); + break; + case "cornrows": + materials.push(["tiger_scalp", "Kd", hairColor]); + materials.push(["tiger_hair", "Kd", hairColor]); + break; + case "bun": + materials.push(["adia_scalp", "Kd", hairColor]); + materials.push(["adia_hair", "Kd", hairColor]); + break; + case "neat": + materials.push(["samira_scalp", "Kd", hairColor]); + materials.push(["samira_hair", "Kd", hairColor]); + break; + case "strip": + materials.push(["rebel_scalp", "Kd", hairColor]); + materials.push(["rebel_hair", "Kd", hairColor]); + break; + case "tails": + materials.push(["kinley_scalp", "Kd", hairColor]); + materials.push(["kinley_hair_thin_strands", "Kd", hairColor]); + materials.push(["kinley_hair_long", "Kd", hairColor]); + materials.push(["kinley_hair_strands", "Kd", hairColor]); + materials.push(["kinley_hair_base", "Kd", hairColor]); + materials.push(["kinley_hair_tie", "Kd", hairColor]); + break; + case "up": + materials.push(["pina_scalp", "Kd", hairColor]); + materials.push(["pina_hair1", "Kd", hairColor]); + materials.push(["pina_hair2", "Kd", hairColor]); + break; + case "ponytail": + materials.push(["ponytail_scalp", "Kd", hairColor]); + materials.push(["ponytail_hair1", "Kd", hairColor]); + materials.push(["ponytail_hair2", "Kd", hairColor]); + materials.push(["ponytail_hair3", "Kd", hairColor]); + materials.push(["ponytail_holder", "Kd", hairColor]); + break; + case "braided": + materials.push(["mishka_scalp", "Kd", hairColor]); + materials.push(["mishka_hair1", "Kd", hairColor]); + materials.push(["mishka_hair2", "Kd", hairColor]); + materials.push(["mishka_hair3", "Kd", hairColor]); + break; + case "dreadlocks": + materials.push(["dreads_scalp", "Kd", hairColor]); + materials.push(["dreads_hair", "Kd", hairColor]); + break; + case "permed": + materials.push(["ichigo_scalp", "Kd", hairColor]); + materials.push(["ichigo_hair1", "Kd", hairColor]); + materials.push(["ichigo_hair2", "Kd", hairColor]); + break; + case "curled": + materials.push(["havana_hair", "Kd", hairColor]); + break; + case "luxurious": + materials.push(["baroness_scalp", "Kd", hairColor]); + materials.push(["baroness_hair", "Kd", hairColor]); + break; + case "messy bun": + materials.push(["krayon_scalp", "Kd", hairColor]); + materials.push(["krayon_hair1", "Kd", hairColor]); + materials.push(["krayon_hair2", "Kd", hairColor]); + materials.push(["krayon_hair3", "Kd", hairColor]); + materials.push(["krayon_hair4", "Kd", hairColor]); + break; + case "messy": + materials.push(["messy_scalp", "Kd", hairColor]); + materials.push(["messy_hair", "Kd", hairColor]); + break; + case "eary": + materials.push(["georgina_scalp", "Kd", hairColor]); + materials.push(["georgina_hair1", "Kd", hairColor]); + materials.push(["georgina_hair2", "Kd", hairColor]); + break; + case "undercut": + materials.push(["edit_scalp", "Kd", hairColor]); + materials.push(["edit_hair", "Kd", hairColor]); + break; + case "buzzcut": + case "trimmed": + materials.push(["shaved_face", "Kd", hairColor]); + materials.push(["shaved_torso", "Kd", hairColor]); + break; + case "bald": + case "shaved": + default: break; + } let irisColor; let scleraColor; @@ -504,6 +653,14 @@ App.Art.applyMaterials = function(slave, scene) { break; } + let torso = App.Art.getMatIdsBySurface(scene, "Torso")[0]; + + if (slave.scar.hasOwnProperty("belly") && slave.scar.belly["c-section"] > 0) { + materials.push([torso, "map_Kn", scene.textureMap["Victoria8_Torso_CNM_1002.jpg"]]); + } else { + materials.push([torso, "map_Kn", scene.textureMap["Victoria8_Torso_NM_1002.jpg"]]); + } + 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]) { @@ -521,27 +678,32 @@ App.Art.applyMorphs = function(slave, scene) { } function random(seed) { - let x = Math.sin(seed++) * 10000; + let x = Math.sin(seed+1) * 10000; return x - Math.floor(x); } + if(hasBothArms(slave) && hasBothLegs(slave)) { - if (slave.devotion > 50) { + if (scene.inspectView) { + morphs.push(["posesInspect", 1]); + morphs.push(["posesInspectGen", 1]); + } else 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 { + } else if (slave.devotion > -20) { morphs.push(["posesMid", 1]); + } else { + morphs.push(["posesLow", 1]); } } + if (slave.trust < 0) { + morphs.push(["expressionsFear", Math.abs(slave.trust)/100]); + } else if (slave.devotion > 0) { + 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 rand = random(slave.ID); + let races = ["raceWhite", "raceAsian", "raceLatina", "raceBlack", "racePacific", "raceEuropean", "raceAmerindian", "raceSemitic", "raceEastern", "raceAryan", "raceLatina", "raceMalay"]; let index1 = Math.floor(random(slave.ID+1) * races.length); let index2 = Math.floor(random(slave.ID-1) * (races.length-1)); @@ -569,12 +731,44 @@ App.Art.applyMorphs = function(slave, scene) { case "malay": morphs.push(["raceMalay", 1]); break; case "mixed race": - morphs.push([races[index1], rand]); + morphs.push([races[index1], 0.5]); races.splice(index1, index1); - morphs.push([races[index2], 1-rand]); + morphs.push([races[index2], 0.5]); break; } + 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]); + } + + let eyeShape = ["eyeShapeNormal", "eyeShapeWide", "eyeShapeRound", "eyeShapeSmall", "eyeShapeSlit", "eyeShapeCute", "eyeShapeOpen"]; + let eye = Math.floor(random(slave.ID+3) * eyeShape.length); + if (eye > 0) { + morphs.push(eyeShape[eye], 1); + } + + let noseShape = ["noseShapeNormal", "noseShapeWide", "noseShapeForward", "noseShapeFlat", "noseShapeTriangular", "noseShapeSmall"]; + let nose = Math.floor(random(slave.ID+4) * noseShape.length); + if (nose > 0) { + morphs.push(noseShape[nose], 1); + } + + let foreheadShape = ["foreheadShapeNormal", "foreheadShapeRound", "foreheadShapeSmall"]; + let forehead = Math.floor(random(slave.ID+5) * foreheadShape.length); + if (forehead > 0) { + morphs.push(foreheadShape[forehead], 1); + } + switch (slave.faceShape) { case "normal": break; @@ -591,7 +785,7 @@ App.Art.applyMorphs = function(slave, scene) { } if (slave.boobs < 600) { - morphs.push(["boobsSmall", -(slave.boobs-600)/600]); + morphs.push(["boobShapeSmall", -(slave.boobs-600)/600]); } else { switch (slave.boobShape) { case "normal": @@ -599,15 +793,25 @@ App.Art.applyMorphs = function(slave, scene) { case "perky": morphs.push(["boobShapePerky", (Math.sqrt(slave.boobs-600)/125)]); break; case "saggy": - morphs.push(["boobShapeSaggy", (Math.sqrt(slave.boobs-600)/50)]); break; + morphs.push(["boobShapeSaggy", (Math.sqrt(slave.boobs-600)/55)]); break; case "torpedo-shaped": morphs.push(["boobShapeTorpedo", (Math.sqrt(slave.boobs-600)/35)]); break; case "downward-facing": - morphs.push(["boobShapeDownward", (Math.sqrt(slave.boobs-600)/160)]); break; + // special case to make nipple work + if (slave.nipples === "flat") { + morphs.push(["boobShapeDownward_nipplesFlat", (Math.sqrt(slave.boobs-600)/80)]); break; + } else { + morphs.push(["boobShapeDownward", (Math.sqrt(slave.boobs-600)/80)]); break; + } case "wide-set": morphs.push(["boobShapeWide", (Math.sqrt(slave.boobs-600)/40)]); break; case "spherical": - morphs.push(["boobShapeSpherical", (Math.sqrt(slave.boobs-600)/60)]); break; + // special case to make nipple work + if (slave.nipples === "flat") { + morphs.push(["boobShapeSpherical_nipplesFlat", (Math.sqrt(slave.boobs-600)/80)]); break; + } else { + morphs.push(["boobShapeSpherical", (Math.sqrt(slave.boobs-600)/80)]); break; + } } } @@ -615,13 +819,13 @@ App.Art.applyMorphs = function(slave, scene) { case "flat": break; case "huge": - morphs.push(["nipplesHuge", Math.sqrt(slave.boobs)/20 + 0.5]); break; + morphs.push(["nipplesHuge", Math.sqrt(slave.boobs)/40 + 0.5]); break; case "tiny": - morphs.push(["nipplesHuge", Math.sqrt(slave.boobs)/60 + 0.10]); break; + morphs.push(["nipplesHuge", Math.sqrt(slave.boobs)/90 + 0.1]); break; case "cute": morphs.push(["nipplesCute", Math.sqrt(slave.boobs)/20 + 0.5]); break; case "puffy": - morphs.push(["nipplesPuffy", Math.sqrt(slave.boobs)/20 + 0.5]); break; + morphs.push(["nipplesPuffy", Math.sqrt(slave.boobs)/35 + 0.5]); break; case "inverted": morphs.push(["nipplesInverted", Math.sqrt(slave.boobs)/20 + 0.5]); break; case "partially inverted": @@ -656,13 +860,12 @@ App.Art.applyMorphs = function(slave, scene) { morphs.push(["areolae", convertRange(0, 4, 0, 5, slave.areolae)]); morphs.push(["shoulders", slave.shoulders/1.2]); - morphs.push(["lips", convertRange(0, 100, -1, 3, slave.lips)]); - scene.transform.scale = slave.height/175; // height by object transform + scene.models[0].transform.scale = slave.height/175; // height by object transform if (slave.muscles > 0) { - morphs.push(["muscles", slave.muscles/50]); + morphs.push(["muscles", slave.muscles/33]); } - morphs.push(["belly", Math.sqrt(slave.belly)/175]); + morphs.push(["belly", Math.sqrt(slave.belly)/150]); morphs.push(["hips", slave.hips/2]); if (slave.butt<=1) { @@ -678,15 +881,15 @@ App.Art.applyMorphs = function(slave, scene) { } if (slave.weight >= 0) { - morphs.push(["weight", slave.weight/50]); + morphs.push(["weight", slave.weight/75]); } else { morphs.push(["weightThin", -slave.weight/80]); } if (slave.visualAge < 20) { - morphs.push(["physicalAgeYoung", -(slave.visualAge-20)/15]); + morphs.push(["physicalAgeYoung", -(slave.visualAge-20)/20]); } else { - morphs.push(["physicalAgeOld", (slave.visualAge-20)/100]); + morphs.push(["physicalAgeOld", (slave.visualAge-20)/66]); } if (!hasLeftArm(slave)) { @@ -702,12 +905,12 @@ App.Art.applyMorphs = function(slave, scene) { morphs.push(["amputeeRightLeg", 1]); } - App.Art.resetMorphs(slave, scene); + App.Art.resetMorphs(scene); - for (let i =0; i < scene.model.morphs.length; i++) { + for (let i =0; i < scene.models[0].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]; + if (scene.models[0].morphs[i].morphId === morphs[j][0]) { + scene.models[0].morphs[i].value = morphs[j][1]; } } } diff --git a/src/art/webgl/contents.txt b/src/art/webgl/contents.txt index 9bbdc2f4bdfa898e6d6de99b4de692343656a102..bdd046e9ea25adfb6b10b34cf56e1545f4a6ea98 100644 --- a/src/art/webgl/contents.txt +++ b/src/art/webgl/contents.txt @@ -55,11 +55,45 @@ faceShape 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 + areolaeShape heart star circle +expressions + happy + fear + race white asian @@ -149,4 +183,24 @@ General dick Hairstyle - bobHair + Neat + Afro + Braided + Cornrows + Curled + Dreadlocks + Eary + In a bun + In a messy bun + In a ponytail + In tails + Luxurious + Messy + Permed + Shaved sides + Up + Undercut + Shaved + Buzzcut + Trimmed short + diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js index 697b77ced0c6fc344d3d50fa5309be85b0df12c0..d0056f30c15898aa4485cb27c0c79300559a1825 100644 --- a/src/art/webgl/engine.js +++ b/src/art/webgl/engine.js @@ -190,61 +190,154 @@ App.Art.Engine = class { }`; } - initBuffers() { - this.backgroundPositionBuffer = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.backgroundPositionBuffer); + initBuffers(sceneData) { + // init buffer containers + this.buffers = new class {}; + this.buffers.models = []; + for (let m=0; m < sceneData.models.length; m++) { + this.buffers.models[m] = new class {}; + } + + // init background buffers + this.buffers.backgroundPositionBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.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.buffers.backgroundIndexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.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, 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, 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, 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, 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] = this.base64ToFloat(this.sceneData.model.mverts[i]); - this.vertexNormalMorphs[i] = this.base64ToFloat(this.sceneData.model.mvertsn[i]); - let vertexIndexMorph = this.base64ToInt(this.sceneData.model.mvertsi[i]); - this.vertexIndexMorphs[i] = vertexIndexMorph.map((sum => value => sum += value)(0)); + // init model buffers + for (let m=0; m < this.buffers.models.length; m++) { + let modelBuffers = this.buffers.models[m]; + let modelData = sceneData.models[m]; + + modelBuffers.verticesPositionBuffer = []; + modelBuffers.verticesNormalBuffer = []; + modelBuffers.verticesTextureCoordBuffer = []; + modelBuffers.verticesTangentBuffer = []; + modelBuffers.vertexCount = []; + modelBuffers.verticesMorphBuffer = []; + modelBuffers.verticesNormalMorphBuffer = []; + + modelBuffers.verticesIndexBuffer = []; + modelBuffers.indexSizes = []; + for (let i=0, count=0; i < modelData.figures.length; i++) { + modelBuffers.verticesPositionBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].verts), this.gl.STATIC_DRAW); + modelBuffers.vertexCount[i] = this.gl.getBufferParameter(this.gl.ARRAY_BUFFER, this.gl.BUFFER_SIZE)/4; + + modelBuffers.verticesNormalBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].vertsn), this.gl.STATIC_DRAW); + + modelBuffers.verticesTextureCoordBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].texts), this.gl.STATIC_DRAW); + + modelBuffers.verticesTangentBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTangentBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].tans), this.gl.STATIC_DRAW); + + // return dummy morph + modelBuffers.verticesMorphBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); + + modelBuffers.verticesNormalMorphBuffer[i] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); + + for (let j=0; j < modelData.figures[i].surfaces.length; j++, count++) { + modelBuffers.verticesIndexBuffer[count] = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]); + let intArray = this.base64ToInt(modelData.figures[i].surfaces[j].vertsi); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, intArray, this.gl.STATIC_DRAW); + modelBuffers.indexSizes[count] = intArray.length; + } + } + + this.initMorphs(modelBuffers, modelData); } + } - 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, intArray, this.gl.STATIC_DRAW); - this.indexSizes[count] = intArray.length; + initMorphs(modelBuffers, modelData) { + window.sceneBlocks = {}; // automatically populated during loading of morphs + + let promisedMorphs = []; + modelBuffers.vertexPositionMorphs = []; + modelBuffers.vertexNormalMorphs = []; + modelBuffers.vertexIndexMorphs = []; + for (let m=0; m < modelData.morphs.length; m++) { + modelBuffers.vertexPositionMorphs[m] = []; + modelBuffers.vertexNormalMorphs[m] = []; + modelBuffers.vertexIndexMorphs[m] = []; + + for (let f=0; f < modelData.figures.length; f++) { + modelBuffers.vertexPositionMorphs[m].push(new Float32Array(0)); + modelBuffers.vertexNormalMorphs[m].push(new Float32Array(0)); + modelBuffers.vertexIndexMorphs[m].push(new Int32Array(0)); } + + // stream real morphs + promisedMorphs.push(this.loadMorph(modelBuffers, m, modelData.morphs[m])); } + + Promise.all(promisedMorphs).then((values) => { + if (values.length > 0) { // promise triggers twice (?) + window.sceneBlocks = null; // let garbage collector clean + if (App.Art.engineReady) { // re-send loaded event after morphs finish streaming + modelBuffers.oldMorphValues = null; + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event("engineLoaded")); + } + } + } + }); + } + + loadMorph(modelBuffers, m, path) { + let engine = this; + return new Promise(function(resolve, reject) { + let script = document.createElement("script"); + script.onload = function() { + let morph = window.sceneBlocks[path]; + + for (let i=0; i < morph.length; i+=3) { + modelBuffers.vertexPositionMorphs[m][i/3] = engine.base64ToFloat(morph[i+0]); + modelBuffers.vertexNormalMorphs[m][i/3] = engine.base64ToFloat(morph[i+1]); + // reconstruct compressed indices + modelBuffers.vertexIndexMorphs[m][i/3] = engine.base64ToInt(morph[i+2]).map((sum => value => sum += value)(0)); + } + resolve(); + }; + script.onerror = function(e) { + reject(e, script); + }; + script.src = path; + document.head.appendChild(script); + }); + } + + initTextures(sceneData) { + // load model textures + this.textures = []; + let promisedTextures = []; + for (let i=0; i < sceneData.textures.length; i++) { + const {texture, promise} = this.loadTexture(this.gl, sceneData.textures[i]); + this.textures[i] = texture; + promisedTextures[i] = promise; + } + Promise.all(promisedTextures).then(() => { + if (App.Art.engineReady) { // re-send loaded event after textures finish streaming + let containers = document.getElementsByClassName("artContainer"); + for (let i = 0; i < containers.length; i++) { + containers[i].dispatchEvent(new Event("engineLoaded")); + } + } + }); } loadTexture(gl, url) { @@ -271,25 +364,6 @@ App.Art.Engine = class { return {texture, promise}; } - initTextures() { - // load model textures - this.modelTextures = []; - let promisedTextures = []; - for (let i=0; i < this.sceneData.textures.length; i++) { - const {texture, promise} = this.loadTexture(this.gl, this.sceneData.textures[i]); - this.modelTextures[i] = texture; - promisedTextures[i] = promise; - } - Promise.all(promisedTextures).then(() => { - if (App.Art.engineReady) { // re-send loaded event after textures finish streaming - let containers = document.getElementsByClassName("artContainer"); - for (let i = 0; i < containers.length; i++) { - containers[i].dispatchEvent(new Event("engineLoaded")); - } - } - }); - } - initShaders(sceneParams) { // compile shaders let vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); @@ -344,8 +418,6 @@ App.Art.Engine = class { } bind(sceneData, sceneParams) { - this.sceneData = sceneData; - this.offscreenCanvas = document.createElement("canvas"); this.gl = this.offscreenCanvas.getContext("webgl2", {alpha:true, premultipliedAlpha: true}); @@ -357,8 +429,8 @@ App.Art.Engine = class { this.gl.blendEquation( this.gl.FUNC_ADD ); this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); - this.initBuffers(); - this.initTextures(); + this.initBuffers(sceneData); + this.initTextures(sceneData); this.initShaders(sceneParams); } @@ -376,10 +448,10 @@ App.Art.Engine = class { this.drawBackground(sceneParams); } - // draw model + // draw scene this.gl.clear(this.gl.DEPTH_BUFFER_BIT); this.gl.useProgram(this.shaderProgram); - this.drawModel(sceneParams); + this.drawScene(sceneParams); // clone from offscreen to real canvas let ctx = canvas.getContext('2d', {alpha:true}); @@ -392,16 +464,16 @@ App.Art.Engine = class { 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.bindTexture(this.gl.TEXTURE_2D, this.textures[sceneParams.background.filename]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.backgroundPositionBuffer); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.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.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.backgroundIndexBuffer); this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0); } - drawModel(sceneParams) { + drawScene(sceneParams) { // create camera let camRotX = this.degreeToRad(-sceneParams.camera.xr); let camRotY = this.degreeToRad(-sceneParams.camera.yr); @@ -415,15 +487,11 @@ App.Art.Engine = class { let target = this.vectorAdd(lookDir, camera); let matCamera = this.matrixPointAt(camera, target, up); - // create transforms - this.applyMorphs(sceneParams); + // create scene transforms 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); - // set uniforms + // set scene 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); @@ -452,135 +520,157 @@ App.Art.Engine = class { 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); + // process each model in the scene + for (let m=0; m < this.buffers.models.length; m++) { + let modelBuffers = this.buffers.models[m]; + let modelParams = sceneParams.models[m]; - 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); + if(!modelParams.visible) { + continue; + } - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesTangentBuffer); - this.gl.vertexAttribPointer(this.vertexTangentAttribute, 3, this.gl.FLOAT, false, 0, 0); + // create model transforms + this.applyMorphs(modelParams, modelBuffers); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesMorphBuffer); - this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + let matRot = this.matrixMakeRotation(this.degreeToRad(modelParams.transform.xr), this.degreeToRad(modelParams.transform.yr), this.degreeToRad(modelParams.transform.zr)); + let matTrans = this.matrixMakeTranslation(modelParams.transform.x, modelParams.transform.y, modelParams.transform.z); + let matScale = this.matrixMakeScaling( modelParams.transform.scale); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesNormalMorphBuffer); - this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + // set model uniforms + 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))); - // 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) { + for (let i=0, count=0; i < modelParams.figures.length; i++) { + if(!modelParams.figures[i].visible) { + count += modelParams.figures[i].surfaces.length; continue; } - let visible = sceneParams.model.figures[i].surfaces[j].visible; + // bind vertex buffers per figure + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]); + this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); - for (let h=0; h < sceneParams.model.figures[i].surfaces[j].matIds.length; h++) { - let matId = sceneParams.model.figures[i].surfaces[j].matIds[h]; - 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); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]); + this.gl.vertexAttribPointer(this.textureCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]); + this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTangentBuffer[i]); + this.gl.vertexAttribPointer(this.vertexTangentAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); + this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]); + this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); + + // bind materials per surface and set uniforms + for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) { + let visible = modelParams.figures[i].surfaces[j].visible; + + for (let h=0; h < modelParams.figures[i].surfaces[j].matIds.length; h++) { + let matId = modelParams.figures[i].surfaces[j].matIds[h]; + 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.textures[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.textures[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.textures[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.textures[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.textures[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.textures[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, modelBuffers.verticesIndexBuffer[count]); + this.gl.drawElements(this.gl.TRIANGLES, modelBuffers.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]; + applyMorphs(modelParams, modelBuffers) { + if(modelBuffers.oldMorphValues !== JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures)) { + for (let f=0; f < modelParams.figures.length; f++) { + if(!modelParams.visible || !modelParams.figures[f].visible) { + continue; + } - 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; + let vertexPositionMorph = new Float32Array(modelBuffers.vertexCount[f]); + let vertexNormalMorph = new Float32Array(modelBuffers.vertexCount[f]); + + for(let m=0; m < modelParams.morphs.length; m++) { + let morphValue = modelParams.morphs[m].value; + + if (morphValue !== 0) { + let vp = modelBuffers.vertexPositionMorphs[m][f]; + let vn = modelBuffers.vertexNormalMorphs[m][f]; + let vi = modelBuffers.vertexIndexMorphs[m][f]; + + 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, modelBuffers.verticesMorphBuffer[f]); + 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.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[f]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormalMorph, this.gl.STATIC_DRAW); + } - this.oldMorphValues = JSON.stringify(sceneParams.model.morphs); + modelBuffers.oldMorphValues = JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures); } } base64ToFloat(array) { - let b = window.atob(array), - fLen = b.length / (Float32Array.BYTES_PER_ELEMENT-1), - dView = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT)), - fAry = new Float32Array(fLen), - p = 0; + let b = window.atob(array); + let fLen = b.length / (Float32Array.BYTES_PER_ELEMENT-1); + let dView = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT)); + let fAry = new Float32Array(fLen); + let p = 0; for(let j=0; j < fLen; j++){ p = j * 3; @@ -594,11 +684,11 @@ App.Art.Engine = class { } 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; + let b = window.atob(array); + let fLen = b.length / Int32Array.BYTES_PER_ELEMENT; + let dView = new DataView(new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); + let fAry = new Int32Array(fLen); + let p = 0; for(let j=0; j < fLen; j++){ p = j * 4; @@ -612,10 +702,10 @@ App.Art.Engine = class { } 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); + let b = window.atob(array); + let fLen = b.length / Uint8Array.BYTES_PER_ELEMENT; + let dView = new DataView(new ArrayBuffer(Uint8Array.BYTES_PER_ELEMENT)); + let fAry = new Uint8Array(fLen); for(let j=0; j < fLen; j++){ dView.setUint8(0, b.charCodeAt(j)); diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js index 15335fd01e571ed3411b2b455d3eecd4f17ad0be..0234056472c6a0429e78437b87f5321d8c4c4b25 100644 --- a/src/art/webgl/ui.js +++ b/src/art/webgl/ui.js @@ -7,6 +7,8 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { let faceViewEnabled = "resources/webgl/ui/faceViewEnabled.png"; let resetViewDisabled = "resources/webgl/ui/resetViewDisabled.png"; let resetViewEnabled = "resources/webgl/ui/resetViewEnabled.png"; + let inspectViewDisabled = "resources/webgl/ui/inspectViewDisabled.png"; + let inspectViewEnabled = "resources/webgl/ui/inspectViewEnabled.png"; let uicontainer = document.createElement("div"); uicontainer.setAttribute("style", "left: 82.5%; top: 5%; position: absolute; width: 15%; border: 0px; padding: 0px;"); @@ -33,34 +35,59 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { btnResetView.setAttribute("type", "image"); btnResetView.setAttribute("src", scene.resetView ? resetViewEnabled : resetViewDisabled); + // btnInspectView + let btnInspectView = document.createElement("input"); + btnInspectView.setAttribute("style", "display: flex; width: 100%; position: relative; border: 0px; padding: 0px; background-color: transparent;"); + btnInspectView.setAttribute("type", "image"); + btnInspectView.setAttribute("src", scene.inspectView ? inspectViewDisabled : inspectViewEnabled); + // events + btnInspectView.onclick = function(e){ + scene.inspectView = true; + scene.resetView = true; + scene.faceView = false; + btnFaceView.src = faceViewEnabled; + btnResetView.src = resetViewEnabled; + btnInspectView.src = inspectViewDisabled; + + scene.models[0].transform.yr = 180; + App.Art.applyMorphs(slave, scene); + App.Art.Frame(slave, scene); + App.Art.engine.render(scene, cvs); + }; + btnLockView.onclick = function(e){ scene.lockView = !scene.lockView; - btnLockView.src = scene.lockView ? lockViewDisabled : lockViewEnabled; }; btnFaceView.onclick = function(e){ scene.resetView = true; + scene.inspectView = false; scene.faceView = false; btnFaceView.src = faceViewDisabled; btnResetView.src = resetViewEnabled; + btnInspectView.src = inspectViewEnabled; scene.camera.y = slave.height-5; - scene.transform.yr = 0; - scene.camera.z = -40 - slave.height/20; + scene.models[0].transform.yr = 0; + scene.camera.xr = -6; + scene.camera.z = -slave.height/3.85; + App.Art.applyMorphs(slave, scene); App.Art.engine.render(scene, cvs); }; btnResetView.onclick = function(e){ scene.resetView = false; scene.faceView = true; + scene.inspectView = false; btnResetView.src = resetViewDisabled; btnFaceView.src = faceViewEnabled; + btnInspectView.src = inspectViewEnabled; - scene.camera.y = App.Art.defaultScene.camera.y; - scene.transform.yr = App.Art.defaultScene.transform.yr; - scene.camera.z = App.Art.defaultScene.camera.z; + scene.models[0].transform.yr = App.Art.defaultScene.models[0].transform.yr; + App.Art.applyMorphs(slave, scene); + App.Art.Frame(slave, scene); App.Art.engine.render(scene, cvs); }; @@ -75,8 +102,8 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { btnResetView.src = resetViewEnabled; btnFaceView.src = faceViewEnabled; - scene.camera.y = scene.camera.y + e.movementY/10; - scene.transform.yr = scene.transform.yr + e.movementX*5; + scene.camera.y = scene.camera.y + e.movementY/7; + scene.models[0].transform.yr = scene.models[0].transform.yr + e.movementX*5; App.Art.engine.render(scene, cvs); }; @@ -111,14 +138,13 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { 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; - } + // zoom speed based on distance from origin, and along direction of camera + let zOld = scene.camera.z; + let magnitude = e.deltaY/(10/V.setZoomSpeed) * (-scene.camera.z/50 + 0.2); + let zDistance = Math.cos(-scene.camera.xr * (Math.PI/180)) * magnitude; + scene.camera.z -= zDistance; + scene.camera.z = Math.clamp(scene.camera.z, -900, -10); + scene.camera.y += Math.sin(-scene.camera.xr * (Math.PI/180)) * magnitude * -(scene.camera.z - zOld)/zDistance; App.Art.engine.render(scene, cvs); return false; @@ -127,9 +153,11 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { container.appendChild(cvs); uicontainer.appendChild(btnLockView); uicontainer.appendChild(btnFaceView); + uicontainer.appendChild(btnInspectView); uicontainer.appendChild(btnResetView); container.appendChild(uicontainer); + // calculate canvas resolution if (artSize) { let sz; switch (artSize) { @@ -161,3 +189,46 @@ App.Art.createWebglUI = function(container, slave, artSize, scene) { return cvs; }; + +App.Art.Frame = function(slave, scene) { + if (slave.height > 185) { + App.Art.AutoFrame(scene, slave.height, 127); + } else { + App.Art.FixedFrame(scene); + } +}; + +App.Art.AutoFrame = function(scene, slaveHeight, cameraHeight) { + // auto-frame based on camera height and FoV + let n = Math.max(slaveHeight * 1.05 - cameraHeight, 1); + let m = cameraHeight * 1.065; + let fov = scene.camera.fov; + + let a = fov * (Math.PI/180); + let r = m/n; + let h = 0; + + // solve for distance + if (a !== Math.PI/2) { + if (a > Math.PI/2) { + h = n/((-(r + 1) - ((r+1)**2 + 4*r*Math.tan(a)**2)**(1/2))/(2*Math.tan(a)*r)); // take negative discriminant + } else { + h = n/((-(r + 1) + ((r+1)**2 + 4*r*Math.tan(a)**2)**(1/2))/(2*Math.tan(a)*r)); // take positive discriminant + } + } else { + h = (m+n)/2 * Math.sin(Math.acos(((m+n)/2-n)/((m+n)/2))); // edge case + } + + // solve for rotation + let rot = fov/2 - Math.atan(n/h) * (180/Math.PI); + + scene.camera.z = -h; + scene.camera.y = cameraHeight; + scene.camera.xr = -rot; +}; + +App.Art.FixedFrame = function(scene) { + scene.camera.z = -275; + scene.camera.y = 127; + scene.camera.xr = -6; +}; diff --git a/src/gui/options/options.js b/src/gui/options/options.js index 2f5502192990cd58e5caea0ea4f0e91ea129cc43..29d254fe68cde67490a4808b86fcd257a097148c 100644 --- a/src/gui/options/options.js +++ b/src/gui/options/options.js @@ -1111,13 +1111,15 @@ 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/ulIX2CAR#_g6wAcOLSCwIeGqrH7oXkA' target='_blank'>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.addComment(`<a href='https://mega.nz/folder/P45nRALC#JkdALlE_w_cHDitz4Xhjeg' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is. + Then <b>refresh</b> the page. + Create the resources folder if it does not exist. <span class="warning">(Android/MacOS not supported)</span>`); options.addOption("Supersampling (SSAA)", "setSuperSampling") .addValue("0.25", 0.25).off().addValue("0.5", 0.5).off().addValue("1", 1).off().addValue("2", 2).on().addValue("4", 4).off() - .addComment("This effectively multiplies the resolution of the render before downsampling again. Use a smaller factor for low-end GPUs."); + .addComment("This effectively multiplies the resolution of the render before downsampling again. Use a smaller factor for low-end GPU's."); + options.addOption("Zoom speed", "setZoomSpeed") + .addValue("0.25", 0.25).off().addValue("0.5", 0.5).off().addValue("1", 1).on().addValue("2", 2).off().addValue("4", 4).off(); } options.addOption("PA avatar art is", "seeAvatar")