PageSourceSearch

https://yuxuan-xue.com/infini-human/

html yuxuan-xue.com collected 2026-10-01 15:06:47 UTC 59,426 bytes, 1,370 lines download raw bytes

1<!DOCTYPE html>
2<html>
3<head>
4  <meta charset="utf-8">
5  <meta name="description"
6        content="InfiniHuman: Generate infinite 3D humans with precise control from text, clothing, body shape, and pose. Automatic pipeline creating 111K diverse identities with multi-modal annotations.">
7  <meta name="keywords" content="3D Human Generation, Diffusion Model, Text-to-3D, Controllable Avatar, 3D Gaussian Splatting, SIGGRAPH Asia 2025">
8  <meta name="viewport" content="width=device-width, initial-scale=1">
9  <title>InfiniHuman: Infinite 3D Human Creation with Precise Control</title>
10
11  <!-- Global site tag (gtag.js) - Google Analytics -->
vendor: 66 bytes, lines 11-12
11
12  <script async src="https://www.googletagmanager.com/gtag/js?id=
12G-PYVRSFMDRL
vendor: 14 bytes, lines 12-13
12"></script>
13  
13<script>
14    
vendor: 155 bytes, lines 14-22
14window.dataLayer = window.dataLayer || [];
15
16    function gtag() {
17      dataLayer.push(arguments);
18    }
19
20    gtag('js', new Date());
21
22    gtag('config', '
22G-PYVRSFMDRL
vendor: 6 bytes, lines 22-23
22');
23  
23</script>
23
24
25  <link href="https://fonts.googleapis.com/css?family=Google+Sans|Noto+Sans|Castoro"
26        rel="stylesheet">
27
28  <link rel="stylesheet" href="./static/css/bulma.min.css">
29  <link rel="stylesheet" href="./static/css/bulma-carousel.min.css">
30  <link rel="stylesheet" href="./static/css/bulma-slider.min.css">
31  <link rel="stylesheet" href="./static/css/fontawesome.all.min.css">
32  <link rel="stylesheet"
33        href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css">
34  <link rel="stylesheet" href="./static/css/index.css">
35  <link rel="icon" href="./static/images/einstein_icon.png">
36
37  <!-- Import map must come BEFORE any module scripts -->
38  
38<script type="importmap">
39    {
40      "imports": {
41        "three": "https://unpkg.com/[email protected]/build/three.module.js",
42        "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
43      }
44    }
45  </script>
45
46  
47  
47<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
47
48  
48<script defer src="./static/js/fontawesome.all.min.js"></script>
48
49  
49<script src="./static/js/bulma-carousel.min.js"></script>
49
50  
50<script src="./static/js/bulma-slider.min.js"></script>
50
51  
51<script src="./static/js/index.js"></script>
51
52
53  <style>
54    .imagesrow {
55      display: flex;
56      justify-content: center;
57      align-items: center;
58      gap: 30px; /* Gap between images */
59    }
60    .row_img {
61      max-width: 100%; /* Adjust the width as needed */
62    }
63  </style>
64</head>
65<body>
66
67<section class="hero">
68  <div class="hero-body">
69    <div class="container is-max-desktop">
70      <div class="columns is-centered">
71        <div class="column has-text-centered">
72          <h1 class="title is-2 publication-title">InfiniHuman: Infinite 3D Human Creation with Precise Control</h1>
73          <div class="is-size-5 publication-authors">
74            <span class="author-block">
75              <a href="https://yuxuan-xue.com">Yuxuan Xue</a><sup>1,2</sup>,</span>
76            <span class="author-block">
77              <a href="https://virtualhumans.mpi-inf.mpg.de/people/Xie.html">Xianghui Xie</a><sup>1,2,3</sup>,</span>
78              <span class="author-block">
79                <a href="https://yuxuan-xue.com">Margaret Kostyrko</a><sup>1,2</sup>,
80              </span>
81            <span class="author-block">
82              <a href="https://virtualhumans.mpi-inf.mpg.de/people/pons-moll.html">Gerard Pons-Moll</a><sup>1,2,3</sup>
83            </span>
84          </div>
85
86          <div class="is-size-5 publication-authors">
87            <span class="author-block"><sup>1</sup>University of Tübingen, </span>  
88            <span class="author-block"><sup>2</sup>Tübingen AI Center,</span> <br> 
89            <span class="author-block"><sup>3</sup>Max Planck Institute for Informatics, Saarland Informatics Campus </sp
89an><br>
90            <!-- <span class="author-block">Technical Report, 2024</span> -->
91          </div>
92
93          <div class="column has-text-centered">
94            <div class="publication-links">
95              <!-- PDF Link. -->
96              <span class="link-block">
97                <a href="paper/infinihuman.pdf"
98                   class="external-link button is-normal is-rounded is-dark">
99                  <span class="icon">
100                      <i class="fas fa-file-pdf"></i>
101                  </span>
102                  <span>Paper</span>
103                </a>
104              </span>
105              <span class="link-block">
106                <a href="https://arxiv.org/abs/2510.11650"
107                   class="external-link button is-normal is-rounded is-dark">
108                  <span class="icon">
109                      <i class="ai ai-arxiv"></i>
110                  </span>
111                  <span>arXiv</span>
112                </a>
113              </span>
114              <!-- Video Link. -->
115              <span class="link-block">
116                <a href="https://youtu.be/nqoomWVDR6w"
117                   class="external-link button is-normal is-rounded is-dark">
118                  <span class="icon">
119                      <i class="fab fa-youtube"></i>
120                  </span>
121                  <span>Video</span>
122                </a>
123              </span>
124              <!-- Code Link. -->
125              <span class="link-block">
126                <a href="https://github.com/YuxuanSnow/InfiniHuman/"
127                   class="external-link button is-normal is-rounded is-dark">
128                  <span class="icon">
129                      <i class="fab fa-github"></i>
130                  </span>
131                  <span>Code</span>
132                  </a>
133              </span>
134            </div>
135            
136            <div class="is-size-5 publication-journal" style="margin-top: 10px;">
137              <span class="journal-block"><strong>SIGGRAPH Asia 2025</strong></span>
138            </div>
139
140          </div>
141        </div>
142      </div>
143    </div>
144  </div>
145</section>
146
147<section class="hero teaser">
148  <div class="container is-max-desktop">
149    <div class="hero-body">
150      <video id="teaser" autoplay muted loop playsinline height="100%">
151        <source src="./static/videos/infinihuman_teaser_crop.mp4"
152                type="video/mp4">
153      </video>
154      <h2 class="subtitle has-text-centered">
155        <span class="dnerf">InfiniHuman</span> can create infinite 3D humans with precise control.
156      </h2>
157    </div>
158  </div>
159</section>
160
161
162  <div class="container is-max-desktop">
163    <!-- Abstract. -->
164    <div class="columns is-centered has-text-centered">
165      <div class="column is-four-fifths">
166        <h2 class="title is-4">Abstract</h2>
167        <div class="content has-text-justified">
168          <p>
169            <strong>TL;DR: We distill foundation models to generate theoretically unbounded, richly annotated 3D human data with 111K identities, enabling fast, realistic, and precisely controllable avatar generation from text descriptions, body shape, and clothing images.</strong>
170            <br>
171            <br>
172            Generating diverse and controllable 3D human avatars is challenging due to the prohibitive cost of capturing and annotating large-scale datasets. 
173            We introduce <strong>InfiniHuman</strong>, a framework that distills foundation models to generate richly annotated human data with minimal cost and unlimited scalability.
174            Our <strong>InfiniHumanData</strong> pipeline automatically creates <strong>111K identities</strong> with unprecedented diversity in ethnicity, age, and clothing styles, each annotated with text descriptions, multi-view images, and SMPL body parameters.
175            Based on this, <strong>InfiniHumanGen</strong> enables fast, realistic, and precisely controllable avatar generation conditioned on text, body shape, and clothing assets.
176          </p>
177        </div>
178      </div>
179    </div>
180  </div>
181
182  <div class="container is-max-desktop">
183    <br>
184     <!-- <h2 class="title is-3">Pipeline</h2>-->
185    <div class="hero-body">
186      <img src="./static/images/pipeline_infiniHumanGen_3.png"/>
187      <p> Taking text description, explicit SMPL shape, and a cloth image as input, Gen-Schnell generates 3D-GS end-to-e
187nd, while Gen-HRes generates high-resolution textured mesh, both matched to input conditions. </p>
188    </div>
189  </div>
190
191
192
193
194<section class="section">
195  <div class="container is-max-desktop">
196    <h2 class="title is-3">Generation Results</h2>
197    <div class="content has-text-justified">
198      <p>
199        InfiniHuman enables diverse and controllable 3D human generation from multiple input modalities. 
200        Below we showcase results from four different generation tasks:
201        <a href="#gen-text-prompts"><strong>Generation from Text Prompts</strong></a>, 
202        <a href="#gen-clothing"><strong>Generation from Clothing</strong></a>, 
203        <a href="#gen-body-pose-shape"><strong>Generation from Body Pose & Shape</strong></a>, and 
204        <a href="#edit-text"><strong>Editing from Text</strong></a>.
205        Click on any link to jump directly to that section, or scroll down to explore all results.
206      </p>
207    </div>
208    
209    <!-- Subsection: Generation from Text Prompts -->
210    <div class="columns is-centered has-text-centered" id="gen-text-prompts">
211      <div class="column is-four-fifths">
212        <h3 class="title is-4">Generation from Text Prompts</h3>
213        
214        <!-- Single 3D Viewer for Text Results -->
215        <div style="margin-bottom: 2rem;">
216          <div id="text-viewer" style="width: 100%; height: 500px; background-color: #f5f5f5; border-radius: 8px; border: 1px solid #ddd; position: relative;">
217            <div class="text-loading-text" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #888; z-index: 10;">Loading 3D model...</div>
218          </div>
219        </div>
220        
221        <!-- Tabs for Text Prompts -->
222        <div class="tabs is-centered is-boxed" style="margin-top: 2rem;">
223          <ul id="text-tabs-container"></ul>
224        </div>
225        
226        
226<script type="module">
227          import * as THREE from 'three';
228          import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
229          import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
230          import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
231          
232          // Define text-based results - easily customizable
233          const textResults = [
234            { id: 1, prompt: 'Albert Einstein', file: 'einstein_1.obj', format: 'obj' },
235            { id: 3, prompt: 'Joker', file: 'joker.obj', format: 'obj' },
236            { id: 7, prompt: 'Donald Trump', file: 'trump.obj', format: 'obj' },
237            { id: 4, prompt: 'Angela Merkel', file: 'merkel.obj', format: 'obj' },
238            { id: 6, prompt: 'Barack Obama', file: 'obama.obj', format: 'obj' },
239            { id: 5, prompt: 'Spider Man', file: 'spiderman.obj', format: 'obj' },
240            { id: 2, prompt: 'Iron Man', file: 'ironman.obj', format: 'obj' },
241            { id: 8, prompt: 'Lionel Messi', file: 'messi.obj', format: 'obj' },
242          ];
243          
244          let textScene, textCamera, textRenderer, textControls, textCurrentMesh;
245          let textLoadingText;
246          
247          // Initialize the text viewer
248          function initTextViewer() {
249            const container = document.getElementById('text-viewer');
250            textLoadingText = container.querySelector('.text-loading-text');
251            const width = container.clientWidth;
252            const height = 500;
253            
254            // Scene setup
255            textScene = new THREE.Scene();
256            textScene.background = new THREE.Color(0xf5f5f5);
257            
258            // Camera setup
259            textCamera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
260            textCamera.position.set(0, 0, 2.5);
261            textCamera.lookAt(0, 0, 0);
262            
263            // Renderer setup
264            textRenderer = new THREE.WebGLRenderer({ antialias: true });
265            textRenderer.setSize(width, height);
266            textRenderer.setPixelRatio(window.devicePixelRatio);
267            container.appendChild(textRenderer.domElement);
268            
269            // Controls
270            textControls = new OrbitControls(textCamera, textRenderer.domElement);
271            textControls.enableDamping = true;
272            textControls.autoRotate = true;
273            textControls.autoRotateSpeed = 2.0;
274            
275            // Lighting
276            const ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
277            textScene.add(ambientLight);
278            
279            const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
280            directionalLight1.position.set(1, 1, 1);
281            textScene.add(directionalLight1);
282            
283            const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.2);
284            directionalLight2.position.set(-1, -1, -1);
285            textScene.add(directionalLight2);
286            
287            const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.0);
288            textScene.add(hemisphereLight);
289            
290            // Animation loop
291            function animate() {
292              requestAnimationFrame(animate);
293              textControls.update();
294              textRenderer.render(textScene, textCamera);
295            }
296            animate();
297            
298            // Handle window resize
299            window.addEventListener('resize', () => {
300              const newWidth = container.clientWidth;
301              textCamera.aspect = newWidth / height;
302              textCamera.updateProjectionMatrix();
303              textRenderer.setSize(newWidth, height);
304            });
305          }
306          
307          // Load a 3D model for text results
308          function loadTextModel(modelPath, prompt, format = 'ply') {
309            textLoadingText.style.display = 'block';
310            textLoadingText.textContent = 'Loading 3D model...';
311            
312            // Remove current mesh if exists
313            if (textCurrentMesh) {
314              textScene.remove(textCurrentMesh);
315              if (textCurrentMesh.geometry) textCurrentMesh.geometry.dispose();
316              if (textCurrentMesh.material) {
317                if (Array.isArray(textCurrentMesh.material)) {
318                  textCurrentMesh.material.forEach(m => m.dispose());
319                } else {
320                  textCurrentMesh.material.dispose();
321                }
322              }
323              if (textCurrentMesh.children) {
324                textCurrentMesh.traverse((child) => {
325                  if (child.geometry) child.geometry.dispose();
326                  if (child.material) {
327                    if (Array.isArray(child.material)) {
328                      child.material.forEach(m => m.dispose());
329                    } else {
330                      child.material.dispose();
331                    }
332                  }
333                });
334              }
335            }
336            
337            const loader = format === 'obj' ? new OBJLoader() : new PLYLoader();
338            
339            loader.load(
340              modelPath,
341              function (loadedObject) {
342                console.log(`Successfully loaded ${format.toUpperCase()} for text prompt: ${prompt}`);
343                
344                let mesh;
345                
346                if (format === 'obj') {
347                  mesh = loadedObject;
348                  
349                  mesh.traverse((child) => {
350                    if (child.isMesh) {
351                      child.geometry.computeVertexNormals();
352                      const hasVertexColors = child.geometry.attributes.color !== undefined;
353                      
354                      child.material = new THREE.MeshStandardMaterial({
355                        color: hasVertexColors ? 0xffffff : 0x606060,
356                        flatShading: false,
357                        side: THREE.DoubleSide,
358                        vertexColors: hasVertexColors,
359                        roughness: 0.8,
360                        metalness: 0.0
361                      });
362                    }
363                  });
364                  
365                  const box = new THREE.Box3().setFromObject(mesh);
366                  const center = new THREE.Vector3();
367                  box.getCenter(center);
368                  
369                  mesh.traverse((child) => {
370                    if (child.isMesh) {
371                      child.geometry.translate(-center.x, -center.y, -center.z);
372                    }
373                  });
374                  
375                  const size = new THREE.Vector3();
376                  box.getSize(size);
377                  const maxDim = Math.max(size.x, size.y, size.z);
378                  const scale = 1.8 / maxDim;
379                  mesh.scale.set(scale, scale, scale);
380                  
381                } else {
382                  const geometry = loadedObject;
383                  geometry.computeVertexNormals();
384                  
385                  const material = new THREE.MeshStandardMaterial({
386                    color: 0xaaaaaa,
387                    flatShading: false,
388                    side: THREE.DoubleSide,
389                    vertexColors: geometry.attributes.color ? true : false
390                  });
391                  
392                  mesh = new THREE.Mesh(geometry, material);
393                  
394                  geometry.computeBoundingBox();
395                  const center = new THREE.Vector3();
396                  geometry.boundingBox.getCenter(center);
397                  mesh.geometry.translate(-center.x, -center.y, -center.z);
398                  
399                  const size = new THREE.Vector3();
400                  geometry.boundingBox.getSize(size);
401                  const maxDim = Math.max(size.x, size.y, size.z);
402                  const scale = 1.8 / maxDim;
403                  mesh.scale.set(scale, scale, scale);
404                  
405                  mesh.rotation.x = Math.PI;
406                }
407                
408                textCurrentMesh = mesh;
409                textScene.add(textCurrentMesh);
410                textLoadingText.style.display = 'none';
411                
412                textCamera.position.set(0, 0, 2.5);
413                textCamera.lookAt(0, 0, 0);
414                textControls.target.set(0, 0, 0);
415                textControls.update();
416              },
417              function (xhr) {
418                const percentComplete = xhr.loaded / xhr.total * 100;
419                textLoadingText.textContent = `Loading: ${Math.round(percentComplete)}%`;
420              },
421              function (error) {
422                console.error(`Error loading ${format.toUpperCase()} for text prompt: ${prompt}`, error);
423                textLoadingText.textContent = 'Error loading 3D model';
424                textLoadingText.style.fontSize = '0.9em';
425              }
426            );
427          }
428          
429          // Generate tabs
430          const tabsContainer = document.getElementById('text-tabs-container');
431          textResults.forEach((result, index) => {
432            const li = document.createElement('li');
433            if (index === 0) li.className = 'is-active';
434            
435            li.innerHTML = `<a>${result.prompt}</a>`;
436            
437            li.addEventListener('click', () => {
438              // Update active state
439              document.querySelectorAll('#text-tabs-container li').forEach((tab, idx) => {
440                if (idx === index) {
441                  tab.className = 'is-active';
442                } else {
443                  tab.className = '';
444                }
445              });
446              
447              const format = result.format || 'ply';
448              loadTextModel(`./static/results/${result.file}`, result.prompt, format);
449            });
450            
451            tabsContainer.appendChild(li);
452          });
453          
454          // Initialize viewer and load first model
455          initTextViewer();
456          loadTextModel(`./static/results/${textResults[0].file}`, textResults[0].prompt, textResults[0].format);
457        </script>
457
458      </div>
459    </div>
460    
461    <!-- Subsection: Generation from Clothing -->
462    <div class="columns is-centered has-text-centered" style="margin-top: 3rem;" id="gen-clothing">
463      <div class="column is-four-fifths">
464        <h3 class="title is-4">Generation from Clothing</h3>
465        
466        <!-- Single 3D Viewer -->
467        <div style="margin-bottom: 2rem;">
468          <div id="main-viewer" style="width: 100%; height: 500px; background-color: #f5f5f5; border-radius: 8px; border: 1px solid #ddd; position: relative;">
469            <div class="loading-text" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #888; z-index: 10;">Loading 3D model...</div>
470          </div>
471        </div>
472        
473        <!-- Thumbnail Grid -->
474        <div id="thumbnail-container" style="display: flex; gap: 1rem; margin-top: 2rem; overflow-x: auto; padding-bottom: 1rem;"></div>
475        
476        
476<script type="module">
477          import * as THREE from 'three';
478          import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
479          import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
480          import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
481          
482          // Define results data - easily portable to other repos
483          // You can specify format as 'ply' or 'obj' (defaults to 'ply' if not specified)
484          const results = [
485            { id: '01_0079_010', image: '01_0079_010.png', format: 'obj' },
486            { id: '03_0145_018', image: '03_0145_018.png', format: 'obj' },
487            { id: '04_0062_012', image: '04_0062_012.png', format: 'obj' },
488            { id: '04_0111_011', image: '04_0111_011.png', format: 'obj' },
489            { id: '08_0089_015', image: '08_0089_015.png', format: 'obj' },
490            { id: '22_0211_005', image: '22_0211_005.png', format: 'obj' },
491            { id: '23_0134_001', image: '23_0134_001.png', format: 'obj' },
492            { id: '33_0092_016', image: '33_0092_016.png', format: 'obj' },
493          ];
494          
495          let scene, camera, renderer, controls, currentMesh;
496          let loadingText;
497          
498          // Initialize the viewer once
499          function initViewer() {
500            const container = document.getElementById('main-viewer');
501            loadingText = container.querySelector('.loading-text');
502            const width = container.clientWidth;
503            const height = 500;
504            
505            // Scene setup
506            scene = new THREE.Scene();
507            scene.background = new THREE.Color(0xf5f5f5);
508            
509            // Camera setup
510            camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
511            camera.position.set(0, 0, 2.5);
512            camera.lookAt(0, 0, 0);
513            
514            // Renderer setup
515            renderer = new THREE.WebGLRenderer({ antialias: true });
516            renderer.setSize(width, height);
517            renderer.setPixelRatio(window.devicePixelRatio);
518            container.appendChild(renderer.domElement);
519            
520            // Controls
521            controls = new OrbitControls(camera, renderer.domElement);
522            controls.enableDamping = true;
523            controls.autoRotate = true;
524            controls.autoRotateSpeed = 2.0;
525            
526            // Improved Lighting - Much Brighter
527            const ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
528            scene.add(ambientLight);
529            
530            const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
531            directionalLight1.position.set(1, 1, 1);
532            scene.add(directionalLight1);
533            
534            const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.2);
535            directionalLight2.position.set(-1, -1, -1);
536            scene.add(directionalLight2);
537            
538            const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.0);
539            scene.add(hemisphereLight);
540            
541            // Animation loop
542            function animate() {
543              requestAnimationFrame(animate);
544              controls.update();
545              renderer.render(scene, camera);
546            }
547            animate();
548            
549            // Handle window resize
550            window.addEventListener('resize', () => {
551              const newWidth = container.clientWidth;
552              camera.aspect = newWidth / height;
553              camera.updateProjectionMatrix();
554              renderer.setSize(newWidth, height);
555            });
556          }
557          
558          // Load a 3D model (PLY or OBJ) into the viewer
559          function loadModel(modelPath, resultId, format = 'ply') {
560            loadingText.style.display = 'block';
561            loadingText.textContent = 'Loading 3D model...';
562            
563            // Remove current mesh if exists
564            if (currentMesh) {
565              scene.remove(currentMesh);
566              if (currentMesh.geometry) currentMesh.geometry.dispose();
567              if (currentMesh.material) {
568                if (Array.isArray(currentMesh.material)) {
569                  currentMesh.material.forEach(m => m.dispose());
570                } else {
571                  currentMesh.material.dispose();
572                }
573              }
574              // For OBJ groups, dispose all children
575              if (currentMesh.children) {
576                currentMesh.traverse((child) => {
577                  if (child.geometry) child.geometry.dispose();
578                  if (child.material) {
579                    if (Array.isArray(child.material)) {
580                      child.material.forEach(m => m.dispose());
581                    } else {
582                      child.material.dispose();
583                    }
584                  }
585                });
586              }
587            }
588            
589            const loader = format === 'obj' ? new OBJLoader() : new PLYLoader();
590            
591            loader.load(
592              modelPath,
593              function (loadedObject) {
594                console.log(`Successfully loaded ${format.toUpperCase()} for result ${resultId}`);
595                
596                let mesh;
597                
598                if (format === 'obj') {
599                  // OBJLoader returns a Group/Object3D
600                  mesh = loadedObject;
601                  
602                  // Apply material to all meshes in the group
603                  mesh.traverse((child) => {
604                    if (child.isMesh) {
605                      child.geometry.computeVertexNormals();
606                      
607                      // Check if the geometry has vertex colors
608                      const hasVertexColors = child.geometry.attributes.color !== undefined;
609                      console.log(`OBJ mesh has vertex colors: ${hasVertexColors}`);
610                      
611                      child.material = new THREE.MeshStandardMaterial({
612                        color: hasVertexColors ? 0xffffff : 0x606060,  // Use white if vertex colors exist, darker gray otherwise
613                        flatShading: false,
614                        side: THREE.DoubleSide,
615                        vertexColors: hasVertexColors,
616                        roughness: 0.8,
617                        metalness: 0.0
618                      });
619                    }
620                  });
621                  
622                  // Center the OBJ by computing bounding box and offsetting position
623                  const box = new THREE.Box3().setFromObject(mesh);
624                  const center = new THREE.Vector3();
625                  box.getCenter(center);
626                  
627                  // Translate all child meshes to center the group
628                  mesh.traverse((child) => {
629                    if (child.isMesh) {
630                      child.geometry.translate(-center.x, -center.y, -center.z);
631                    }
632                  });
633                  
634                  // Scale the model
635                  const size = new THREE.Vector3();
636                  box.getSize(size);
637                  const maxDim = Math.max(size.x, size.y, size.z);
638                  const scale = 1.8 / maxDim;
639                  mesh.scale.set(scale, scale, scale);
640                  
641                  // OBJ files typically don't need rotation (unlike PLY files)
642                  // If your OBJ is upside down, uncomment the line below:
643                  // mesh.rotation.x = Math.PI;
644                  
645                } else {
646                  // PLYLoader returns a BufferGeometry
647                  const geometry = loadedObject;
648                  geometry.computeVertexNormals();
649                  
650                  const material = new THREE.MeshStandardMaterial({
651                    color: 0xaaaaaa,
652                    flatShading: false,
653                    side: THREE.DoubleSide,
654                    vertexColors: geometry.attributes.color ? true : false
655                  });
656                  
657                  mesh = new THREE.Mesh(geometry, material);
658                  
659                  // Center and scale the model (original PLY approach)
660                  geometry.computeBoundingBox();
661                  const center = new THREE.Vector3();
662                  geometry.boundingBox.getCenter(center);
663                  mesh.geometry.translate(-center.x, -center.y, -center.z);
664                  
665                  const size = new THREE.Vector3();
666                  geometry.boundingBox.getSize(size);
667                  const maxDim = Math.max(size.x, size.y, size.z);
668                  const scale = 1.8 / maxDim;
669                  mesh.scale.set(scale, scale, scale);
670                  
671                  // Fix upside-down orientation - rotate 180 degrees around X axis
672                  mesh.rotation.x = Math.PI;
673                }
674                
675                currentMesh = mesh;
676                scene.add(currentMesh);
677                loadingText.style.display = 'none';
678                
679                // Reset camera position and look at center
680                camera.position.set(0, 0, 2.5);
681                camera.lookAt(0, 0, 0);
682                controls.target.set(0, 0, 0);
683                controls.update();
684              },
685              function (xhr) {
686                const percentComplete = xhr.loaded / xhr.total * 100;
687                loadingText.textContent = `Loading: ${Math.round(percentComplete)}%`;
688              },
689              function (error) {
690                console.error(`Error loading ${format.toUpperCase()} for result ${resultId}:`, error);
691                loadingText.textContent = 'Error loading 3D model';
692                loadingText.style.fontSize = '0.9em';
693              }
694            );
695          }
696          
697          // Generate thumbnail grid
698          const thumbnailContainer = document.getElementById('thumbnail-container');
699          results.forEach((result, index) => {
700            const thumbDiv = document.createElement('div');
701            thumbDiv.style.cursor = 'pointer';
702            thumbDiv.style.transition = 'transform 0.2s, box-shadow 0.2s';
703            thumbDiv.style.minWidth = '150px';
704            thumbDiv.style.flexShrink = '0';
705            thumbDiv.className = index === 0 ? 'active-thumb' : '';
706            
707            thumbDiv.innerHTML = `
708        <img src="./static/results/${result.image}" 
709                   alt="Result ${result.id}" 
710                   style="width: 150px; height: 150px; object-fit: cover; border-radius: 8px; border: ${index === 0 ? '3px solid #3273dc' : '2px solid #ddd'}; display: block;">
711            `;
712            
713            thumbDiv.addEventListener('click', () => {
714              // Update active state
715              document.querySelectorAll('#thumbnail-container > div').forEach((div, idx) => {
716                const img = div.querySelector('img');
717                if (idx === index) {
718                  img.style.border = '3px solid #3273dc';
719                } else {
720                  img.style.border = '2px solid #ddd';
721                }
722              });
723              
724              const format = result.format || 'ply';
725              loadModel(`./static/results/${result.id}.${format}`, result.id, format);
726            });
727            
728            thumbDiv.addEventListener('mouseenter', () => {
729              if (!thumbDiv.classList.contains('active-thumb')) {
730                thumbDiv.style.transform = 'scale(1.05)';
731              }
732            });
733            
734            thumbDiv.addEventListener('mouseleave', () => {
735              thumbDiv.style.transform = 'scale(1)';
736            });
737            
738            thumbnailContainer.appendChild(thumbDiv);
739          });
740          
741          // Initialize viewer and load first model
742          initViewer();
743          loadModel('./static/results/01_0079_010.obj', '01_0079_010', 'obj');
744        </script>
744
745      </div>
746    </div>
747    
748    <!-- Subsection: Generation from Body Pose & Shape -->
749    <div class="columns is-centered has-text-centered" style="margin-top: 3rem;" id="gen-body-pose-shape">
750      <div class="column is-four-fifths">
751        <h3 class="title is-4">Generation from Body Pose & Shape</h3>
752        
753        <!-- Single 3D Viewer -->
754        <div style="margin-bottom: 2rem;">
755          <div id="shape-viewer" style="width: 100%; height: 500px; background-color: #f5f5f5; border-radius: 8px; border: 1px solid #ddd; position: relative;">
756            <div class="shape-loading-text" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #888; z-index: 10;">Loading 3D model...</div>
757          </div>
758        </div>
759        
760        <!-- Thumbnail Grid -->
761        <div id="shape-thumbnail-container" style="display: flex; gap: 1rem; margin-top: 2rem; overflow-x: auto; padding-bottom: 1rem;"></div>
762        
763        
763<script type="module">
764          import * as THREE from 'three';
765          import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
766          import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
767          import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
768          
769          // Define body shape editing results
770          const shapeResults = [
771            { id: "pose21", image: 'pose21.png', format: 'obj' },
772            { id: "pose22", image: 'pose22.png', format: 'obj' },
773            { id: "pose23", image: 'pose23.png', format: 'obj' },
774            { id: "shape5", image: 'shape5.png', format: 'obj' },
775            { id: "shape10", image: 'shape10.png', format: 'obj' },
776            { id: "shape12", image: 'shape12.png', format: 'obj' },
777          ];
778          
779          let shapeScene, shapeCamera, shapeRenderer, shapeControls, shapeCurrentMesh;
780          let shapeLoadingText;
781          
782          // Initialize the shape viewer
783          function initShapeViewer() {
784            const container = document.getElementById('shape-viewer');
785            shapeLoadingText = container.querySelector('.shape-loading-text');
786            const width = container.clientWidth;
787            const height = 500;
788            
789            // Scene setup
790            shapeScene = new THREE.Scene();
791            shapeScene.background = new THREE.Color(0xf5f5f5);
792            
793            // Camera setup
794            shapeCamera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
795            shapeCamera.position.set(0, 0, 2.5);
796            shapeCamera.lookAt(0, 0, 0);
797            
798            // Renderer setup
799            shapeRenderer = new THREE.WebGLRenderer({ antialias: true });
800            shapeRenderer.setSize(width, height);
801            shapeRenderer.setPixelRatio(window.devicePixelRatio);
802            container.appendChild(shapeRenderer.domElement);
803            
804            // Controls
805            shapeControls = new OrbitControls(shapeCamera, shapeRenderer.domElement);
806            shapeControls.enableDamping = true;
807            shapeControls.autoRotate = true;
808            shapeControls.autoRotateSpeed = 2.0;
809            
810            // Lighting
811            const ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
812            shapeScene.add(ambientLight);
813            
814            const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
815            directionalLight1.position.set(1, 1, 1);
816            shapeScene.add(directionalLight1);
817            
818            const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.2);
819            directionalLight2.position.set(-1, -1, -1);
820            shapeScene.add(directionalLight2);
821            
822            const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.0);
823            shapeScene.add(hemisphereLight);
824            
825            // Animation loop
826            function animate() {
827              requestAnimationFrame(animate);
828              shapeControls.update();
829              shapeRenderer.render(shapeScene, shapeCamera);
830            }
831            animate();
832            
833            // Handle window resize
834            window.addEventListener('resize', () => {
835              const newWidth = container.clientWidth;
836              shapeCamera.aspect = newWidth / height;
837              shapeCamera.updateProjectionMatrix();
838              shapeRenderer.setSize(newWidth, height);
839            });
840          }
841          
842          // Load a 3D model for shape results
843          function loadShapeModel(modelPath, resultId, format = 'ply') {
844            shapeLoadingText.style.display = 'block';
845            shapeLoadingText.textContent = 'Loading 3D model...';
846            
847            // Remove current mesh if exists
848            if (shapeCurrentMesh) {
849              shapeScene.remove(shapeCurrentMesh);
850              if (shapeCurrentMesh.geometry) shapeCurrentMesh.geometry.dispose();
851              if (shapeCurrentMesh.material) {
852                if (Array.isArray(shapeCurrentMesh.material)) {
853                  shapeCurrentMesh.material.forEach(m => m.dispose());
854                } else {
855                  shapeCurrentMesh.material.dispose();
856                }
857              }
858              if (shapeCurrentMesh.children) {
859                shapeCurrentMesh.traverse((child) => {
860                  if (child.geometry) child.geometry.dispose();
861                  if (child.material) {
862                    if (Array.isArray(child.material)) {
863                      child.material.forEach(m => m.dispose());
864                    } else {
865                      child.material.dispose();
866                    }
867                  }
868                });
869              }
870            }
871            
872            const loader = format === 'obj' ? new OBJLoader() : new PLYLoader();
873            
874            loader.load(
875              modelPath,
876              function (loadedObject) {
877                console.log(`Successfully loaded ${format.toUpperCase()} for shape result ${resultId}`);
878                
879                let mesh;
880                
881                if (format === 'obj') {
882                  mesh = loadedObject;
883                  
884                  mesh.traverse((child) => {
885                    if (child.isMesh) {
886                      child.geometry.computeVertexNormals();
887                      const hasVertexColors = child.geometry.attributes.color !== undefined;
888                      console.log(`OBJ mesh has vertex colors: ${hasVertexColors}`);
889                      
890                      child.material = new THREE.MeshStandardMaterial({
891                        color: hasVertexColors ? 0xffffff : 0x606060,
892                        flatShading: false,
893                        side: THREE.DoubleSide,
894                        vertexColors: hasVertexColors,
895                        roughness: 0.8,
896                        metalness: 0.0
897                      });
898                    }
899                  });
900                  
901                  const box = new THREE.Box3().setFromObject(mesh);
902                  const center = new THREE.Vector3();
903                  box.getCenter(center);
904                  
905                  mesh.traverse((child) => {
906                    if (child.isMesh) {
907                      child.geometry.translate(-center.x, -center.y, -center.z);
908                    }
909                  });
910                  
911                  const size = new THREE.Vector3();
912                  box.getSize(size);
913                  const maxDim = Math.max(size.x, size.y, size.z);
914                  const scale = 1.8 / maxDim;
915                  mesh.scale.set(scale, scale, scale);
916                  
917                } else {
918                  const geometry = loadedObject;
919                  geometry.computeVertexNormals();
920                  
921                  const material = new THREE.MeshStandardMaterial({
922                    color: 0xaaaaaa,
923                    flatShading: false,
924                    side: THREE.DoubleSide,
925                    vertexColors: geometry.attributes.color ? true : false
926                  });
927                  
928                  mesh = new THREE.Mesh(geometry, material);
929                  
930                  geometry.computeBoundingBox();
931                  const center = new THREE.Vector3();
932                  geometry.boundingBox.getCenter(center);
933                  mesh.geometry.translate(-center.x, -center.y, -center.z);
934                  
935                  const size = new THREE.Vector3();
936                  geometry.boundingBox.getSize(size);
937                  const maxDim = Math.max(size.x, size.y, size.z);
938                  const scale = 1.8 / maxDim;
939                  mesh.scale.set(scale, scale, scale);
940                  
941                  mesh.rotation.x = Math.PI;
942                }
943                
944                shapeCurrentMesh = mesh;
945                shapeScene.add(shapeCurrentMesh);
946                shapeLoadingText.style.display = 'none';
947                
948                shapeCamera.position.set(0, 0, 2.5);
949                shapeCamera.lookAt(0, 0, 0);
950                shapeControls.target.set(0, 0, 0);
951                shapeControls.update();
952              },
953              function (xhr) {
954                const percentComplete = xhr.loaded / xhr.total * 100;
955                shapeLoadingText.textContent = `Loading: ${Math.round(percentComplete)}%`;
956              },
957              function (error) {
958                console.error(`Error loading ${format.toUpperCase()} for shape result ${resultId}:`, error);
959                shapeLoadingText.textContent = 'Error loading 3D model';
960                shapeLoadingText.style.fontSize = '0.9em';
961              }
962            );
963          }
964          
965          // Generate thumbnail grid
966          const shapeThumbnailContainer = document.getElementById('shape-thumbnail-container');
967          shapeResults.forEach((result, index) => {
968            const thumbDiv = document.createElement('div');
969            thumbDiv.style.cursor = 'pointer';
970            thumbDiv.style.transition = 'transform 0.2s, box-shadow 0.2s';
971            thumbDiv.style.minWidth = '150px';
972            thumbDiv.style.flexShrink = '0';
973            thumbDiv.className = index === 0 ? 'active-thumb' : '';
974            
975            thumbDiv.innerHTML = `
976        <img src="./static/results/${result.image}" 
977                   alt="Result ${result.id}" 
978                   style="width: 150px; height: 150px; object-fit: cover; border-radius: 8px; border: ${index === 0 ? '3px solid #3273dc' : '2px solid #ddd'}; display: block;">
979            `;
980            
981            thumbDiv.addEventListener('click', () => {
982              // Update active state
983              document.querySelectorAll('#shape-thumbnail-container > div').forEach((div, idx) => {
984                const img = div.querySelector('img');
985                if (idx === index) {
986                  img.style.border = '3px solid #3273dc';
987                } else {
988                  img.style.border = '2px solid #ddd';
989                }
990              });
991              
992              const format = result.format || 'ply';
993              loadShapeModel(`./static/results/${result.id}.${format}`, result.id, format);
994            });
995            
996            thumbDiv.addEventListener('mouseenter', () => {
997              if (!thumbDiv.classList.contains('active-thumb')) {
998                thumbDiv.style.transform = 'scale(1.05)';
999              }
1000            });
1001            
1002            thumbDiv.addEventListener('mouseleave', () => {
1003              thumbDiv.style.transform = 'scale(1)';
1004            });
1005            
1006            shapeThumbnailContainer.appendChild(thumbDiv);
1007          });
1008          
1009          // Initialize viewer and load first model
1010          initShapeViewer();
1011          const firstFormat = shapeResults[0].format || 'ply';
1012          loadShapeModel(`./static/results/${shapeResults[0].id}.${firstFormat}`, shapeResults[0].id, firstFormat);
1013        </script>
1013
1014      </div>
1015    </div>
1016    
1017    <!-- Subsection: Editing from Text -->
1018    <div class="columns is-centered has-text-centered" style="margin-top: 3rem;" id="edit-text">
1019      <div class="column is-four-fifths">
1020        <h3 class="title is-4">Editing from Text</h3>
1021        
1022        <!-- Single 3D Viewer -->
1023        <div style="margin-bottom: 2rem;">
1024          <div id="edit-viewer" style="width: 100%; height: 500px; background-color: #f5f5f5; border-radius: 8px; border: 1px solid #ddd; position: relative;">
1025            <div class="edit-loading-text" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #888; z-index: 10;">Loading 3D model...</div>
1026          </div>
1027        </div>
1028        
1029        <!-- Tabs for Editing -->
1030        <div class="tabs is-centered is-boxed" style="margin-top: 2rem;">
1031          <ul id="edit-tabs-container"></ul>
1032        </div>
1033        
1034        
1034<script type="module">
1035          import * as THREE from 'three';
1036          import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
1037          import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
1038          import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
1039          
1040          // Define text editing results
1041          const editResults = [
1042            { id: 6, prompt: 'Blue jeans', file: 'bluejeans.obj', format: 'obj' },
1043            { id: 8, prompt: 'Short jeans', file: 'shortjeans.obj', format: 'obj' },
1044            { id: 12, prompt: 'Sunglasses', file: 'sunglasses.obj', format: 'obj' },
1045            { id: 7, prompt: 'Black jeans', file: 'blackjeans.obj', format: 'obj' },
1046            { id: 9, prompt: 'Red high heels', file: 'redheels.obj', format: 'obj' },
1047            { id: 10, prompt: 'Scarf', file: 'scarf.obj', format: 'obj' },
1048            { id: 11, prompt: 'Black stockings', file: 'blackstockings.obj', format: 'obj' }
1049          ];
1050          
1051          let editScene, editCamera, editRenderer, editControls, editCurrentMesh;
1052          let editLoadingText;
1053          
1054          // Initialize the edit viewer
1055          function initEditViewer() {
1056            const container = document.getElementById('edit-viewer');
1057            editLoadingText = container.querySelector('.edit-loading-text');
1058            const width = container.clientWidth;
1059            const height = 500;
1060            
1061            // Scene setup
1062            editScene = new THREE.Scene();
1063            editScene.background = new THREE.Color(0xf5f5f5);
1064            
1065            // Camera setup
1066            editCamera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
1067            editCamera.position.set(0, 0, 2.5);
1068            editCamera.lookAt(0, 0, 0);
1069            
1070            // Renderer setup
1071            editRenderer = new THREE.WebGLRenderer({ antialias: true });
1072            editRenderer.setSize(width, height);
1073            editRenderer.setPixelRatio(window.devicePixelRatio);
1074            container.appendChild(editRenderer.domElement);
1075            
1076            // Controls
1077            editControls = new OrbitControls(editCamera, editRenderer.domElement);
1078            editControls.enableDamping = true;
1079            editControls.autoRotate = true;
1080            editControls.autoRotateSpeed = 2.0;
1081            
1082            // Lighting
1083            const ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
1084            editScene.add(ambientLight);
1085            
1086            const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
1087            directionalLight1.position.set(1, 1, 1);
1088            editScene.add(directionalLight1);
1089            
1090            const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.2);
1091            directionalLight2.position.set(-1, -1, -1);
1092            editScene.add(directionalLight2);
1093            
1094            const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 1.0);
1095            editScene.add(hemisphereLight);
1096            
1097            // Animation loop
1098            function animate() {
1099              requestAnimationFrame(animate);
1100              editControls.update();
1101              editRenderer.render(editScene, editCamera);
1102            }
1103            animate();
1104            
1105            // Handle window resize
1106            window.addEventListener('resize', () => {
1107              const newWidth = container.clientWidth;
1108              editCamera.aspect = newWidth / height;
1109              editCamera.updateProjectionMatrix();
1110              editRenderer.setSize(newWidth, height);
1111            });
1112          }
1113          
1114          // Load a 3D model for edit results
1115          function loadEditModel(modelPath, resultId, format = 'ply') {
1116            editLoadingText.style.display = 'block';
1117            editLoadingText.textContent = 'Loading 3D model...';
1118            
1119            // Remove current mesh if exists
1120            if (editCurrentMesh) {
1121              editScene.remove(editCurrentMesh);
1122              if (editCurrentMesh.geometry) editCurrentMesh.geometry.dispose();
1123              if (editCurrentMesh.material) {
1124                if (Array.isArray(editCurrentMesh.material)) {
1125                  editCurrentMesh.material.forEach(m => m.dispose());
1126                } else {
1127                  editCurrentMesh.material.dispose();
1128                }
1129              }
1130              if (editCurrentMesh.children) {
1131                editCurrentMesh.traverse((child) => {
1132                  if (child.geometry) child.geometry.dispose();
1133                  if (child.material) {
1134                    if (Array.isArray(child.material)) {
1135                      child.material.forEach(m => m.dispose());
1136                    } else {
1137                      child.material.dispose();
1138                    }
1139                  }
1140                });
1141              }
1142            }
1143            
1144            const loader = format === 'obj' ? new OBJLoader() : new PLYLoader();
1145            
1146            loader.load(
1147              modelPath,
1148              function (loadedObject) {
1149                console.log(`Successfully loaded ${format.toUpperCase()} for edit result ${resultId}`);
1150                
1151                let mesh;
1152                
1153                if (format === 'obj') {
1154                  mesh = loadedObject;
1155                  
1156                  mesh.traverse((child) => {
1157                    if (child.isMesh) {
1158                      child.geometry.computeVertexNormals();
1159                      const hasVertexColors = child.geometry.attributes.color !== undefined;
1160                      
1161                      child.material = new THREE.MeshStandardMaterial({
1162                        color: hasVertexColors ? 0xffffff : 0x606060,
1163                        flatShading: false,
1164                        side: THREE.DoubleSide,
1165                        vertexColors: hasVertexColors,
1166                        roughness: 0.8,
1167                        metalness: 0.0
1168                      });
1169                    }
1170                  });
1171                  
1172                  const box = new THREE.Box3().setFromObject(mesh);
1173                  const center = new THREE.Vector3();
1174                  box.getCenter(center);
1175                  
1176                  mesh.traverse((child) => {
1177                    if (child.isMesh) {
1178                      child.geometry.translate(-center.x, -center.y, -center.z);
1179                    }
1180                  });
1181                  
1182                  const size = new THREE.Vector3();
1183                  box.getSize(size);
1184                  const maxDim = Math.max(size.x, size.y, size.z);
1185                  const scale = 1.8 / maxDim;
1186                  mesh.scale.set(scale, scale, scale);
1187                  
1188                } else {
1189                  const geometry = loadedObject;
1190                  geometry.computeVertexNormals();
1191                  
1192                  const material = new THREE.MeshStandardMaterial({
1193                    color: 0xaaaaaa,
1194                    flatShading: false,
1195                    side: THREE.DoubleSide,
1196                    vertexColors: geometry.attributes.color ? true : false
1197                  });
1198                  
1199                  mesh = new THREE.Mesh(geometry, material);
1200                  
1201                  geometry.computeBoundingBox();
1202                  const center = new THREE.Vector3();
1203                  geometry.boundingBox.getCenter(center);
1204                  mesh.geometry.translate(-center.x, -center.y, -center.z);
1205                  
1206                  const size = new THREE.Vector3();
1207                  geometry.boundingBox.getSize(size);
1208                  const maxDim = Math.max(size.x, size.y, size.z);
1209                  const scale = 1.8 / maxDim;
1210                  mesh.scale.set(scale, scale, scale);
1211                  
1212                  mesh.rotation.x = Math.PI;
1213                }
1214                
1215                editCurrentMesh = mesh;
1216                editScene.add(editCurrentMesh);
1217                editLoadingText.style.display = 'none';
1218                
1219                editCamera.position.set(0, 0, 2.5);
1220                editCamera.lookAt(0, 0, 0);
1221                editControls.target.set(0, 0, 0);
1222                editControls.update();
1223              },
1224              function (xhr) {
1225                const percentComplete = xhr.loaded / xhr.total * 100;
1226                editLoadingText.textContent = `Loading: ${Math.round(percentComplete)}%`;
1227              },
1228              function (error) {
1229                console.error(`Error loading ${format.toUpperCase()} for edit result ${resultId}:`, error);
1230                editLoadingText.textContent = 'Error loading 3D model';
1231                editLoadingText.style.fontSize = '0.9em';
1232              }
1233            );
1234          }
1235          
1236          // Generate tabs
1237          const editTabsContainer = document.getElementById('edit-tabs-container');
1238          editResults.forEach((result, index) => {
1239            const li = document.createElement('li');
1240            if (index === 0) li.className = 'is-active';
1241            
1242            li.innerHTML = `<a>${result.prompt}</a>`;
1243            
1244            li.addEventListener('click', () => {
1245              // Update active state
1246              document.querySelectorAll('#edit-tabs-container li').forEach((tab, idx) => {
1247                if (idx === index) {
1248                  tab.className = 'is-active';
1249                } else {
1250                  tab.className = '';
1251                }
1252              });
1253              
1254              const format = result.format || 'ply';
1255              loadEditModel(`./static/results/${result.file}`, result.prompt, format);
1256            });
1257            
1258            editTabsContainer.appendChild(li);
1259          });
1260          
1261          // Initialize viewer and load first model
1262          initEditViewer();
1263          loadEditModel(`./static/results/${editResults[0].file}`, editResults[0].prompt, editResults[0].format);
1264        </script>
1264
1265      </div>
1266    </div>
1267  </div>
1268</section>
1269
1270
1271<div class="container is-max-desktop">
1272    <div class="columns is-centered">
1273      <!-- Visual Effects. -->
1274      <div class="column">
1275        <div class="content">
1276          <h2 class="title is-3">Method Overview</h2>
1277          <p>
1278            Watch our 5-minute voice-over explanation to understand how InfiniHuman works. 
1279            This video provides a comprehensive overview of our approach, from data generation to the final controllable 3D human creation pipeline.
1280          </p>
1281          <video id="dollyzoom" autoplay controls muted loop playsinline height="100%">
1282            <source src="./static/videos/infinihuman_5min.mp4"
1283                    type="video/mp4">
1284          </video>
1285        </div>
1286      </div>
1287      <!--/ Visual Effects. -->
1288      
1289    </div>
1290    <!--/ Matting. -->
1291  </div>
1292  
1293</section>
1294
1295
1296<section class="section">
1297  <div class="container is-max-desktop">
1298
1299    <!-- Concurrent Work. -->
1300    <div class="columns is-centered">
1301      <div class="column is-full-width">
1302        <h2 class="title is-3">Acknowledgement</h2>
1303
1304        <div class="content has-text-justified">
1305          <p>
1306            We appreciate our friends and colleagues in RVH for their feedback to improve the work.
1307            This work is made possible by funding from the Carl Zeiss Foundation. 
1308This work is also funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) - 409792180 (EmmyNoether Programme, project: Real Virtual Humans) and the German Federal Ministry of Education and Research (BMBF): Tübingen AI Center, FKZ: 01IS18039A. 
1309The authors thank the International Max Planck Research School for Intelligent Systems (IMPRS-IS) for supporting Y.Xue.
1310G. Pons-Moll is a member of the Machine Learning Cluster of Excellence, EXC number 2064/1 – Project number 390727645. 
1311          </p>
1312        </div>
1313      </div>
1314    </div>
1315    <!--/ Concurrent Work. -->
1316
1317  </div>
1318<br>
1319<br>
1320<center>
1321  <div class="imagesrow">
1322    <img class="row_img" src="static/images/Carl-Zeiss-Stiftung_Logo.png" alt="Carl-Zeiss-Stiftung" style="max-width:5%;">
1323    <img class="row_img" src="static/images/ai_center_3.png" alt="Tübingen AI Center" style="max-width:7%;">
1324    <img class="row_img" src="static/images/Tuebingen_logo_scaled.png" alt="University of Tübingen" style="max-width:7%;">
1325    <img class="row_img" src="static/images/IMPRS-IS.png" alt="IMPRS" style="max-width:5%;">
1326    <img class="row_img" src="static/images/mpi-logo-new.svg" alt="mpi-inf" style="max-width:8%;">
1327  </div>
1328</center>
1329</section>
1330
1331
1332<section class="section" id="BibTeX">
1333  <div class="container is-max-desktop content">
1334    <h2 class="title">BibTeX</h2>
1335    <pre><code>@article{xue2025infinihuman,
1336  author    = {Xue, Yuxuan and Xie, Xianghui and Kostyrko, Margaret and Pons-Moll, Gerard},
1337  title     = {InfiniHuman: Infinite 3D Human Creation with Precise Control},
1338  booktitle    = {SIGGRAPH Asia 2025 Conference Papers},
1339  year      = {2025},
1340}</code></pre>
1341  </div>
1342</section>
1343
1344<footer class="footer">
1345  <div class="container">
1346    <div class="content has-text-centered">
1347      <a class="icon-link"
1348         href="./paper/infinihuman.pdf">
1349        <i class="fas fa-file-pdf"></i>
1350      </a>
1351      <a class="icon-link" href="https://github.com/yuxuansnow/InfiniHuman" class="external-link" disabled>
1352        <i class="fab fa-github"></i>
1353      </a>
1354    </div>
1355    <div class="columns is-centered">
1356      <div class="column is-8">
1357        <div class="content">
1358          <p>
1359            The website template is from <a
1360              href="https://github.com/nerfies/nerfies.github.io">Nerfies</a> and <a
1361              href="https://yuxuan-xue.com/nsf">NSF</a>.
1362          </p>
1363        </div>
1364      </div>
1365    </div>
1366  </div>
1367</footer>
1368
1369</body>
1370</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.