You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							476 lines
						
					
					
						
							29 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							476 lines
						
					
					
						
							29 KiB
						
					
					
				
								<!DOCTYPE html><html lang="ru"><head>
							 | 
						|
								    <meta charset="utf-8">
							 | 
						|
								    <title>Оптимизация большого количества анимированных объектов</title>
							 | 
						|
								    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
							 | 
						|
								    <meta name="twitter:card" content="summary_large_image">
							 | 
						|
								    <meta name="twitter:site" content="@threejs">
							 | 
						|
								    <meta name="twitter:title" content="Three.js – Оптимизация большого количества анимированных объектов">
							 | 
						|
								    <meta property="og:image" content="https://threejs.org/files/share.png">
							 | 
						|
								    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
							 | 
						|
								    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
							 | 
						|
								
							 | 
						|
								    <link rel="stylesheet" href="/manual/resources/lesson.css">
							 | 
						|
								    <link rel="stylesheet" href="/manual/resources/lang.css">
							 | 
						|
								<!-- Import maps polyfill -->
							 | 
						|
								<!-- Remove this when import maps will be widely supported -->
							 | 
						|
								<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
							 | 
						|
								
							 | 
						|
								<script type="importmap">
							 | 
						|
								{
							 | 
						|
								  "imports": {
							 | 
						|
								    "three": "../../build/three.module.js"
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</script>
							 | 
						|
								  </head>
							 | 
						|
								  <body>
							 | 
						|
								    <div class="container">
							 | 
						|
								      <div class="lesson-title">
							 | 
						|
								        <h1>Оптимизация большого количества анимированных объектов</h1>
							 | 
						|
								      </div>
							 | 
						|
								      <div class="lesson">
							 | 
						|
								        <div class="lesson-main">
							 | 
						|
								          <p></p>
							 | 
						|
								<p>Эта статья является продолжением  <a href="optimize-lots-of-objects.html">статьи об оптимизации множества объектов
							 | 
						|
								</a>. Если вы еще не прочитали это, пожалуйста, прочитайте его, прежде чем продолжить.  </p>
							 | 
						|
								<p>В предыдущей статье мы объединили около 19000 кубов в одну геометрию. Это имело преимущество, заключающееся в том, 
							 | 
						|
								что оно оптимизировало наш рисунок из 19000 кубов, но имело тот недостаток, что затрудняло перемещение любого отдельного куба. </p>
							 | 
						|
								<p>В зависимости от того, чего мы пытаемся достичь, существуют разные решения. В этом случае давайте наметим несколько наборов данных и анимируем между наборами. </p>
							 | 
						|
								<p>Первое, что нам нужно сделать, это получить несколько наборов данных. 
							 | 
						|
								В идеале мы бы, вероятно, предварительно обрабатывали данные в автономном режиме, 
							 | 
						|
								но в этом случае давайте загрузим 2 набора данных и сгенерируем еще 2 </p>
							 | 
						|
								<p>Вот наш старый код загрузки </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">loadFile('resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc')
							 | 
						|
								  .then(parseData)
							 | 
						|
								  .then(addBoxes)
							 | 
						|
								  .then(render);
							 | 
						|
								</pre>
							 | 
						|
								<p>Давайте изменим это на что-то вроде этого </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadData(info) {
							 | 
						|
								  const text = await loadFile(info.url);
							 | 
						|
								  info.file = parseData(text);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								async function loadAll() {
							 | 
						|
								  const fileInfos = [
							 | 
						|
								    {name: 'men',   hueRange: [0.7, 0.3], url: 'resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc' },
							 | 
						|
								    {name: 'women', hueRange: [0.9, 1.1], url: 'resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014ft_2010_cntm_1_deg.asc' },
							 | 
						|
								  ];
							 | 
						|
								
							 | 
						|
								  await Promise.all(fileInfos.map(loadData));
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								}
							 | 
						|
								loadAll();
							 | 
						|
								</pre>
							 | 
						|
								<p>Приведенный выше код загрузит все файлы в <code class="notranslate" translate="no">fileInfos</code>, и после этого каждый объект в <code class="notranslate" translate="no">fileInfos</code>
							 | 
						|
								будет иметь свойство <code class="notranslate" translate="no">file</code> с загруженным файлом. <code class="notranslate" translate="no">name</code> и <code class="notranslate" translate="no">hueRange</code> мы будем использовать позже. 
							 | 
						|
								<code class="notranslate" translate="no">name</code> будет для поля пользовательского интерфейса. <code class="notranslate" translate="no">hueRange</code> будет использоваться для выбора диапазона оттенков для отображения. </p>
							 | 
						|
								<p>Два файла выше, по-видимому, представляют собой количество мужчин на область и число женщин на область по состоянию на 2010 год. Обратите внимание,
							 | 
						|
								я не знаю, верны ли эти данные, но на самом деле это не важно. Важная часть показывает разные наборы данных. </p>
							 | 
						|
								<p>Давайте сгенерируем еще 2 набора данных. Одним из них являются места, 
							 | 
						|
								где число мужчин превышает число женщин, и наоборот, места, где число женщин превышает число мужчин. </p>
							 | 
						|
								<p>Первым делом давайте напишем функцию, которая с помощью двухмерного массива массивов, 
							 | 
						|
								как мы делали раньше, отобразит ее, чтобы сгенерировать новый двумерный массив массивов. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function mapValues(data, fn) {
							 | 
						|
								  return data.map((row, rowNdx) => {
							 | 
						|
								    return row.map((value, colNdx) => {
							 | 
						|
								      return fn(value, rowNdx, colNdx);
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Как и обычная функция <code class="notranslate" translate="no">Array.map</code>, функция <code class="notranslate" translate="no">mapValues</code> вызывает функцию fn для каждого значения в массиве массивов. 
							 | 
						|
								Он передает ему значение, а также индексы строки и столбца. </p>
							 | 
						|
								<p>Теперь давайте создадим некоторый код для генерации нового файла, который сравнивает 2 файла. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeDiffFile(baseFile, otherFile, compareFn) {
							 | 
						|
								  let min;
							 | 
						|
								  let max;
							 | 
						|
								  const baseData = baseFile.data;
							 | 
						|
								  const otherData = otherFile.data;
							 | 
						|
								  const data = mapValues(baseData, (base, rowNdx, colNdx) => {
							 | 
						|
								    const other = otherData[rowNdx][colNdx];
							 | 
						|
								      if (base === undefined || other === undefined) {
							 | 
						|
								        return undefined;
							 | 
						|
								      }
							 | 
						|
								      const value = compareFn(base, other);
							 | 
						|
								      min = Math.min(min === undefined ? value : min, value);
							 | 
						|
								      max = Math.max(max === undefined ? value : max, value);
							 | 
						|
								      return value;
							 | 
						|
								  });
							 | 
						|
								  // make a copy of baseFile and replace min, max, and data
							 | 
						|
								  // with the new data
							 | 
						|
								  return {...baseFile, min, max, data};
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Приведенный выше код использует <code class="notranslate" translate="no">mapValues</code> для генерации нового набора данных, 
							 | 
						|
								который представляет собой сравнение на основе переданной функции <code class="notranslate" translate="no">CompareFn</code>. 
							 | 
						|
								Он также отслеживает минимальные и максимальные результаты сравнения. Наконец, 
							 | 
						|
								он создает новый файл со всеми теми же свойствами, что и <code class="notranslate" translate="no">baseFile</code>, за исключением новых <code class="notranslate" translate="no">min</code>, <code class="notranslate" translate="no">max</code> и <code class="notranslate" translate="no">data</code>. </p>
							 | 
						|
								<p>Тогда давайте использовать это, чтобы сделать 2 новых набора данных </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						|
								  const menInfo = fileInfos[0];
							 | 
						|
								  const womenInfo = fileInfos[1];
							 | 
						|
								  const menFile = menInfo.file;
							 | 
						|
								  const womenFile = womenInfo.file;
							 | 
						|
								
							 | 
						|
								  function amountGreaterThan(a, b) {
							 | 
						|
								    return Math.max(a - b, 0);
							 | 
						|
								  }
							 | 
						|
								  fileInfos.push({
							 | 
						|
								    name: '>50%men',
							 | 
						|
								    hueRange: [0.6, 1.1],
							 | 
						|
								    file: makeDiffFile(menFile, womenFile, (men, women) => {
							 | 
						|
								      return amountGreaterThan(men, women);
							 | 
						|
								    }),
							 | 
						|
								  });
							 | 
						|
								  fileInfos.push({
							 | 
						|
								    name: '>50% women', 
							 | 
						|
								    hueRange: [0.0, 0.4],
							 | 
						|
								    file: makeDiffFile(womenFile, menFile, (women, men) => {
							 | 
						|
								      return amountGreaterThan(women, men);
							 | 
						|
								    }),
							 | 
						|
								  });
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Теперь давайте сгенерируем пользовательский интерфейс для выбора между этими наборами данных. Для начала нам нужен HTML-интерфейс </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
							 | 
						|
								  <canvas id="c"></canvas>
							 | 
						|
								+  <div id="ui"></div>
							 | 
						|
								</body>
							 | 
						|
								</pre>
							 | 
						|
								<p>и немного CSS, чтобы он появился в верхней левой области </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#ui {
							 | 
						|
								  position: absolute;
							 | 
						|
								  left: 1em;
							 | 
						|
								  top: 1em;
							 | 
						|
								}
							 | 
						|
								#ui>div {
							 | 
						|
								  font-size: 20pt;
							 | 
						|
								  padding: 1em;
							 | 
						|
								  display: inline-block;
							 | 
						|
								}
							 | 
						|
								#ui>div.selected {
							 | 
						|
								  color: red;
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Затем мы можем просмотреть каждый файл и сгенерировать набор объединенных блоков
							 | 
						|
								для набора данных и элемент, который при наведении курсора отобразит этот набор и скроет все остальные.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// show the selected data, hide the rest
							 | 
						|
								function showFileInfo(fileInfos, fileInfo) {
							 | 
						|
								  fileInfos.forEach((info) => {
							 | 
						|
								    const visible = fileInfo === info;
							 | 
						|
								    info.root.visible = visible;
							 | 
						|
								    info.elem.className = visible ? 'selected' : '';
							 | 
						|
								  });
							 | 
						|
								  requestRenderIfNotRequested();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								const uiElem = document.querySelector('#ui');
							 | 
						|
								fileInfos.forEach((info) => {
							 | 
						|
								  const boxes = addBoxes(info.file, info.hueRange);
							 | 
						|
								  info.root = boxes;
							 | 
						|
								  const div = document.createElement('div');
							 | 
						|
								  info.elem = div;
							 | 
						|
								  div.textContent = info.name;
							 | 
						|
								  uiElem.appendChild(div);
							 | 
						|
								  div.addEventListener('mouseover', () => {
							 | 
						|
								    showFileInfo(fileInfos, info);
							 | 
						|
								  });
							 | 
						|
								});
							 | 
						|
								// show the first set of data
							 | 
						|
								showFileInfo(fileInfos, fileInfos[0]);
							 | 
						|
								</pre>
							 | 
						|
								<p>Еще одно изменение, которое нам нужно из предыдущего примера, заключается в том, что мы должны заставить <code class="notranslate" translate="no">addBoxes</code> принимать <code class="notranslate" translate="no">hueRange</code> </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function addBoxes(file) {
							 | 
						|
								+function addBoxes(file, hueRange) {
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								
							 | 
						|
								    // compute a color
							 | 
						|
								-    const hue = THREE.MathUtils.lerp(0.7, 0.3, amount);
							 | 
						|
								+    const hue = THREE.MathUtils.lerp(...hueRange, amount);
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								</pre>
							 | 
						|
								<p>и с этим мы должны быть в состоянии показать 4 набора данных. Наведите указатель мыши на ярлыки или коснитесь их, чтобы переключать наборы </p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-multiple-data-sets.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/lots-of-objects-multiple-data-sets.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Обратите внимание, что есть несколько странных точек данных, которые действительно выделяются. 
							 | 
						|
								Интересно, что с ними? ??! В любом случае, как мы анимируем между этими 4 наборами данных. </p>
							 | 
						|
								<p>Много идей. </p>
							 | 
						|
								<ul>
							 | 
						|
								<li><p>Просто исчезните между ними, используя <a href="/docs/#api/en/materials/Material.opacity"><code class="notranslate" translate="no">Material.opacity</code></a> </p>
							 | 
						|
								<p>Проблема с этим решением заключается в том, что кубы полностью перекрываются, 
							 | 
						|
								что означает, что возникнут проблемы z-борьбы. Возможно, мы могли бы исправить это, 
							 | 
						|
								изменив функцию глубины и используя смешивание. Вероятно, мы должны изучить это. </p>
							 | 
						|
								</li>
							 | 
						|
								</ul>
							 | 
						|
								<ul>
							 | 
						|
								<li><p>Увеличьте набор, который мы хотим видеть, и уменьшите другие наборы. </p>
							 | 
						|
								<p>Поскольку все коробки имеют свое происхождение в центре планеты, если мы масштабируем их ниже 1,0, они погрузятся в планету. 
							 | 
						|
								Сначала это звучит как хорошая идея, но проблема в том, что все поля низкой высоты исчезнут почти сразу и не будут заменены, 
							 | 
						|
								пока новый набор данных не масштабируется до 1,0. 
							 | 
						|
								Это делает переход не очень приятным. Мы могли бы исправить это с помощью необычного шейдера. </p>
							 | 
						|
								</li>
							 | 
						|
								<li><p>Используйте Morphtargets </p>
							 | 
						|
								<p>Morphtargets - это способ, которым мы предоставляем несколько значений 
							 | 
						|
								для каждой вершины в геометрии и морф или lerp (линейная интерполяция) 
							 | 
						|
								между ними. Morphtargets чаще всего используются для лицевой анимации 3D персонажей, но это не единственное их использование. </p>
							 | 
						|
								</li>
							 | 
						|
								</ul>
							 | 
						|
								<p>Давайте попробуем morphtargets. </p>
							 | 
						|
								<p>Мы по-прежнему создадим геометрию для каждого набора данных, 
							 | 
						|
								но затем мы извлечем атрибут позиции из каждого из них и будем использовать их как морфтинги. </p>
							 | 
						|
								<p>Сначала давайте изменим <code class="notranslate" translate="no">addBoxes</code>, чтобы просто создать и вернуть объединенную геометрию. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function addBoxes(file, hueRange) {
							 | 
						|
								+function makeBoxes(file, hueRange) {
							 | 
						|
								  const {min, max, data} = file;
							 | 
						|
								  const range = max - min;
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								
							 | 
						|
								-  const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(
							 | 
						|
								-      geometries, false);
							 | 
						|
								-  const material = new THREE.MeshBasicMaterial({
							 | 
						|
								-    vertexColors: true,
							 | 
						|
								-  });
							 | 
						|
								-  const mesh = new THREE.Mesh(mergedGeometry, material);
							 | 
						|
								-  scene.add(mesh);
							 | 
						|
								-  return mesh;
							 | 
						|
								+  return BufferGeometryUtils.mergeBufferGeometries(
							 | 
						|
								+     geometries, false);
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Здесь есть еще одна вещь, которую нам нужно сделать. Morphtargets требуются, чтобы у всех было точно одинаковое количество вершин.
							 | 
						|
								Вершина # 123 в одной цели должна иметь соответствующую вершину # 123 во всех других целях. Но, поскольку сейчас 
							 | 
						|
								разные наборы данных могут иметь некоторые точки данных без данных, поэтому для этой точки не будет сгенерировано ни одного блока, 
							 | 
						|
								что означало бы отсутствие соответствующих вершин для другого набора. Итак, нам нужно проверить все наборы данных и либо всегда генерировать что-либо, если
							 | 
						|
								в каком-либо наборе есть данные, либо ничего не генерировать, если в каком-либо наборе отсутствуют данные. Давайте сделаем последнее. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function dataMissingInAnySet(fileInfos, latNdx, lonNdx) {
							 | 
						|
								+  for (const fileInfo of fileInfos) {
							 | 
						|
								+    if (fileInfo.file.data[latNdx][lonNdx] === undefined) {
							 | 
						|
								+      return true;
							 | 
						|
								+    }
							 | 
						|
								+  }
							 | 
						|
								+  return false;
							 | 
						|
								+}
							 | 
						|
								
							 | 
						|
								-function makeBoxes(file, hueRange) {
							 | 
						|
								+function makeBoxes(file, hueRange, fileInfos) {
							 | 
						|
								  const {min, max, data} = file;
							 | 
						|
								  const range = max - min;
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								
							 | 
						|
								  const geometries = [];
							 | 
						|
								  data.forEach((row, latNdx) => {
							 | 
						|
								    row.forEach((value, lonNdx) => {
							 | 
						|
								+      if (dataMissingInAnySet(fileInfos, latNdx, lonNdx)) {
							 | 
						|
								+        return;
							 | 
						|
								+      }
							 | 
						|
								      const amount = (value - min) / range;
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								</pre>
							 | 
						|
								<p>Теперь мы изменим код, который вызывал <code class="notranslate" translate="no">addBoxes</code>, для использования <code class="notranslate" translate="no">makeBoxes</code> и установки morphtargets. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+// make geometry for each data set
							 | 
						|
								+const geometries = fileInfos.map((info) => {
							 | 
						|
								+  return makeBoxes(info.file, info.hueRange, fileInfos);
							 | 
						|
								+});
							 | 
						|
								+
							 | 
						|
								+// use the first geometry as the base
							 | 
						|
								+// and add all the geometries as morphtargets
							 | 
						|
								+const baseGeometry = geometries[0];
							 | 
						|
								+baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) => {
							 | 
						|
								+  const attribute = geometry.getAttribute('position');
							 | 
						|
								+  const name = `target${ndx}`;
							 | 
						|
								+  attribute.name = name;
							 | 
						|
								+  return attribute;
							 | 
						|
								+});
							 | 
						|
								+baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) => {
							 | 
						|
								+  const attribute = geometry.getAttribute('color');
							 | 
						|
								+  const name = `target${ndx}`;
							 | 
						|
								+  attribute.name = name;
							 | 
						|
								+  return attribute;
							 | 
						|
								+});
							 | 
						|
								+const material = new THREE.MeshBasicMaterial({
							 | 
						|
								+  vertexColors: true,
							 | 
						|
								+});
							 | 
						|
								+const mesh = new THREE.Mesh(baseGeometry, material);
							 | 
						|
								+scene.add(mesh);
							 | 
						|
								
							 | 
						|
								const uiElem = document.querySelector('#ui');
							 | 
						|
								fileInfos.forEach((info) => {
							 | 
						|
								-  const boxes = addBoxes(info.file, info.hueRange);
							 | 
						|
								-  info.root = boxes;
							 | 
						|
								  const div = document.createElement('div');
							 | 
						|
								  info.elem = div;
							 | 
						|
								  div.textContent = info.name;
							 | 
						|
								  uiElem.appendChild(div);
							 | 
						|
								  function show() {
							 | 
						|
								    showFileInfo(fileInfos, info);
							 | 
						|
								  }
							 | 
						|
								  div.addEventListener('mouseover', show);
							 | 
						|
								  div.addEventListener('touchstart', show);
							 | 
						|
								});
							 | 
						|
								// show the first set of data
							 | 
						|
								showFileInfo(fileInfos, fileInfos[0]);
							 | 
						|
								</pre>
							 | 
						|
								<p>Выше мы создаем геометрию для каждого набора данных, 
							 | 
						|
								используем первый в качестве базы, затем получаем атрибут 
							 | 
						|
								позиции из каждой геометрии и добавляем его в качестве морфтинга к базовой геометрии для позиции. </p>
							 | 
						|
								<p>Теперь нам нужно изменить способ отображения и скрытия различных наборов данных. 
							 | 
						|
								Вместо того, чтобы показывать или скрывать меш, нам нужно изменить влияние морфтинга. Для набора данных, 
							 | 
						|
								который мы хотим видеть, нам нужно иметь влияние 1, а для всех тех, которые мы не хотим видеть, нам нужно иметь влияние 0. </p>
							 | 
						|
								<p>Мы могли бы просто установить их в 0 или 1 напрямую, но если бы мы это сделали, мы бы не увидели никакой анимации,
							 | 
						|
								она просто щелкала бы, что не отличалось бы от того, что у нас уже есть. Мы также могли бы написать некоторый пользовательский анимационный код, который был бы легок, 
							 | 
						|
								но поскольку оригинальный глобус webgl использует
							 | 
						|
								<a href="https://github.com/tweenjs/tween.js/">библиотеку анимации</a> давайте используем тот же самый здесь.</p>
							 | 
						|
								<p>Нам нужно включить библиотеку </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
							 | 
						|
								import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
							 | 
						|
								import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
							 | 
						|
								+import {TWEEN} from 'three/addons/libs/tween.min.js';
							 | 
						|
								</pre>
							 | 
						|
								<p>А затем создайте <code class="notranslate" translate="no">Tween</code> чтобы оживить влияние.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// show the selected data, hide the rest
							 | 
						|
								function showFileInfo(fileInfos, fileInfo) {
							 | 
						|
								  fileInfos.forEach((info) => {
							 | 
						|
								    const visible = fileInfo === info;
							 | 
						|
								-    info.root.visible = visible;
							 | 
						|
								    info.elem.className = visible ? 'selected' : '';
							 | 
						|
								+    const targets = {};
							 | 
						|
								+    fileInfos.forEach((info, i) => {
							 | 
						|
								+      targets[i] = info === fileInfo ? 1 : 0;
							 | 
						|
								+    });
							 | 
						|
								+    const durationInMs = 1000;
							 | 
						|
								+    new TWEEN.Tween(mesh.morphTargetInfluences)
							 | 
						|
								+      .to(targets, durationInMs)
							 | 
						|
								+      .start();
							 | 
						|
								  });
							 | 
						|
								  requestRenderIfNotRequested();
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Мы также предполагаем вызывать TWEEN.update каждый кадр в нашем цикле рендеринга, но это указывает на проблему. 
							 | 
						|
								"tween.js" предназначен для непрерывного рендеринга, но мы делаем <a href="rendering-on-demand.html">рендеринг по требованию </a>. 
							 | 
						|
								Мы могли бы переключиться на непрерывный рендеринг, но иногда приятно рендерить только по требованию, так как он перестает использовать 
							 | 
						|
								силу пользователя, когда ничего не происходит, поэтому давайте посмотрим, сможем ли мы сделать его анимированным по запросу. </p>
							 | 
						|
								<p>Мы сделаем <code class="notranslate" translate="no">TweenManager</code>, чтобы помочь. Мы будем использовать его для создания 
							 | 
						|
								<code class="notranslate" translate="no">Tweens</code> и отслеживания их. Он будет иметь метод <code class="notranslate" translate="no">update</code>, который будет 
							 | 
						|
								возвращать true, если нам нужно будет вызвать его снова, и false, если все анимации завершены. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class TweenManger {
							 | 
						|
								  constructor() {
							 | 
						|
								    this.numTweensRunning = 0;
							 | 
						|
								  }
							 | 
						|
								  _handleComplete() {
							 | 
						|
								    --this.numTweensRunning;
							 | 
						|
								    console.assert(this.numTweensRunning >= 0);
							 | 
						|
								  }
							 | 
						|
								  createTween(targetObject) {
							 | 
						|
								    const self = this;
							 | 
						|
								    ++this.numTweensRunning;
							 | 
						|
								    let userCompleteFn = () => {};
							 | 
						|
								    // create a new tween and install our own onComplete callback
							 | 
						|
								    const tween = new TWEEN.Tween(targetObject).onComplete(function(...args) {
							 | 
						|
								      self._handleComplete();
							 | 
						|
								      userCompleteFn.call(this, ...args);
							 | 
						|
								    });
							 | 
						|
								    // replace the tween's onComplete function with our own
							 | 
						|
								    // so we can call the user's callback if they supply one.
							 | 
						|
								    tween.onComplete = (fn) => {
							 | 
						|
								      userCompleteFn = fn;
							 | 
						|
								      return tween;
							 | 
						|
								    };
							 | 
						|
								    return tween;
							 | 
						|
								  }
							 | 
						|
								  update() {
							 | 
						|
								    TWEEN.update();
							 | 
						|
								    return this.numTweensRunning > 0;
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Чтобы использовать его, мы создадим один </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
							 | 
						|
								  const canvas = document.querySelector('#c');
							 | 
						|
								  const renderer = new THREE.WebGLRenderer({canvas});
							 | 
						|
								+  const tweenManager = new TweenManger();
							 | 
						|
								
							 | 
						|
								  ...
							 | 
						|
								</pre>
							 | 
						|
								<p>Мы будем использовать его для создания наших <code class="notranslate" translate="no">Tween</code>s.</p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// show the selected data, hide the rest
							 | 
						|
								function showFileInfo(fileInfos, fileInfo) {
							 | 
						|
								  fileInfos.forEach((info) => {
							 | 
						|
								    const visible = fileInfo === info;
							 | 
						|
								    info.elem.className = visible ? 'selected' : '';
							 | 
						|
								    const targets = {};
							 | 
						|
								    fileInfos.forEach((info, i) => {
							 | 
						|
								      targets[i] = info === fileInfo ? 1 : 0;
							 | 
						|
								    });
							 | 
						|
								    const durationInMs = 1000;
							 | 
						|
								-    new TWEEN.Tween(mesh.morphTargetInfluences)
							 | 
						|
								+    tweenManager.createTween(mesh.morphTargetInfluences)
							 | 
						|
								      .to(targets, durationInMs)
							 | 
						|
								      .start();
							 | 
						|
								  });
							 | 
						|
								  requestRenderIfNotRequested();
							 | 
						|
								}
							 | 
						|
								</pre>
							 | 
						|
								<p>Затем мы обновим наш цикл рендеринга, чтобы обновить анимацию и продолжать рендеринг, если анимация все еще выполняется. </p>
							 | 
						|
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
							 | 
						|
								  renderRequested = false;
							 | 
						|
								
							 | 
						|
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						|
								    const canvas = renderer.domElement;
							 | 
						|
								    camera.aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						|
								    camera.updateProjectionMatrix();
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								+  if (tweenManager.update()) {
							 | 
						|
								+    requestRenderIfNotRequested();
							 | 
						|
								+  }
							 | 
						|
								
							 | 
						|
								  controls.update();
							 | 
						|
								  renderer.render(scene, camera);
							 | 
						|
								}
							 | 
						|
								render();
							 | 
						|
								</pre>
							 | 
						|
								<p>И с этим мы должны анимировать между наборами данных. </p>
							 | 
						|
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						|
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets.html"></iframe></div>
							 | 
						|
								  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
							 | 
						|
								</div>
							 | 
						|
								
							 | 
						|
								<p></p>
							 | 
						|
								<p>Я надеюсь, что пройти через это было полезно. Использование morphtargets либо через сервисы,
							 | 
						|
								которые предоставляет three.js, либо путем написания пользовательских шейдеров - 
							 | 
						|
								это распространенная техника для перемещения большого количества объектов. 
							 | 
						|
								В качестве примера мы могли бы дать каждому кубу случайное место в другой цели и 
							 | 
						|
								превратить его в свои первые позиции на земном шаре. Это может быть крутой способ представить миру. </p>
							 | 
						|
								<p>Далее вас может заинтересовать добавление ярлыков к глобусу, который описан в разделе. 
							 | 
						|
								 <a href="align-html-elements-to-3d.html"> «Выравнивание элементов HTML в 3D»</a>.</p>
							 | 
						|
								<p>Примечание: мы могли бы попытаться просто изобразить процент мужчин
							 | 
						|
								или женщин или общую разницу, но основываясь на том, как мы отображаем 
							 | 
						|
								информацию, кубы, которые растут с поверхности земли, мы бы предпочли, 
							 | 
						|
								чтобы большинство кубов были низкими. Если бы мы использовали одно из 
							 | 
						|
								этих других сравнений, то большинство кубов имели бы примерно половину
							 | 
						|
								их максимальной высоты, что не давало бы хорошей визуализации. 
							 | 
						|
								Не стесняйтесь изменить количество GreaterThan
							 | 
						|
								с Math.max (a - b, 0) на что-то вроде (a - b) «сырой разницы» или a / (a + b) «процентов», и вы поймете, что я имею в виду. </p>
							 | 
						|
								
							 | 
						|
								        </div>
							 | 
						|
								      </div>
							 | 
						|
								    </div>
							 | 
						|
								  
							 | 
						|
								  <script src="/manual/resources/prettify.js"></script>
							 | 
						|
								  <script src="/manual/resources/lesson.js"></script>
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								</body></html>
							 |