FaceGen 3 SDKs Reference
Loading...
Searching...
No Matches
Fg3CmdApply.cpp
1//
2// Copyright (c) Singular Inversions Inc. 2012
3//
4// Authors: Andrew Beatty
5// Created: August 20, 2001.
6//
7// Construct 3D mesh and colour map from FG and SAM files
8//
9
10#include "stdafx.h"
11
12#include "FgSerial.hpp"
13#include "FgCommand.hpp"
14#include "FgFileSystem.hpp"
15#include "FgTime.hpp"
16#include "Fg3dMeshIo.hpp"
17#include "Fg3Sam.hpp"
18#include "FgTestUtils.hpp"
19#include "Fg3ImgIo.hpp"
20#include "fimBmpIo.hpp"
21#include "FgImageIo.hpp"
22#include "FgApply.hpp"
23#include "FgApply.hpp"
24
25using namespace std;
26
27namespace Fg {
28
33void cmd3ConstructSsm(CLArgs const & args)
34{
35 Syntax syn {args,
36 R"(<ssm> <face>.fg <out>.<ext>
37 <ssm> - <baseName> | <mesh>.<ext> <ssm>.egm
38 <ext> - )" + getMeshLoadExtsCLDescription() + R"(
39OUTPUT:
40 <out>.fgmesh In the shape defined by <face>.fg
41NOTES:
42 If <baseName> is used, <baseName>.<ext> and <baseName>.egm must exist)"
43 };
44 Mesh mesh;
45 Ssm3 ssm;
46 if (pathToExt(syn.next()).empty()) {
47 mesh = loadMeshAnyFormat(syn.curr());
48 ssm = Ssm3 {mesh.allVerts(),syn.curr()+".egm"};
49 }
50 else {
51 mesh = loadMesh(syn.curr());
52 ssm = Ssm3 {mesh.allVerts(),syn.next()};
53 }
54 Face3 face {syn.next()};
55 {
56 PushTimer timer {"Construct SSM"};
57 mesh.updateAllVerts(ssm.applyCoord(face.coord));
58 }
59 saveMesh(mesh,syn.next());
60}
61
66void cmd3ConstructScm(CLArgs const & args)
67{
68 Syntax syn {args,
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)
75OUTPUT:
76 <out>.<img> The color map for the given SCM part for the given face.
77NOTES:
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)"
81 };
82 float detailModulation = 1.0f;
83 Scm3 scm;
84 if (pathToExt(syn.next()).empty())
85 scm = Scm3 {syn.curr()};
86 else {
87 string imgFile = syn.curr(),
88 egtFile = syn.next(),
89 fimFile;
90 if (toLower(pathToExt(syn.peekNext())) == "fim")
91 fimFile = syn.next();
92 scm = Scm3 {imgFile,egtFile,fimFile};
93 }
94 Face3 face {syn.next()};
95 string outFile = syn.next();
96 bool mt = true;
97 while (syn.more()) {
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());
102 }
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());
107 scm.gamma = gamma;
108 }
109 else if (syn.curr() == "-s")
110 mt = false;
111 else
112 syn.error("Unrecognized option",syn.curr());
113 }
114 ImgRgba8 outMap;
115 {
116 PushTimer timer {"Construct SCM"};
117 outMap = scm.applyFace(face,detailModulation,mt);
118 }
119 saveImage(outMap,outFile);
120}
121
122Cmd getCmd3ConstructSsm()
123{
124 return Cmd {cmd3ConstructSsm,"ssm","Apply a face to an SSM to create a mesh"};
125}
126
127Cmd getCmd3ConstructScm()
128{
129 return Cmd {cmd3ConstructScm,"scm","Apply a face to an SCM to create a color map"};
130}
131
132void fg3RegressTri(const String & testn,const String & refn)
133{
134 testRegressApprox<Mesh>(
135 loadTri(testn),
136 refn,
137 [](Mesh const & l,Mesh const & r){return isApproxEqualPrec(l.verts,r.verts); },
138 loadMesh,
139 [](Mesh const m,String8 const & f){saveTri(f,m); },
140 isEqualSrlz<Mesh>);
141}
142
143void testCmd3ConstructSsm(CLArgs const & args)
144{
145 FGTESTDIR;
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";
153 runCmd(cmd3ConstructSsm,"ssm Face John.fg "+csj);
154 fg3RegressTri(csj,"main/tests/"+csj);
155 runCmd(cmd3ConstructSsm,"ssm Face.tri Face.egm John.fg "+csj);
156 fg3RegressTri(csj,"main/tests/"+csj);
157 runCmd(cmd3ConstructSsm,"ssm Short John.fg constructHairJohn.tri");
158 fg3RegressTri("constructHairJohn.tri","main/tests/constructHairJohn.tri");
159}
160
161void testCmd3ConstructScm(CLArgs const & args)
162{
163 FGTESTDIR;
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";
170 runCmd(cmd3ConstructScm,"scm Face John.fg "+csj);
171 regressImage(csj,"main/tests/",4);
172 runCmd(cmd3ConstructScm,"scm Face.png Face.egt Face.fim John.fg "+csj);
173 regressImage(csj,"main/tests/",4);
174}
175
176void cmdApplyDaz(CLArgs const & args)
177{
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
193NOTES:
194 * <gender> is ignored for G9 which uses a gender-neutral mesh
195 * morph controls will appear under the group name 'FaceGen')"
196 };
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();
207 String8s baseMaps;
208 while (syn.more())
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,{});
217 }
218 }
219 else { // versions 1 through 8.1:
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,{});
224 }
225}
226
227void testDaz81(CLArgs const &)
228{
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");
238}
239
240void testDaz9(CLArgs const &)
241{
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()) { // not found, can only test morph
247 runCmd(cmdApplyDaz,"daz John.fg 9 f . test");
248 }
249 else {
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");
257 }
258}
259
260void testDaz(CLArgs const & args)
261{
262 Cmds cmds {
263 {testDaz81,"81","Genesis 8.1"},
264 {testDaz9,"9","Genesis 9"},
265 };
266 doMenu(args,cmds,true);
267}
268
269void testCmdApply(CLArgs const & args)
270{
271 Cmds cmds {
272 {testDaz,"daz"},
273 {testCmd3ConstructScm,"scm"},
274 {testCmd3ConstructSsm,"ssm"},
275 };
276 doMenu(args,cmds,true);
277}
278
279void cmdApplyDna(CLArgs const & args)
280{
281 Syntax syn {args,R"(<face>.fg <in>.dna <out>.dna
282OUTPUT:
283 <out>.dna - <in>.dna with vertex positions adjusted to the shape of <face>.fg)"
284 };
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));
305 else
306 shapes.push_back(ssm.applyCoord(face.coord));
307 }
308 dnaInject_(shapes,dna);
309 saveRaw(dna,outfile);
310}
311
312void cmdApplyIris(CLArgs const & args)
313{
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"(
318OUTPUT:
319 <output>.<img> - The <region> area of <color> is adjusted to match the iris color of <face>)"
320 };
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";
326 Scm3 fgScm {dd};
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);
333}
334
335void cmdApply(CLArgs const & args)
336{
337 Cmds cmds {
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"},
344 };
345 doMenu(args,cmds);
346}
347
348}
void cmd3ConstructScm(CLArgs const &args)
void cmd3ConstructSsm(CLArgs const &args)
FaceGen 3 Statistical Shape Model.
Definition Fg3Sam.hpp:39
Vec3Fs applyCoord(Sam3Coord const &coord) const
Functional version:
Definition Fg3Sam.hpp:63