12#include "FgSerial.hpp"
13#include "FgCommand.hpp"
14#include "FgFileSystem.hpp"
16#include "Fg3dMeshIo.hpp"
18#include "FgTestUtils.hpp"
19#include "Fg3ImgIo.hpp"
20#include "fimBmpIo.hpp"
21#include "FgImageIo.hpp"
36 R
"(<ssm> <face>.fg <out>.<ext>
37 <ssm> - <baseName> | <mesh>.<ext> <ssm>.egm
38 <ext> - )" + getMeshLoadExtsCLDescription() + R"(
40 <out>.fgmesh In the shape defined by <face>.fg
42 If <baseName> is used, <baseName>.<ext> and <baseName>.egm must exist)"
46 if (pathToExt(syn.next()).empty()) {
47 mesh = loadMeshAnyFormat(syn.curr());
48 ssm =
Ssm3 {mesh.allVerts(),syn.curr()+
".egm"};
51 mesh = loadMesh(syn.curr());
52 ssm =
Ssm3 {mesh.allVerts(),syn.next()};
54 Face3 face {syn.next()};
56 PushTimer timer {
"Construct SSM"};
57 mesh.updateAllVerts(ssm.
applyCoord(face.coord));
59 saveMesh(mesh,syn.next());
69 R
"(<scm> <face>.fg <out>.<img> [-d <detailModulation>] [-g <gamma>] [-s]
70 <scm> - <baseName> | <base>.<img> <scm>.egt [<uvmap>.fim]
71 <img> - )" + clOptionsStr(getImgExts()) + R"(
72 <detailModulation> - [0.0,2.0] defaults to 1.0
73 <gamma> - [1.0,3.0] defaults to )" + toStr(Scm3::gammaDefault()) + R"(
74 -s - single-threaded only (don't use multithreading)
76 <out>.<img> The color map for the given SCM part for the given face.
78 * If <face>.fg contains a detail texture this will also be applied
79 * Detail modulation allows you to increase or reduce the strength of this application
80 * This command is only used on color maps containing skin)"
82 float detailModulation = 1.0f;
84 if (pathToExt(syn.next()).empty())
85 scm = Scm3 {syn.curr()};
87 string imgFile = syn.curr(),
90 if (toLower(pathToExt(syn.peekNext())) ==
"fim")
92 scm = Scm3 {imgFile,egtFile,fimFile};
94 Face3 face {syn.next()};
95 string outFile = syn.next();
98 if (syn.next() ==
"-d") {
99 detailModulation = syn.nextAs<
float>();
100 if ((detailModulation < 0.0f) || (detailModulation > 2.0f))
101 syn.error(
"Invalid detailModulation value",syn.curr());
103 else if (syn.curr() ==
"-g") {
104 float gamma = syn.nextAs<
float>();
105 if ((gamma < 1.0f) || (gamma > 3.0f))
106 syn.error(
"Invalid gamma value",syn.curr());
109 else if (syn.curr() ==
"-s")
112 syn.error(
"Unrecognized option",syn.curr());
116 PushTimer timer {
"Construct SCM"};
117 outMap = scm.applyFace(face,detailModulation,mt);
119 saveImage(outMap,outFile);
122Cmd getCmd3ConstructSsm()
124 return Cmd {
cmd3ConstructSsm,
"ssm",
"Apply a face to an SSM to create a mesh"};
127Cmd getCmd3ConstructScm()
129 return Cmd {
cmd3ConstructScm,
"scm",
"Apply a face to an SCM to create a color map"};
132void fg3RegressTri(
const String & testn,
const String & refn)
134 testRegressApprox<Mesh>(
137 [](Mesh
const & l,Mesh
const & r){
return isApproxEqualPrec(l.verts,r.verts); },
139 [](Mesh
const m,String8
const & f){saveTri(f,m); },
143void testCmd3ConstructSsm(CLArgs
const & args)
146 string pfd =
"csam/Preview/Face/";
147 copyDataFileToCurr(
"main/John.fg");
148 copyDataFileToCurr(pfd+
"Face.tri");
149 copyDataFileToCurr(pfd+
"Face.egm");
150 copyDataFileToCurr(
"csam/Animate/Hair/Short.tri");
151 copyDataFileToCurr(
"csam/Animate/Hair/Short.egm");
152 string csj =
"constructSiJohn.tri";
154 fg3RegressTri(csj,
"main/tests/"+csj);
156 fg3RegressTri(csj,
"main/tests/"+csj);
158 fg3RegressTri(
"constructHairJohn.tri",
"main/tests/constructHairJohn.tri");
161void testCmd3ConstructScm(CLArgs
const & args)
164 string pfd =
"csam/Preview/Face/";
165 copyDataFileToCurr(
"main/John.fg");
166 copyDataFileToCurr(pfd+
"Face.png");
167 copyDataFileToCurr(pfd+
"Face.egt");
168 copyDataFileToCurr(pfd+
"Face.fim");
169 string csj =
"constructSiJohn.png";
171 regressImage(csj,
"main/tests/",4);
173 regressImage(csj,
"main/tests/",4);
176void cmdApplyDaz(CLArgs
const & args)
178 Svec<DazGenesis> dgi = cDazGenesisInfo();
179 Strings versions = mapMember(dgi,&DazGenesis::str);
180 String versionSyntax =
"( " + cat(versions,
" | ") +
" )";
181 Syntax syn {args,R
"(<face>.fg <version> <gender> <dazLib> <name> [<img>+]
182 <version> - )" + versionSyntax + R"( corresponding to the Genesis version.
183 <gender> - (F | M) corresponding to female or male.
184 <dazLib> - Daz content library directory for output.
185 <name> - Output DSF and image files will start with this name.
186 <img>+ - Base color map image files. If given they must be as follows:
187 - G1: face, limbs, torso
188 - G2: face, limbs, torso
189 - G3: face, arms, legs, torso
190 - G8: face, arms, legs, torso
191 - G8.1: face, arms, body, head, legs
192 - G9: head, arms, body, legs, eyes
194 * <gender> is ignored for G9 which uses a gender-neutral mesh
195 * morph controls will appear under the group name 'FaceGen')"
197 Face3 face {syn.next()};
198 size_t verIdx = findFirstIdx(dgi,syn.next());
199 if (verIdx >= dgi.size())
200 syn.error(
"not a valid Daz Genesis version number",syn.curr());
201 DazGenesisVer version = dgi[verIdx].ver;
202 bool female = (toLower(syn.next()) ==
"f");
203 if (!female && (toLower(syn.curr()) !=
"m"))
204 syn.error(
"not a valid gender",syn.curr());
205 String8 dazLib = asDirectory(syn.next());
206 String outName = syn.next();
209 baseMaps.push_back(syn.next());
210 if (version == DazGenesisVer::G9) {
211 PushIndent pind {
"Writing files:"};
212 dazExportShapeG9(face.coord,dazLib,
"/FaceGen",outName);
213 if (!baseMaps.empty()) {
214 if (baseMaps.size() != 5)
215 syn.error(
"G9 requires 5 base maps; Head, Arms, Body, Legs, Eyes");
216 dazExportColorG9(face.coord,face.detail,dazLib,baseMaps,
"/FaceGen",outName,
false,{});
220 Strings bodyMaps = dazGenesisBodyMapNames(version);
221 if ((!baseMaps.empty()) && (baseMaps.size() != bodyMaps.size()+1))
222 syn.error(
"Incorrect number of maps",
"face,"+cat(bodyMaps,
","));
223 dazExport(version,female,dazLib,face.coord,face.detail,
true,
"/FaceGen",outName,baseMaps,{});
227void testDaz81(CLArgs
const &)
229 TestDir td {
"fg3-daz-81"};
230 copyDataFileToCurr(
"main/John.fg");
231 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_arms.jpg");
232 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_body.jpg");
233 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_eyes.jpg");
234 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_face.jpg");
235 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_head.jpg");
236 copyDataFileToCurr(
"artist/daz/Genesis81F/daz_legs.jpg");
237 runCmd(cmdApplyDaz,
"daz John.fg 81 f . test daz_face.jpg daz_arms.jpg daz_body.jpg daz_head.jpg daz_legs.jpg");
240void testDaz9(CLArgs
const &)
242 TestDir td {
"fg3-daz-9"};
243 copyDataFileToCurr(
"main/John.fg");
244 String dufRelDir =
"/People/Genesis 9/Materials/Daz Originals/Base Materials/";
245 String8 dazContentDir = getDazContentDirContaining(dufRelDir +
"G9 Feminine Skin 01 MAT.duf");
246 if (dazContentDir.empty()) {
247 runCmd(cmdApplyDaz,
"daz John.fg 9 f . test");
250 String8 mapBase = dazContentDir+
"Runtime/Textures/DAZ/Characters/Genesis9/Base/Feminine_01/G9Feminine01_";
251 copyFile(mapBase+
"Head_D_1001.jpg",
"Head.jpg");
252 copyFile(mapBase+
"Arms_D_1004.jpg",
"Arms.jpg");
253 copyFile(mapBase+
"Body_D_1002.jpg",
"Body.jpg");
254 copyFile(mapBase+
"Legs_D_1003.jpg",
"Legs.jpg");
255 copyFile(dataDir()+
"artist/daz/Genesis9/G9_Eyes03_D.jpg",
"Eyes.jpg");
256 runCmd(cmdApplyDaz,
"daz John.fg 9 f . test Head.jpg Arms.jpg Body.jpg Legs.jpg Eyes.jpg");
260void testDaz(CLArgs
const & args)
263 {testDaz81,
"81",
"Genesis 8.1"},
264 {testDaz9,
"9",
"Genesis 9"},
266 doMenu(args,cmds,
true);
269void testCmdApply(CLArgs
const & args)
273 {testCmd3ConstructScm,
"scm"},
274 {testCmd3ConstructSsm,
"ssm"},
276 doMenu(args,cmds,
true);
279void cmdApplyDna(CLArgs
const & args)
281 Syntax syn {args,R
"(<face>.fg <in>.dna <out>.dna
283 <out>.dna - <in>.dna with vertex positions adjusted to the shape of <face>.fg)"
285 Face3 face = loadFg(syn.next());
286 Bytes dna = loadRaw(syn.next());
287 String outfile = syn.next();
288 Meshes meshes = dnaImport(dna);
289 size_t M = meshes.size();
290 String8 dd = dataDir()+
"artist/dna/";
291 Mesh lmsMesh = loadMesh(dd+
"lms-nrr.fgmesh");
292 FaceLmPos3Fs lmsBase = toFaceLmPoss(lmsMesh.markedVertsAsNameVecs());
293 Ssm3 lmsSsm {lmsMesh.verts,dd+
"lms-nrr.egm"};
294 Arr<ScaleTrans3F,2> eyeXfs = cEyesTransform(lmsBase,lmsSsm.applyCoord(face.coord));
295 Ssm3s ssms = dsrlz<Ssm3s>(loadRaw(dd+
"mean.Ssm3s"));
296 FGASSERT(ssms.size() == M);
297 Vec3Fss shapes; shapes.reserve(M);
298 for (
size_t mm=0; mm<M; ++mm) {
299 Mesh
const & mesh = meshes[mm];
300 Ssm3
const & ssm = ssms[mm];
301 if (containsSubstr(mesh.name.m_str,
"eyeLeft"))
302 shapes.push_back(mapMul(eyeXfs[0],ssm.meanVerts));
303 else if (containsSubstr(mesh.name.m_str,
"eyeRight"))
304 shapes.push_back(mapMul(eyeXfs[1],ssm.meanVerts));
306 shapes.push_back(ssm.applyCoord(face.coord));
308 dnaInject_(shapes,dna);
309 saveRaw(dna,outfile);
312void cmdApplyIris(CLArgs
const & args)
314 Syntax syn {args,R
"(<face>.fg <color>.<img> <region>.<img> <output>.<img>
315 <color>.<img> - The color map for the face which contains the irises
316 <region>.<img> - Greyscale image [0,255] with same layout and aspect ratio as <color> defining iris area (=255)
317 <img> - )" + clOptionsStr(getImgExts()) + R"(
319 <output>.<img> - The <region> area of <color> is adjusted to match the iris color of <face>)"
321 Face3 face = loadFg(syn.next());
322 ImgRgba8 color = loadImage(syn.next());
323 ImgUC region = toUC(loadImage(syn.next()));
324 String outName = syn.next();
325 String8 dd = dataDir() + "photofit/si";
327 ImgRgba8 fgColor = fgScm.applyFace(face);
328 ImgUC fgRegion = toUC(loadImage(dataDir()+
"photofit/si-iris-sample.png"));
329 Arr3D clr = sampleColor(color,region),
330 fgClr = sampleColor(fgColor,fgRegion);
331 shiftColor_(fgClr-clr,region,color);
332 saveImage(color,outName);
335void cmdApply(CLArgs
const & args)
338 {cmdApplyDaz,
"daz",
"Apply a face to Daz Studio Genesis meshes"},
339 {cmdApplyDna,
"dna",
"Apply a face to an Epic Metahuman DNA file"},
340 {cmdApplyIris,
"iris",
"Apply a face's iris color to a color map"},
341 {getCmd3ConstructScm()},
342 {getCmd3ConstructSsm()},
343 {cmdScm3Base,
"scmBase",
"Apply custom base textures to the SCMs of a body"},
void cmd3ConstructScm(CLArgs const &args)
void cmd3ConstructSsm(CLArgs const &args)
FaceGen 3 Statistical Shape Model.
Vec3Fs applyCoord(Sam3Coord const &coord) const
Functional version: