-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable/Disable Player Aging #18
base: master
Are you sure you want to change the base?
Changes from all commits
340730f
2288557
22ee228
a83c3a3
2dd6e5f
d891d7d
a23084f
a6e335e
298895e
851aedd
c397351
7105496
6f5a62b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -204,6 +204,80 @@ <h1 class="mb-4 mt-2"> | |
</div> | ||
</div> | ||
</div> | ||
<br /> | ||
<div class="p-3 bg-warning bg-gradient"> | ||
<div class="row"> | ||
<div class="col-10"><h4>Aging</h4></div> | ||
<div class="col-2 text-right"> | ||
<input | ||
class="form-check-input random-group randomize-smile-lines" | ||
type="checkbox" | ||
value="" | ||
checked | ||
id="randomize-aging" | ||
/> | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="col-10"> | ||
<div class="form-group"> | ||
<select class="form-control" id="aging-enabled"> | ||
</select> | ||
</div> | ||
</div> | ||
<div class="col-2 text-right"> | ||
| ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="col-5"> | ||
<label for="aging-age">Age</label> | ||
</div> | ||
<div class="col-5 form-group"> | ||
<input | ||
type="number" | ||
class="form-control" | ||
id="aging-age" | ||
min="18" | ||
max="40" | ||
step="1" | ||
/> | ||
</div> | ||
<div class="col-2 text-right"> | ||
<input | ||
class="form-check-input random-attribute randomize-aging" | ||
type="checkbox" | ||
value="" | ||
checked | ||
id="randomize-aging-age" | ||
/> | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="col-5"> | ||
<label for="aging-maturity">Maturity</label> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like having a "maturity" variable but I think it needs to be renamed, at least in the UI, to help people understand what it does. Maybe rename to "Aging speed" or something? |
||
</div> | ||
<div class="col-5 form-group"> | ||
<input | ||
type="number" | ||
class="form-control" | ||
id="aging-maturity" | ||
min="-3" | ||
max="3" | ||
step="1" | ||
/> | ||
</div> | ||
<div class="col-2 text-right"> | ||
<input | ||
class="form-check-input random-attribute randomize-aging" | ||
type="checkbox" | ||
value="" | ||
checked | ||
id="randomize-aging-maturity" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="col"> | ||
<div class="p-3 bg-info bg-gradient"> | ||
|
@@ -353,7 +427,7 @@ <h1 class="mb-4 mt-2"> | |
</div> | ||
<div class="col-2 text-right"> | ||
<input | ||
class="form-check-input random-attribute randomize-hair" | ||
class="form-check-input random-attribute randomize-long-hair" | ||
type="checkbox" | ||
value="" | ||
checked | ||
|
@@ -890,27 +964,27 @@ <h1 class="mb-4 mt-2"> | |
|
||
let face; | ||
if (location.hash.length <= 1) { | ||
face = faces.generate(); | ||
face = faces.generate({ aging: { enabled: true } }); | ||
} else { | ||
try { | ||
face = JSON.parse(atob(location.hash.slice(1))); | ||
} catch (error) { | ||
console.error(error); | ||
face = faces.generate(); | ||
face = faces.generate({ aging: { enabled: true } }); | ||
} | ||
} | ||
|
||
const randomizeFace = (oldFace, newFace) => { | ||
Array.from(document.getElementsByClassName("random-attribute")).forEach( | ||
(elem) => { | ||
if (!elem.checked) { | ||
const attr = elem.id.split("-").slice(1); | ||
if (attr.length === 1) newFace[attr[0]] = oldFace[attr[0]]; | ||
else if (!isNaN(parseInt(attr[1]))) { | ||
const idx = parseInt(attr[1]); | ||
newFace[attr[0]][idx] = oldFace[attr[0]][idx]; | ||
} else if (attr.length === 2) | ||
newFace[attr[0]][attr[1]] = oldFace[attr[0]][attr[1]]; | ||
const parts = elem.id.split("-").slice(1); | ||
if (parts.length === 1) newFace[parts[0]] = oldFace[parts[0]]; | ||
else if (!isNaN(parseInt(parts[1]))) { | ||
const idx = parseInt(parts[1]); | ||
newFace[parts[0]][idx] = oldFace[parts[0]][idx]; | ||
} else if (parts.length === 2) | ||
newFace[parts[0]][parts[1]] = oldFace[parts[0]][parts[1]]; | ||
} | ||
} | ||
); | ||
|
@@ -929,16 +1003,27 @@ <h1 class="mb-4 mt-2"> | |
}; | ||
|
||
const initializeSelectOptions = () => { | ||
let selectElement, optionElement; | ||
for (const feature of Object.keys(faces.svgsIndex)) { | ||
const options = faces.svgsIndex[feature]; | ||
const selectElement = document.getElementById(`${feature}-id`); | ||
selectElement = document.getElementById(`${feature}-id`); | ||
for (const option of options) { | ||
const optionElement = document.createElement("option"); | ||
optionElement = document.createElement("option"); | ||
optionElement.value = option; | ||
optionElement.text = option; | ||
selectElement.add(optionElement, null); | ||
} | ||
} | ||
selectElement = document.getElementById("aging-enabled"); | ||
optionElement = document.createElement("option"); | ||
optionElement.selected = true; | ||
optionElement.value = "true"; | ||
optionElement.text = "Enabled"; | ||
selectElement.add(optionElement, null); | ||
optionElement = document.createElement("option"); | ||
optionElement.value = "false"; | ||
optionElement.text = "Disabled"; | ||
selectElement.add(optionElement, null); | ||
}; | ||
|
||
const isValue = (obj) => | ||
|
@@ -975,6 +1060,12 @@ <h1 class="mb-4 mt-2"> | |
if (typeof oldValue === "number") { | ||
return parseFloat(event.target.value); | ||
} | ||
if ( | ||
typeof oldValue === "boolean" && | ||
typeof event.target.value !== "boolean" | ||
) { | ||
return event.target.value === "true"; | ||
} | ||
if (typeof oldValue === "boolean") { | ||
return event.target.checked; | ||
} | ||
|
@@ -1008,8 +1099,10 @@ <h1 class="mb-4 mt-2"> | |
} | ||
|
||
document.getElementById("randomize").addEventListener("click", () => { | ||
const oldFace = face; | ||
face = randomizeFace(oldFace, faces.generate()); | ||
face = randomizeFace( | ||
face, | ||
faces.generate({ aging: { enabled: face.aging.enabled } }) | ||
); | ||
initializeFormValues(); | ||
updateDisplay(); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,11 +97,93 @@ type FeatureInfo = { | |
scaleFatness?: true; | ||
}; | ||
|
||
const hashCode = (str) => { | ||
let hash = 0; | ||
for (let i = 0; i < str.length; i++) { | ||
const character = str.charCodeAt(i); | ||
hash = (hash << 5) - hash + character; | ||
hash = hash & hash; // Convert to 32bit integer | ||
} | ||
return Math.abs(hash); | ||
}; | ||
|
||
const deterministicRandom = (face: Face) => { | ||
const hash = | ||
hashCode(face.body.id) + | ||
hashCode(face.body.color) + | ||
hashCode(face.head.id) + | ||
hashCode("" + face.fatness) + | ||
hashCode(face.hair.id) + | ||
hashCode(face.hair.color); | ||
return (hash % 1000) / 1000; | ||
}; | ||
|
||
const ageHair = (hairId: String) => { | ||
switch (hairId) { | ||
case "afro": | ||
return "short"; | ||
case "afro2": | ||
return "short"; | ||
case "blowoutFade": | ||
return "cropFade2"; | ||
case "cornrows": | ||
return "short-fade"; | ||
case "curly3": | ||
return "short3"; | ||
case "dreads": | ||
return "short-fade"; | ||
case "emo": | ||
return "short2"; | ||
case "faux-hawk": | ||
return "short3"; | ||
case "fauxhawk-fade": | ||
return "short-fade"; | ||
case "high": | ||
return "short"; | ||
case "juice": | ||
return "short2"; | ||
case "longHair": | ||
return "short-fade"; | ||
case "shaggy2": | ||
return "shaggy1"; | ||
case "short-bald": | ||
return "short-bald"; | ||
case "shortBangs": | ||
return "short-bald"; | ||
case "spike": | ||
return "short"; | ||
case "spike2": | ||
return "short"; | ||
case "spike3": | ||
return "short"; | ||
case "spike4": | ||
return "short"; | ||
case "tall-fade": | ||
return "crop-fade"; | ||
default: | ||
return "short-fade"; | ||
} | ||
}; | ||
|
||
const drawFeature = (svg: SVGSVGElement, face: Face, info: FeatureInfo) => { | ||
const feature = face[info.name]; | ||
const feature = Object.assign({}, face[info.name]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this change? I think it doesn't do anything, but I may be missing something. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change creates a copy of My thinking and overall strategy for implementing the aging feature was that the parts and their values would act as master DNA for the person if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with that is you'd then have to generate every new face with a bunch of wrinkles and other old people features. Otherwise you'd have some that never age. Ultimately you can get the same result either way, I just think it'd be more intuitive to have the base face object be the young version, and then add on stuff to represent aging. Because that's what actually happens as real humans age :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'm more considering generating new faces rather than aging existing faces. I figured new face objects would have to be generated anyway to include the However, I think by making the base face object the young version, it won't allow users to specify what aging lines will eventually occur when the face ages. Or if the deterministic aging isn't varied enough, you might see too many common combinations of heads/lines. I'd rather have the option to choose what my face will look like at 35 and then have the younger versions of the face be determined, especially since its basically just removal of lines. I do realize that's now how it works in real life but I believe this way allows for full customization, i.e. what if I want a different aging line for a face then what the deterministic random would apply. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another way of phrasing that is... you pick all the facial features you want for your guy. Some will appear/disappear based on age. If you say he has a bunch of wrinkles, he won't when he's young. If you say he has long hair, he might not when he's old. I think that makes enough sense for people to understand. |
||
if (!feature || !svgs[info.name]) { | ||
return; | ||
} | ||
if (face.aging && face.aging.enabled) { | ||
if ( | ||
info.name === "hair" && | ||
face.aging.age + face.aging.maturity / 2 >= 30 && | ||
deterministicRandom(face) < 0.5 | ||
) | ||
feature.id = ageHair(feature.id); | ||
else if ( | ||
info.name === "hairBg" && | ||
face.aging.age + face.aging.maturity / 2 >= 27 && | ||
deterministicRandom(face) < 0.75 | ||
) | ||
feature.id = "none"; | ||
} | ||
|
||
// @ts-ignore | ||
let featureSVGString = svgs[info.name][feature.id]; | ||
|
@@ -111,14 +193,15 @@ const drawFeature = (svg: SVGSVGElement, face: Face, info: FeatureInfo) => { | |
|
||
// @ts-ignore | ||
if (feature.shave) { | ||
let shave; | ||
if (face.aging && face.aging.enabled) | ||
if (face.aging.age + face.aging.maturity > 23) shave = feature.shave; | ||
else shave = "rgba(0,0,0,0)"; | ||
else shave = feature.shave; | ||
// @ts-ignore | ||
featureSVGString = featureSVGString.replace("$[faceShave]", feature.shave); | ||
} | ||
|
||
// @ts-ignore | ||
if (feature.shave) { | ||
featureSVGString = featureSVGString.replace("$[faceShave]", shave); | ||
// @ts-ignore | ||
featureSVGString = featureSVGString.replace("$[headShave]", feature.shave); | ||
featureSVGString = featureSVGString.replace("$[headShave]", shave); | ||
} | ||
|
||
featureSVGString = featureSVGString.replace("$[skinColor]", face.body.color); | ||
|
@@ -311,6 +394,33 @@ const display = ( | |
]; | ||
|
||
for (const info of featureInfos) { | ||
if (face.aging && face.aging.enabled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to what I wrote about hair... maybe switch this around, so some facial lines are just never (or more rarely) included in the base face object, and then apply them here (maybe again with deterministic randomness). I think that would make it easier to have aging appear in all faces. Since currently, when I'm playing around with this, a lot of faces are not impacted by aging. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Responding to the last part of your comment. My understanding is that you implemented this version in BBGM and you didn't see many aged faces? If so, first, were you able to inject the age value in If so, my thoughts are the way aging is currently set up (to use the parts as master DNA) a lot of your currently generated face models would not show the effects of aging. This is because I assume the proportion of faces that have aging lines are low combined with the fact that of these faces, only some are actually old enough to show effects of aging. Using this version of faces.js, most if not all generated faces should be randomly assigned aging lines (unless they're blessed with great genes) and those lines would appear appropriately as they age. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I very arbitrarily chose different ages for different lines to appear. They could easily be too high. There could also be a variable called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, just playing around in the editor UI. But it's the same thing, BBGM generates random faces the same way the editor does.
Probably a good idea. On second thought, what I wrote before about using deterministic random numbers to determine aging might be kind of annoying, because then users wouldn't be able to control it... lol complicated stuff the more you think about it! |
||
if ( | ||
info.name === "miscLine" && | ||
face.aging.age + face.aging.maturity >= 22 && | ||
face.miscLine.id.startsWith("freckles") | ||
) | ||
continue; | ||
if ( | ||
info.name === "miscLine" && | ||
face.aging.age + face.aging.maturity < 25 && | ||
face.miscLine.id.startsWith("chin") | ||
) | ||
continue; | ||
if ( | ||
info.name === "smileLine" && | ||
face.aging.age + face.aging.maturity < 27 | ||
) | ||
continue; | ||
if (info.name === "eyeLine" && face.aging.age + face.aging.maturity < 30) | ||
continue; | ||
if ( | ||
info.name === "miscLine" && | ||
face.aging.age + face.aging.maturity < 34 && | ||
face.miscLine.id.startsWith("forehead") | ||
) | ||
continue; | ||
} | ||
drawFeature(svg, face, info); | ||
} | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to
type="range"
and you get a nifty slider. Should display the age as text next to it too.