FaceGen 3 SDKs Reference
Loading...
Searching...
No Matches
FgApplyDaz.cpp
1//
2// Copyright (c) 2023 Singular Inversions Inc. (facegen.com)
3//
4// Authors: Andrew Beatty
5// Created: Oct. 4, 2011
6//
7
8#include "stdafx.h"
9
10#include "FgSerial.hpp"
11#include "FgApply.hpp"
12#include "FgTestUtils.hpp"
13#include "FgParse.hpp"
14#include "Fg3Sam.hpp"
15#include "Fg3Face.hpp"
16#include "FgGuiApi.hpp"
17#include "Fg3dMeshIo.hpp"
18#include "FgTime.hpp"
19#include "FgSystem.hpp"
20#include "FgAnthropometry.hpp"
21#include "FgRender.hpp"
22
23#ifdef _MSC_VER
24#pragma warning(disable:4996) // ignore 'gmtime' security deprecation warning
25#endif
26
27using namespace std;
28
29namespace Fg {
30
31namespace {
32
33String8 formatDsfPath(String8 const & str)
34{
35 String8 slash = str.replace('\\','/');
36 return replaceCharWithString(slash,char32_t(' '),String8("%20"));
37}
38
39string dateTimeISO8601()
40{
41 time_t now;
42 time(&now);
43 char buff[sizeof "2019-10-22T08:06:00Z"];
44 strftime(buff,sizeof buff,"%FT%TZ",gmtime(&now));
45 return string(buff);
46}
47
48void exportScmMap(
49 String8 const & scmPathBase,
50 String8 const & outPathBase,
51 Sam3Coord const & coord,
52 Bytes const & detailEnc,
53 ImgFormat imgFormat)
54{
55 PushIndent op(pathToName(scmPathBase).m_str);
56 Timer time;
57 Scm3 scm(scmPathBase);
58 time.report("SCM load");
59 ImgRgba8 detail = scm.transformDetail(detailEnc);
60 time.report("Detail transform");
61 ImgRgba8 colorMap = scm.applyFaceCoord(coord.ts(1,0),detail);
62 time.report("SCM application");
63 String ext = getImgFormatInfo(imgFormat).extensions.at(0);
64 saveImage(colorMap,outPathBase+"."+ext);
65 time.report("Save image");
66}
67
68void createCustomBodyMap(
69 String8 const & basePathName,
70 Arr3D colorFac,
71 String8 const & outPathBase,
72 ImgFormat imgFormat)
73{
74 PushIndent op(pathToName(basePathName).m_str);
75 //Timer time;
76 ImgRgba8 baseImg = loadImage(basePathName);
77 ImgRgba8 shiftImg = applyColorFac(baseImg,colorFac);
78 String ext = getImgFormatInfo(imgFormat).extensions.at(0);
79 saveImage(shiftImg,outPathBase+"."+ext);
80 //time.report("Body load, shift, save");
81}
82
83// returns the per-channel least-squares multiplicative factor to adjust the given daz map to the fg map color:
84Arr3D createFaceMap(
85 String8 const & scmPathBase,
86 String8 const & dazPath,
87 String8 const & outPathBase,
88 Sam3Coord const & coord,
89 Bytes const & detailBlob,
90 TattooImage const & tattooImage,
91 Arr3D renderFac={1,1,1}) // multiplicative factor to adjust fg map to daz render input
92{
93 //PushIndent op {"createFaceMap"};
94 //Timer time;
95 Scm3 scm {scmPathBase};
96 ImgUC faceFade = toUC(loadImage(scmPathBase+"_fade.jpg"));
97 //time.report("SCM load");
98 ImgRgba8 scmMap = scm.calcScmColorMap(coord);
99 //time.report("SCM apply");
100 ImgRgba8 dazImg = loadImage(dazPath);
101 //time.report("Base image load");
102 ImgRgba8 detail = scm.transformDetail(detailBlob);
103 //time.report("Detail transform");
104 ImgUC skinSampleRegion = getSkinSampleRegion(scm.detailXfm);
105 Arr3D colorFac = calcColorFac(dazImg,scmMap,skinSampleRegion),
106 facegenFac = mapMul(renderFac,colorFac);
107 // make sure to do this AFTER color sampling but BEFORE detail application:
108 if (tattooImage) {
109 tattooImage(scm.detailXfm,scmMap);
110 //time.report("add logo");
111 }
112 ImgRgba8 detailMap = applyColorFac(imgModulate(scmMap,detail),renderFac);
113 //time.report("Detail modulation");
114 ImgRgba8 dazMapAdj = applyColorFac(dazImg,facegenFac),
115 blendMap = imgBlend(detailMap,dazMapAdj,faceFade);
116 //time.report("Shift and blend");
117 saveImage(blendMap,outPathBase+".jpg");
118 //time.report("Save image");
119 return facegenFac;
120}
121
122ostream & operator<<(ostream & os,IdxVec3F const & v)
123{
124 os << " [ " << v.idx << ", "
125 << v.vec[0] << ", "
126 << v.vec[1] << ", "
127 << v.vec[2] << " ]";
128 return os;
129}
130
131void dazSaveDsfMorphV6(
132 String8 const & libPath,
133 String8 const & relPath, // must end with delimiter but not start with one
134 String8 const & controlName,
135 String8 const & parent,
136 string const & modifierNodeId,
137 size_t totNumVerts,
138 IdxVec3Fs const & morph,
139 String8 group,
140 Arr<Vec3F,2> eyeCentreDeltas) // 0 - left, 1 - right
141{
142 if (group.m_str[0] != '/')
143 group.m_str.insert(group.m_str.begin(),'/'); // Must start with '/'
144 Ofstream ofs(libPath+relPath+controlName+".dsf");
145 String8 controlID = "FG"+controlName,
146 parentFmt = formatDsfPath(parent);
147 // Spaces in PATHS are changed to '%20' to match files Daz produces, but spaces in just the names
148 // (eg. modifier_library id/name) cannot contain '%' but can contain spaces, so leave those unchanged.
149 // 'auto_follow' is for hairstyles (& clothing?) to follow new head shape.
150 ofs.precision(7);
151 ofs.setf(ios::fixed);
152 ofs << R"({
153 "file_version" : "0.6.0.0",
154 "asset_info" : {
155 "id" : "/)" << formatDsfPath(relPath+controlName) << R"(.dsf",
156 "type" : "modifier",
157 "contributor" : {
158 "author" : "FaceGen",
159 "email" : "",
160 "website" : "https://facegen.com"
161 },
162 "revision" : "1.0",
163 "modified" : ")" << dateTimeISO8601() << R"("
164 },
165 "modifier_library" : [
166 {
167 "id" : ")" << controlID << R"(",
168 "name" : ")" << controlID << R"(",
169 "parent" : ")" << parentFmt << modifierNodeId << R"(",
170 "presentation" : {
171 "type" : "Modifier/Shape",
172 "label" : "",
173 "description" : "",
174 "icon_large" : "",
175 "colors" : [ [ 0.1568627, 0.1568627, 0.1568627 ], [ 1, 1, 1 ] ]
176 },
177 "channel" : {
178 "id" : "value",
179 "type" : "float",
180 "name" : "Value",
181 "label" : ")" << controlName << R"(",
182 "value" : 0,
183 "min" : 0,
184 "max" : 1,
185 "clamped" : true,
186 "display_as_percent" : true,
187 "step_size" : 0.01,
188 "auto_follow" : true
189 },
190 "region" : "Head",
191 "group" : ")" << group << R"(",
192 "formulas" : [)";
193 Strings coords {"x","y","z"};
194 for (size_t ss=0; ss<2; ++ss) {
195 String eye = (ss==0) ? "lEye" : "rEye";
196 for (uint dd=0; dd<3; ++dd) {
197 ofs << R"(
198 {
199 "output" : ")" << eye << ":" << parentFmt << eye << "?center_point/" << coords[dd] << R"(",
200 "operations" : [
201 { "op" : "push", "url" : ")" << modifierNodeId << ":#" << controlID << R"(?value" },
202 { "op" : "push", "val" : )" << eyeCentreDeltas[ss][dd] << R"( },
203 { "op" : "mult" }
204 ]
205 })";
206 if ((ss==0) || (dd<2))
207 ofs << ",";
208 }
209 }
210 ofs << R"(
211 ],
212 "morph" : {
213 "vertex_count" : )" << totNumVerts << R"(,
214 "deltas" : {
215 "count" : )" << morph.size() << R"(,
216 "values" : [
217)"
218 << morph[0];
219 for (size_t ii=1; ii<morph.size(); ++ii)
220 ofs << ",\n" << morph[ii];
221 ofs << R"(
222 ]
223 }
224 }
225 }
226 ],
227 "scene" : {
228 "modifiers" : [
229 {
230 "id" : ")" << controlID << R"(-1",
231 "url" : "#)" << controlID << R"("
232 }
233 ]
234 }
235})";
236}
237
238}
239
240Svec<DazGenesis> cDazGenesisInfo()
241{
242 return {
243 {DazGenesisVer::G1,"1"},
244 {DazGenesisVer::G2,"2"},
245 {DazGenesisVer::G3,"3"},
246 {DazGenesisVer::G8,"8"},
247 {DazGenesisVer::G81,"81"},
248 {DazGenesisVer::G9,"9"},
249 };
250}
251
252ostream & operator<<(ostream & os,DazGenesisVer ver)
253{
254 return os << findFirst(cDazGenesisInfo(),ver).str;
255}
256
257Strings dazGenesisBodyMapNames(DazGenesisVer ver)
258{
259 map<DazGenesisVer,Strings> verMaps {
260 {DazGenesisVer::G1,{"limbs","torso"}},
261 {DazGenesisVer::G2,{"limbs","torso"}},
262 {DazGenesisVer::G3,{"arms","legs","torso"}},
263 {DazGenesisVer::G8,{"arms","legs","torso"}},
264 {DazGenesisVer::G81,{"arms","body","head","legs"}},
265 };
266 return verMaps.at(ver);
267}
268
269String dazRelMorphDir(DazGenesisVer ver,bool female)
270{
271 map<DazGenesisVer,string> morphVerDir {
272 {DazGenesisVer::G1,"Genesis"},
273 {DazGenesisVer::G2,"Genesis 2"},
274 {DazGenesisVer::G3,"Genesis 3"},
275 {DazGenesisVer::G8,"Genesis 8"},
276 {DazGenesisVer::G81,"Genesis 8"},
277 };
278 bool isV1 = (ver == DazGenesisVer::G1);
279 string genderStr = isV1 ? "" : (female ? "Female" : "Male"),
280 morphGenderVer = (ver == DazGenesisVer::G81 ? " 8_1" : ""),
281 morphGenderDir = isV1 ? "Base" : genderStr;
282 return "data/DAZ 3D/" + morphVerDir[ver] + "/" + morphGenderDir + morphGenderVer + "/";
283}
284
285// Returns empty if registryPath doesn't exist or has no content directories:
286static String8s getContentDirs(String8 const registryPath)
287{
288 String8s ret;
289 // This gives the number of non-cloud content dirs:
290 Opt<ulong> numDirs = getWinRegistryUlong(registryPath,"NumContentDirs");
291 if (!numDirs.has_value())
292 return ret;
293 // These are all non-cloud content dirs. The "Daz Connect" cloud content dir is in 'CloudDir' but we don't
294 // use that as Daz Studio won't load custom morphs from it:
295 String8 nameRoot = "ContentDir";
296 for (ulong ii=0; ii<numDirs.value(); ++ii) {
297 String8 name = nameRoot + toStr(ii);
298 Opt<String8> dirOpt = getWinRegistryString(registryPath,name);
299 if (dirOpt.has_value()) {
300 String8 dirSlash = toNativeDirSep(dirOpt.value()+"/"); // They lack a trailing separator
301 if (!contains(ret,dirSlash))
302 ret.push_back(dirSlash);
303 }
304 }
305 return ret;
306}
307
308String8s getDazContentDirs()
309{
310 String8s releaseDirs = getContentDirs("Software\\Daz\\Studio4");
311 // Do not remove this; many people use the public beta version instead of the release version as they like access
312 // to the latest working features, and the content directory presumably has to be different to support new formats:
313 String8s betaDirs = getContentDirs("Software\\Daz\\Studio4PublicBuild");
314 String8s ret;
315 for (String8 const & dir : cat(releaseDirs,betaDirs)) {
316 if (!contains(ret,dir)) { // don't think a beta dir can be the same as a release dir but you never know
317 if (pathExists(dir))
318 ret.push_back(dir);
319 }
320 }
321 return ret;
322}
323
324String8 getDazContentDirContaining(String const & relPath)
325{
326 String8s cds = getDazContentDirs();
327 for (String8 const & cd : cds) {
328 if (pathExists(cd+relPath))
329 return cd;
330 }
331 return {};
332}
333
334String8 getDazConnectDirContaining(String const & relDir)
335{
336 auto findFn = [&](String const & registryPath)
337 {
338 Opt<String8> cdo = getWinRegistryString(registryPath,"CloudDir");
339 if (cdo.has_value()) {
340 String8 const & cd = cdo.value();
341 DirContents dcs = getDirContents(cd+"/data/cloud/");
342 String8s vers = sortAll(select(dcs.dirnames,[](String8 const & d){return d.beginsWith("1_"); }));
343 for (auto it = vers.rbegin(); it != vers.rend(); ++it) {
344 String8 ret = cd+"/data/cloud/"+*it+"/";
345 String8 path = ret + relDir;
346 if (pathExists(path))
347 return ret;
348 }
349 }
350 return String8{};
351 };
352 String8 ret = findFn("Software\\Daz\\Studio4");
353 if (ret.empty())
354 ret = findFn("Software\\Daz\\Studio4PublicBuild");
355 return ret;
356}
357
358void dazExport(
359 DazGenesisVer ver,
360 bool female,
361 String8 const & contentDir,
362 Sam3Coord const & coord,
363 Bytes const & detail,
364 bool sphericalEyes,
365 String8 const & group,
366 String8 const & controlName,
367 String8s const & baseMapNames,
368 WorkerCallback const & fnUpdate,
369 EmbossMesh const & embossMesh,
370 TattooImage const & tattooImage)
371{
372 // SSM:
373 map<DazGenesisVer,string> fullVerStr {
374 {DazGenesisVer::G1,""},
375 {DazGenesisVer::G2,"2"},
376 {DazGenesisVer::G3,"3"},
377 {DazGenesisVer::G8,"8"},
378 {DazGenesisVer::G81,"8_1"},
379 };
380 map<DazGenesisVer,string> nodeIdVerStr {
381 {DazGenesisVer::G1,""},
382 {DazGenesisVer::G2,""},
383 {DazGenesisVer::G3,"3"},
384 {DazGenesisVer::G8,"8"},
385 {DazGenesisVer::G81,"8_1"},
386 };
387 bool isV1 = (ver == DazGenesisVer::G1);
388 string verStr = toStr(ver),
389 genderStr = isV1 ? "" : (female ? "Female" : "Male"),
390 genderChar = female ? "F" : "M",
391 morphRelDir = dazRelMorphDir(ver,female),
392 genFileBase = "Genesis" + fullVerStr[ver] + genderStr;
393 string parent = "/" + morphRelDir + genFileBase + ".dsf#",
394 modifierNodeId = "Genesis" + nodeIdVerStr[ver] + (isV1 ? "" : genderStr);
395 String8 dd = dataDir() + "artist/daz/Genesis" + verStr + genderChar + "/";
396 Mesh dazShape = loadTri(dd+"allVerts_eyeSurfs.tri"),
397 fgShape = loadTri(dd+"fit_allVerts_eyeSurfs.tri");
398 FGASSERT(dazShape.verts.size() == fgShape.verts.size());
399 String8 ssmName = sphericalEyes ? "fit_allVerts.egm" : "fit_allVerts_eyeSurfs.egm";
400 Ssm3 ssm(fgShape.verts,dd+ssmName);
401 Vec3Fs idShape = ssm.applyCoord(coord);
402 Vec3Fs dazEyeCentres = loadTri(dd+"eyeCentres.tri").verts;
403 Ssm3 fgEyeCentres(dd+"fit_eyeCentres");
404 Vec3Fs eyeDeltas = fgEyeCentres.applyCoord(coord) - dazEyeCentres;
405 Arr<Vec3F,2> eyeCentreDeltas {eyeDeltas[0],eyeDeltas[1]};
406 if (embossMesh) {
407 // Must use a mesh with only face UVs for the emboss, or other head parts (eg. lacrimals)
408 // with overlapping UV coordinates (for different maps) will get embossed:
409 Mesh fgShapeFace = loadTri(dd+"tex_face.tri");
410 embossMesh(loadFim(dd+"tex_face.fim"),fgShapeFace);
411 idShape += (fgShapeFace.verts - fgShape.verts);
412 }
413 IdxVec3Fs morph;
414 for (size_t ii=0; ii<idShape.size(); ++ii) {
415 Vec3F delta = idShape[ii] - dazShape.verts[ii];
416 if (delta.magD() > 0.0f)
417 morph.emplace_back(scast<uint>(ii),delta);
418 }
419 string morphDir = morphRelDir + "Morphs/FaceGen/";
420 createPath(contentDir+morphDir);
421 dazSaveDsfMorphV6(contentDir,morphDir,controlName,parent,modifierNodeId,idShape.size(),morph,group,eyeCentreDeltas);
422 if (baseMapNames.empty())
423 return;
424 // SCM takes all the time:
425 if (fnUpdate && fnUpdate(true))
426 return;
427 Strings bodyMapNames = dazGenesisBodyMapNames(ver);
428 FGASSERT(baseMapNames.size() == bodyMapNames.size()+1);
429 String8 outPath = contentDir+"Runtime/Textures/FaceGen/Genesis"+verStr+genderChar+"/"+controlName+"/";
430 createPath(outPath);
431 outPath += controlName + "_";
432 Arr3D colorFac = createFaceMap(dd+"tex_face",baseMapNames[0],outPath+"face",coord,detail,tattooImage);
433 if (fnUpdate && fnUpdate(true))
434 return;
435 exportScmMap(dd+"tex_eyes",outPath+"eyes",coord,detail,ImgFormat::jpg); // Don't support custom base for eyes
436 for (size_t ii=0; ii<bodyMapNames.size(); ++ii) {
437 if (fnUpdate && fnUpdate(true))
438 return;
439 createCustomBodyMap(baseMapNames[ii+1],colorFac,outPath+bodyMapNames[ii],ImgFormat::jpg);
440 }
441}
442
443void dazSaveDsfMorphV6G9(
444 String8 const & libPath,
445 String8 const & relPath,
446 String8 const & controlName,
447 String8 const & parent,
448 size_t totNumVerts,
449 IdxVec3Fs const & morph,
450 String8 group, // must begin with slash but not end with one
451 Arr<Vec3F,2> eyeCentreDeltas, // 0 - left, 1 - right
452 Arr<float,2> eyeScaleDeltas) // "
453{
454 String8 fname = libPath+relPath+controlName+".dsf";
455 Ofstream ofs {fname};
456 String8 controlID = "FaceGen_"+controlName;
457 // Spaces in PATHS are changed to '%20' to match files Daz produces, but spaces in just the names
458 // (eg. modifier_library id/name) cannot contain '%' but can contain spaces, so leave those unchanged.
459 // 'auto_follow' is for hairstyles (& clothing?) to follow new head shape.
460 ofs.precision(7);
461 ofs.setf(ios::fixed);
462 ofs << R"({
463 "file_version" : "0.6.0.0",
464 "asset_info" : {
465 "id" : "/)" << formatDsfPath(relPath+controlName) << R"(.dsf",
466 "type" : "modifier",
467 "contributor" : {
468 "author" : "FaceGen",
469 "email" : "",
470 "website" : "https://facegen.com"
471 },
472 "revision" : "1.0",
473 "modified" : ")" << dateTimeISO8601() << R"("
474 },
475 "modifier_library" : [
476 {
477 "id" : ")" << controlID << R"(",
478 "name" : ")" << controlID << R"(",
479 "parent" : ")" << parent << R"(",
480 "presentation" : {
481 "type" : "Modifier/Shape",
482 "label" : "",
483 "description" : "",
484 "icon_large" : "",
485 "colors" : [ [ 0.1568627, 0.1568627, 0.1568627 ], [ 1, 1, 1 ] ]
486 },
487 "channel" : {
488 "id" : "value",
489 "type" : "float",
490 "name" : "Value",
491 "label" : ")" << controlName << R"(",
492 "value" : 0,
493 "min" : 0,
494 "max" : 1,
495 "clamped" : true,
496 "display_as_percent" : true,
497 "step_size" : 0.01,
498 "auto_follow" : true
499 },
500 "region" : "Head",
501 "group" : ")" << group << R"(",
502 "formulas" : [)";
503 if (cMagD(eyeCentreDeltas) > 0) {
504 Strings coords {"x","y","z"};
505 for (size_t ss=0; ss<2; ++ss) {
506 String eye = (ss==0) ? "l_eye" : "r_eye";
507 for (uint dd=0; dd<3; ++dd) {
508 ofs << R"(
509 {
510 "output" : ")" << eye << ":/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#" << eye << "?center_point/" << coords[dd] << R"(",
511 "operations" : [
512 { "op" : "push", "url" : "Genesis9:#)" << controlID << R"(?value" },
513 { "op" : "push", "val" : )" << eyeCentreDeltas[ss][dd] << R"( },
514 { "op" : "mult" }
515 ]
516 },)";
517 //if ((ss<1) || (dd<2))
518 // ofs << ",";
519 }
520 }
521 }
522 if (cMagD(eyeScaleDeltas) > 0) {
523 for (size_t ss=0; ss<2; ++ss) {
524 String eye = (ss==0) ? "l_eye" : "r_eye";
525 ofs << R"(
526 {
527 "output" : ")" << eye << ":/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#" << eye << R"(?scale/general",
528 "operations" : [
529 { "op" : "push", "url" : "Genesis9:#)" << controlID << R"(?value" },
530 { "op" : "push", "val" : )" << eyeScaleDeltas[ss] << R"( },
531 { "op" : "mult" }
532 ]
533 })";
534 if (ss==0)
535 ofs << ",";
536 }
537 }
538 ofs << R"(
539 ],
540 "morph" : {
541 "vertex_count" : )" << totNumVerts << R"(,
542 "deltas" : {
543 "count" : )" << morph.size() << R"(,
544 "values" : [
545)"
546 << morph[0];
547 for (size_t ii=1; ii<morph.size(); ++ii)
548 ofs << ",\n" << morph[ii];
549 ofs << R"(
550 ]
551 }
552 }
553 }
554 ],
555 "scene" : {
556 "modifiers" : [
557 {
558 "id" : ")" << controlID << R"(-1",
559 "url" : "#)" << controlID << R"("
560 }
561 ]
562 }
563})";
564 fgout << fgnl << fname;
565}
566
567void dazExportShapeG9(
568 Sam3Coord const & coord,
569 String8 const & contentDir,
570 String8 const & group,
571 String8 const & name,
572 IdxVec3Fs const & headDeltas)
573{
574 String8 dd = dataDir() + "artist/daz/Genesis9/";
575 Arr<Vec3F,2> eyeCentreDeltas;
576 Arr<float,2> eyeScaleDeltas;
577 {
578 Mesh lmsMesh = loadMesh(dd+"lms.fgmesh");
579 FaceLmPos3Fs lmsBase = toFaceLmPoss(lmsMesh.markedVertsAsNameVecs());
580 Ssm3 ssm {dd+"nrr-lms"};
581 Vec3Fs lmsTarg = ssm.applyCoord(coord);
582 Arr<ScaleTrans3F,2> xfs = cEyesTransform(lmsBase,lmsTarg);
583 Mesh eyeL = loadMesh(dd+"eyeL.fgmesh"),
584 eyeR = loadMesh(dd+"eyeR.fgmesh");
585 // eye similarity was tested as a morph but we now use Daz transform fields:
586 //Vec3Fs eyes = cat(mapMul(xfs[0],eyeL.verts),mapMul(xfs[1],eyeR.verts)); // verts are consecutive in order L,R
587 //IdxVec3Fs morph = toIndexedDeltaMorph(eyes-cat(eyeL.verts,eyeR.verts),0);
588 //String8 relDir = "data/DAZ 3D/Genesis 9/Genesis 9 Eyes/Morphs/FaceGen/",
589 // parent = "/data/Daz%203D/Genesis%209/Genesis%209%20Eyes/Genesis9Eyes.dsf#Genesis9Eyes-1";
590 //createPath(contentDir+relDir);
591 //dazSaveDsfMorphV6G9(contentDir,relDir,name,parent,eyes.size(),morph,group,{});
592 for (size_t ss=0; ss<2; ++ss) {
593 Mesh const & eye = (ss==0) ? eyeL : eyeR;
594 Vec3F eyeCentreB = eye.joints[0].pos,
595 eyeCentreT = xfs[ss] * eyeCentreB;
596 eyeCentreDeltas[ss] = eyeCentreT - eyeCentreB;
597 eyeScaleDeltas[ss] = xfs[ss].scale - 1;
598 }
599 }
600 {
601 Vec3Fs base = loadMesh(dd+"base.fgmesh").verts;
602 size_t V = base.size();
603 Ssm3 ssm {dd+"nrr-base"};
604 Vec3Fs targ = cHead(ssm.applyCoord(coord),V); // don't need allVerts, just vert list
605 accDeltaMorph_(headDeltas,targ);
606 IdxVec3Fs morph = toIndexedDeltaMorph(targ-base,0);
607 String8 relDir = "data/daz 3D/genesis 9/base/morphs/FaceGen/",
608 parent = "/data/Daz%203D/Genesis%209/Base/Genesis9.dsf#Genesis9-1";
609 createPath(contentDir+relDir);
610 dazSaveDsfMorphV6G9(contentDir,relDir,name,parent,V,morph,group,eyeCentreDeltas,eyeScaleDeltas);
611 }
612 {
613 Vec3Fs base = loadMesh(dd+"mouth.fgmesh").verts;
614 size_t V = base.size();
615 Ssm3 ssm {dd+"nrr-mouth"};
616 Vec3Fs targ = cHead(ssm.applyCoord(coord),V); // just vert list
617 IdxVec3Fs morph = toIndexedDeltaMorph(targ-base,0);
618 String8 relDir = "data/DAZ 3D/Genesis 9/Genesis 9 Mouth/Morphs/FaceGen/",
619 parent = "/data/Daz%203D/Genesis%209/Genesis%209%20Mouth/Genesis9Mouth.dsf#Genesis9Mouth-1";
620 createPath(contentDir+relDir);
621 dazSaveDsfMorphV6G9(contentDir,relDir,name,parent,V,morph,group,{},{});
622 }
623}
624
625Strings dazGetMapPathsG9(String8 const & materialDuf,Strings const & parts)
626{
627 Strings partSrcPaths (parts.size()); // relative to content dir
628 { // load the DUF for the given G9 base material and extract the diffuse map paths for each part:
629 Any duf = parseJson(loadRawString(materialDuf));
630 JsonObject const & obj = duf.ref<JsonObject>();
631 Anys const & lib = findFirst(obj,"image_library").val.as<Anys>();
632 for (size_t ii=0; ii<lib.size(); ++ii) {
633 Any const & node = lib[ii];
634 JsonObject const & map = node.as<JsonObject>();
635 String const & id = findFirst(map,"id").val.as<String>();
636 for (size_t pp=0; pp<parts.size(); ++pp) {
637 String ss = "_" + parts[pp] + "_D_"; // diffuse map for this part
638 if (containsSubstr(id,ss)) {
639 Anys const & mm = findFirst(map,"map").val.as<Anys>();
640 JsonObject const & mo = mm.at(0).as<JsonObject>();
641 partSrcPaths[pp] = findFirst(mo,"url").val.as<String>();
642 }
643 }
644 }
645 }
646 return partSrcPaths;
647}
648
649void saveImagePrint(ImgRgba8 const & img,String8 const & fname)
650{
651 saveImage(img,fname);
652 fgout << fgnl << fname;
653};
654void copyFilePrint(String8 const & from,String8 const & to)
655{
656 copyFile(from,to,true);
657 fgout << fgnl << to;
658}
659
660void dazExportColorG9(
661 Sam3Coord const & coord,
662 Bytes const & detail,
663 String8 const & contentDir,
664 String8s const & baseMapPaths,
665 String8 const & group,
666 String8 const & name,
667 bool adjustIray,
668 WorkerCallback const & updateFn,
669 TattooImage const & tattoo)
670{
671 FGASSERT(baseMapPaths.size() == 5);
672 if (updateFn && updateFn(true))
673 return;
674 // create the modified diffuse color images:
675 String8 imgRelDir = "Runtime/Textures/FaceGen/Genesis9/" + name + "/",
676 imgSaveDir = contentDir + imgRelDir;
677 createPath(imgSaveDir);
678 String8 ddg9 = dataDir()+"artist/daz/Genesis9/",
679 headPath = baseMapPaths[0];
680 FGASSERT(!headPath.empty());
681 String8 imgSaveDirBase = imgSaveDir + "Head";
682 Arr3D const irayFac = adjustIray ? Arr3D{1,1.2,1.2} : Arr3D{1,1,1};
683 Arr3D colorFac = createFaceMap(ddg9+"nrr-head",headPath,imgSaveDirBase,coord,detail,tattoo,irayFac);
684 // make a copy for the SSS (simpler for user to modify, plus Daz has trouble using same soure file for 2 channels):
685 copyFilePrint(imgSaveDirBase+".jpg",imgSaveDirBase+"-SSS.jpg");
686 Strings parts {"Head","Arms","Body","Legs"}; // first includes face
687 for (size_t pp=1; pp<4; ++pp) { // Arms, Body, Legs
688 if (updateFn && updateFn(true))
689 return;
690 String part = parts[pp];
691 String8 baseMapPath = baseMapPaths[pp];
692 FGASSERT(!baseMapPath.empty());
693 //PushIndent op {part};
694 //Timer time;
695 imgSaveDirBase = imgSaveDir+part;
696 ImgRgba8 baseImg = loadImage(baseMapPath);
697 ImgRgba8 adjImg = applyColorFac(baseImg,colorFac);
698 saveImagePrint(adjImg,imgSaveDir+part+".jpg");
699 copyFilePrint(imgSaveDirBase+".jpg",imgSaveDirBase+"-SSS.jpg");
700 //time.report("Body load, shift, save");
701 }
702 if (updateFn && updateFn(true))
703 return;
704 //Timer time;
705 // get the iris color and adjust the Daz iris diffuse map:
706 Sam3 sam {dataDir()+"photofit/si"};
707 sam.transform(cIbfcsToHcsi()); // needed for thumbnail below
708 sam.applyFace(Face3{coord,detail});
709 ImgUC fgIrisSample = toUC(loadImage(dataDir()+"photofit/si-iris-sample.png")),
710 g9IrisRegion = toUC(loadImage(ddg9+"eyes-iris-region.png"));
711 ImgRgba8 g9EyesMap = loadImage(baseMapPaths[4]); // daz eyes base
712 Arr3D clrFg = sampleColor(sam.mesh.surfaces[0].albedoMapRef(),fgIrisSample),
713 clrFgAdj = mapMul(clrFg,irayFac),
714 clrG9 = sampleColor(g9EyesMap,g9IrisRegion),
715 clrDel = clrFgAdj-clrG9;
716 shiftColor_(clrDel,g9IrisRegion,g9EyesMap);
717 saveImagePrint(g9EyesMap,imgSaveDir+"Eyes.jpg");
718 //time.report("Eyes");
719 // create the DUF that points to the images:
720 String8 dufRelPathBase = "People/Genesis 9/Materials" + group + "/" + name;
721 JsonObject contributor {
722 {"author",Any{String{"FaceGen"}}},
723 {"email",Any{String{}}},
724 {"website",Any{String{"FaceGen.com"}}},
725 };
726 JsonObject asset_info {
727 {"id",Any{"/"+encodeUrl(dufRelPathBase)+".duf"}},
728 {"type",Any{String{"preset_material"}}},
729 {"contributor",Any{contributor}},
730 {"revision",Any{String{"1.0"}}},
731 {"modified",Any{getDateTimeString()}},
732 };
733 Anys image_library;
734 String idBase = "FG-G9-" + encodeUrl(name) + "-";
735 auto addImageFn = [&](String const & part)
736 {
737 String id = idBase + part,
738 url = "/" + encodeUrl(imgRelDir + part + ".jpg");
739 JsonObject ul {
740 {"url",Any{url}},
741 {"label",Any{id}},
742 };
743 JsonObject ile {
744 {"id",Any{id}},
745 {"name",Any{id}},
746 {"map_gamma",Any{0.0}},
747 {"map",Any{Anys{Any{ul}}}},
748 };
749 image_library.push_back(Any{ile});
750 };
751 for (String const & part : parts) {
752 addImageFn(part);
753 addImageFn(part+"-SSS");
754 }
755 addImageFn("Eyes");
756 Anys animations;
757 auto addLinkFn = [&](String const & partRef,String const & imgName)
758 {
759 String url = "name://@selection#materials/" + partRef + "/image_file", // NOT URL encoded
760 path = "/" + encodeUrl(imgRelDir+imgName+".jpg");
761 Any key {Anys{Any{0.0},Any{path}}};
762 Anys keys {key,};
763 JsonObject anim {
764 {"url",Any{url}},
765 {"keys",Any{keys}},
766 };
767 animations.push_back(Any{anim});
768 };
769 for (String const & part : parts) {
770 addLinkFn(part+":?diffuse",part); // link diffuse reflectance
771 addLinkFn(part+":?extra/studio_material_channels/channels/Translucency%20Color",part+"-SSS"); // link diffuse SSS
772 }
773 addLinkFn("Eye%20Left:?diffuse","Eyes");
774 addLinkFn("Eye%20Right:?diffuse","Eyes");
775 JsonObject scene {
776 {"animations",Any{animations}},
777 };
778 JsonObject duf {
779 {"file_version",Any{String{"0.6.1.0"}}},
780 {"asset_info",Any{asset_info}},
781 {"image_library",Any{image_library}},
782 {"scene",Any{scene}},
783 };
784 createPath(contentDir+dufRelPathBase);
785 {
786 String8 fname = contentDir+dufRelPathBase+".duf";
787 saveRaw(writeJson(Any{duf}),fname);
788 fgout << fgnl << fname;
789 }
790 // create the thumbnail for the DUF:
791 ImgRgba8 thumb = renderSoft({91U,91U},{sam.mesh},{0,0,0,1});
792 saveImagePrint(thumb,contentDir+dufRelPathBase+".png");
793}
794
795}
796
797// */