Танцевальный вечер в WebVR

Я был взволнован, когда команда Google Data Arts обратилась к нам с Моникером с предложением о совместной работе над изучением возможностей, предоставляемых WebVR. Я наблюдал за работой их команды на протяжении многих лет, и их проекты всегда вызывали у меня отклик. Результатом нашего сотрудничества стал Dance Tonite , постоянно меняющийся танцевальный опыт в виртуальной реальности от LCD Soundsystem и их поклонников. Вот как мы это сделали.

Концепция

Мы начали с разработки серии прототипов с использованием WebVR — открытого стандарта, который позволяет войти в виртуальную реальность, посетив веб-сайт с помощью браузера. Цель — облегчить доступ к виртуальной реальности каждому, независимо от того, какое у вас устройство.

Мы приняли это близко к сердцу. Все, что мы придумали, должно работать на всех типах виртуальной реальности: от гарнитур виртуальной реальности, работающих с мобильными телефонами, таких как Daydream View от Google, Cardboard и Gear VR от Samsung, до систем размером с комнату, таких как HTC VIVE и Oculus Rift, которые отражают ваше физическое состояние. движения в вашей виртуальной среде. Возможно, самое главное, мы чувствовали, что было бы в духе Интернета создать что-то, что будет работать для всех, у кого нет VR-устройства.

1. Захват движения своими руками

Поскольку мы хотели творчески вовлечь пользователей, мы начали изучать возможности участия и самовыражения с помощью виртуальной реальности. Мы были впечатлены тем, насколько точно вы можете двигаться и смотреть по сторонам в виртуальной реальности, а также насколько точно это происходит. Это дало нам идею. Вместо того, чтобы заставлять пользователей что-то смотреть или создавать, как насчет записи их движений?

Кто-то записывает себя на Dance Tonite. Экран позади них показывает то, что они видят в гарнитуре.

Мы подготовили прототип, в котором записывали положения наших VR-очков и контроллеров во время танца. Мы заменили записанные позиции абстрактными фигурами и поразились результатам. Результаты были такими человечными и содержали столько индивидуальности! Мы быстро поняли, что можем использовать WebVR для дешевого захвата движения дома.

Благодаря WebVR разработчик имеет доступ к положению и ориентации головы пользователя через объект VRPose . Это значение обновляется каждый кадр аппаратным обеспечением виртуальной реальности, чтобы ваш код мог отображать новые кадры с правильной точки зрения. Через API GamePad с WebVR мы также можем получить доступ к положению/ориентации пользовательских контроллеров через объект GamepadPose . Мы просто сохраняем все эти значения положения и ориентации в каждом кадре, создавая таким образом «запись» движений пользователя.

2. Минимализм и костюмы

С помощью современного VR-оборудования размером с комнату мы можем отслеживать три точки тела пользователя: его голову и две руки. В Dance Tonite мы хотели сосредоточить внимание на человечности в движении этих трех точек в пространстве. Чтобы добиться этого, мы максимально свели эстетику к минимуму, чтобы сосредоточиться на движении. Нам понравилась идея заставить людей работать.

Это видео, демонстрирующее работу шведского психолога Гуннара Йоханссона, было одним из примеров, на которые мы ссылались, когда рассматривали возможность максимально упростить ситуацию. Он показывает, как плавающие белые точки мгновенно узнаются как тела, если их увидеть в движении.

Визуально нас вдохновили цветные комнаты и геометрические костюмы в этой записи перепостановки Маргарет Гастингс 1970 года «Триадического балета» Оскара Шлеммера.

В то время как причина выбора Шлеммером абстрактных геометрических костюмов заключалась в том, чтобы ограничить движения его танцоров движениями марионеток и марионеток, в «Dance Tonite» у нас была противоположная цель.

В итоге мы основывали выбор форм на том, сколько информации они передают при вращении. Шар выглядит одинаково, независимо от того, как его поворачивают, но конус на самом деле указывает в том направлении, в котором он смотрит, и выглядит спереди иначе, чем сзади.

3. Петлевая педаль для движения.

Мы хотели показать большие группы записанных людей, танцующих и двигающихся друг с другом. Сделать это вживую было бы невозможно, поскольку устройства виртуальной реальности не выпускаются в достаточном количестве. Но мы по-прежнему хотели, чтобы группы людей реагировали друг на друга посредством движения. Мы сразу вспомнили рекурсивное выступление Нормана Макларена в его видеофрагменте «Канон» 1964 года.

Выступление Макларена представляет собой серию хорошо поставленных движений, которые начинают взаимодействовать друг с другом после каждого цикла. Подобно педали в музыке, где музыканты играют сами с собой, накладывая разные фрагменты живой музыки, нам было интересно посмотреть, сможем ли мы создать среду, в которой пользователи могли бы свободно импровизировать более свободные версии исполнения.

4. Смежные комнаты

Смежные номера

Как и большая часть музыки, треки LCD Soundsystem построены с использованием точно рассчитанных тактов. Их трек Tonite, представленный в нашем проекте, имеет такты длительностью ровно 8 секунд. Мы хотели, чтобы пользователи исполняли каждый 8-секундный цикл трека. Хотя ритм этих тактов не меняется, меняется их музыкальное содержание. По ходу песни возникают моменты с разными инструментами и вокалом, на которые исполнители могут реагировать по-разному. Каждая из этих мер выражается в виде комнаты, в которой люди могут устроить соответствующее ей представление.

Оптимизация производительности: не пропускайте кадры

Создание многоплатформенной виртуальной реальности, работающей на единой базе кода с оптимальной производительностью для каждого устройства или платформы, — непростая задача.

В виртуальной реальности одна из самых неприятных вещей, с которой вы можете столкнуться, — это то, что частота кадров не успевает за вашими движениями. Если вы поворачиваете голову, но изображение, которое видят ваши глаза, не соответствует движению, которое ощущает ваше внутреннее ухо, это вызывает мгновенное ощущение тошноты в желудке. По этой причине нам нужно было избегать большой задержки частоты кадров. Вот некоторые оптимизации, которые мы реализовали.

1. Геометрия экземплярного буфера

Поскольку весь наш проект использует лишь несколько 3D-объектов, мы смогли получить огромный прирост производительности с помощью Instanced Buffer Geometry. По сути, он позволяет вам один раз загрузить объект в графический процессор и нарисовать столько «экземпляров» этого объекта, сколько захотите, за один вызов отрисовки. В Dance Tonite у нас есть только три разных объекта (конус, цилиндр и комната с дыркой), но потенциально сотни копий этих объектов. Instance Buffer Geometry является частью ThreeJS , но мы использовали экспериментальную и находящуюся в разработке версию Душана Босняка , которая реализует THREE.InstanceMesh , что значительно упрощает работу с Instanced Buffer Geometry.

2. Избегайте сборщика мусора

Как и во многих других языках сценариев, JavaScript автоматически освобождает память, определяя, какие объекты, которые были выделены, больше не используются. Этот процесс называется сборкой мусора.

Разработчики не могут контролировать, когда это произойдет. Сборщик мусора может появиться у наших дверей в любой момент и начать выбрасывать мусор, в результате чего кадры выпадают, поскольку они не торопятся.

Решение этой проблемы — производить как можно меньше мусора, перерабатывая наши объекты. Вместо того, чтобы создавать новый векторный объект для каждого расчета, мы пометили временные объекты для повторного использования. Поскольку мы удерживаем их, перемещая ссылку на них за пределы нашей области действия, они не были помечены для удаления.

Например, вот наш код для преобразования матрицы местоположения головы и рук пользователя в массив значений положения/вращения, который мы сохраняем в каждом кадре. Повторно используя SERIALIZE_POSITION , SERIALIZE_ROTATION и SERIALIZE_SCALE , мы избегаем выделения памяти и сборки мусора, которые происходили бы, если бы мы создавали новые объекты при каждом вызове функции.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. Сериализация движения и прогрессивное воспроизведение

Чтобы фиксировать движения пользователей в виртуальной реальности, нам нужно было сериализовать положение и вращение их гарнитуры и контроллеров и загрузить эти данные на наши серверы. Мы начали с захвата полных матриц преобразования для каждого кадра. Это работало хорошо, но из-за того, что 16 чисел умножались на 3 позиции каждое со скоростью 90 кадров в секунду, это приводило к очень большим файлам и, следовательно, к длительному ожиданию при загрузке и скачивании данных. Извлекая только данные о положении и вращении из матриц преобразования, мы смогли снизить эти значения с 16 до 7.

Поскольку посетители в Интернете часто нажимают на ссылку, не зная точно, чего ожидать, нам нужно быстро показывать визуальный контент, иначе они уйдут в течение нескольких секунд.

По этой причине мы хотели убедиться, что наш проект сможет начать работу как можно скорее. Первоначально мы использовали JSON в качестве формата для загрузки данных о движении. Проблема в том, что нам нужно загрузить полный файл JSON, прежде чем мы сможем его проанализировать. Не очень прогрессивно.

Чтобы такой проект, как Dance Tonite, отображался с максимально возможной частотой кадров, у браузера есть лишь небольшое количество времени в каждом кадре для вычислений JavaScript. Если вы продержитесь слишком долго, анимация начнет заикаться. Сначала мы испытывали заикания, поскольку эти огромные файлы JSON декодировались браузером.

Мы столкнулись с удобным форматом потоковой передачи данных, который называется NDJSON или JSON с разделителями новой строки. Хитрость здесь заключается в том, чтобы создать файл с серией допустимых строк JSON, каждая на отдельной строке. Это позволяет вам анализировать файл во время его загрузки, что позволяет нам отображать выступления до их полной загрузки.

Вот как выглядит фрагмент одной из наших записей :

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

Использование NDJSON позволяет нам сохранить представление данных отдельных кадров выступлений в виде строк. Мы могли бы подождать, пока не будет достигнуто необходимое время, прежде чем декодировать их в позиционные данные, таким образом распределяя необходимую обработку во времени.

4. Интерполяционное движение

Поскольку мы надеялись отображать от 30 до 60 представлений одновременно, нам нужно было снизить скорость передачи данных еще больше, чем мы уже имели. Команда Data Arts решила ту же проблему в своем проекте Virtual Art Sessions , где они воспроизводят записи художников, рисующих в виртуальной реальности с помощью Tilt Brush. Они решили ее, создав промежуточные версии пользовательских данных с более низкой частотой кадров и интерполируя между кадрами при их воспроизведении. Мы были удивлены, обнаружив, что едва ли можем заметить разницу между интерполированной записью со скоростью 15 кадров в секунду и исходной записью со скоростью 90 кадров в секунду.

Чтобы убедиться в этом, вы можете заставить Dance Tonite воспроизводить данные с различной скоростью, используя строку запроса ?dataRate= . Вы можете использовать это для сравнения записанного движения со скоростью 90 кадров в секунду , 45 кадров в секунду или 15 кадров в секунду .

Для позиции мы выполняем линейную интерполяцию между предыдущим ключевым кадром и следующим в зависимости от того, насколько близко мы находимся во времени между ключевыми кадрами (соотношение):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

Для ориентации мы выполняем сферическую линейную интерполяцию (slerp) между ключевыми кадрами. Ориентация хранится в виде кватернионов .

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. Синхронизация движений с музыкой

Чтобы узнать, какой кадр записанной анимации воспроизводить, нам нужно знать текущее время музыки с точностью до миллисекунды. Оказывается, хотя элемент HTML Audio идеально подходит для постепенной загрузки и воспроизведения звука, предоставляемое им свойство времени не меняется синхронно с циклом кадров браузера. Это всегда немного не так. Иногда на долю мс раньше, иногда на долю слишком поздно.

Это приводит к заиканию в наших прекрасных танцевальных записях, чего мы хотим избежать любой ценой. Чтобы исправить это, мы реализовали собственный таймер в JavaScript . Таким образом, мы можем быть уверены, что количество времени, изменяющееся между кадрами, равно количеству времени, прошедшему с момента последнего кадра. Всякий раз, когда наш таймер рассинхронизируется с музыкой более чем на 10 мс, мы синхронизируем его снова.

6. Отбраковка и туман

Каждой истории нужен хороший конец, и мы хотели сделать что-то удивительное для пользователей, которые дожили до конца нашего опыта. Выходя из последней комнаты, вы попадаете в тихий пейзаж из конусов и цилиндров. «Это конец?» — спросите вы. По мере того, как вы продвигаетесь дальше по полю, внезапно звуки музыки заставляют различные группы конусов и цилиндров превращаться в танцоров. Вы оказываетесь в центре огромной вечеринки! Затем, когда музыка резко прекращается, все падает на землю.

Хотя зрителю это казалось замечательным, это создавало некоторые проблемы с производительностью, которые нужно было решить. Устройства виртуальной реальности комнатного масштаба и их высококлассные игровые установки отлично показали себя с 40 с лишним дополнительными возможностями, необходимыми для нашей новой концовки. Но частота кадров на некоторых мобильных устройствах была снижена вдвое.

Чтобы противодействовать этому, мы ввели туман. После определенного расстояния все постепенно становится черным. Поскольку нам не нужно рассчитывать или рисовать то, что не видно, мы отбираем производительность в невидимых комнатах, и это позволяет нам сэкономить работу как для ЦП, так и для графического процессора. Но как определиться с правильным расстоянием?

Некоторые устройства могут обрабатывать все, что вы им бросаете, а другие более ограничены. Мы решили реализовать скользящую шкалу. Постоянно измеряя количество кадров в секунду, мы можем соответствующим образом регулировать расстояние нашего тумана. Пока частота кадров у нас стабильная, мы пытаемся взять на себя больше работы по рендерингу, разгоняя туман. Если частота кадров недостаточно плавная, мы приближаем туман, позволяя пропустить рендеринг в темноте.

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

Что-то для каждого: создание виртуальной реальности для Интернета

Проектирование и разработка мультиплатформенного асимметричного опыта означает учет потребностей каждого пользователя в зависимости от его устройства. И при каждом дизайнерском решении нам нужно было видеть, как это может повлиять на других пользователей. Как добиться того, чтобы то, что вы видите в виртуальной реальности, было таким же захватывающим, как и без виртуальной реальности, и наоборот?

1. Желтый шар

Таким образом, наши пользователи виртуальной реальности в масштабе комнаты будут выступать, но как пользователи мобильных устройств виртуальной реальности (таких как Cardboard, Daydream View или Samsung Gear) воспримут проект? Для этого мы ввели в нашу среду новый элемент: желтый шар.

Желтый шар
Желтый шар

Когда вы смотрите проект в VR, вы делаете это с точки зрения желтого шара. Когда вы перемещаетесь из комнаты в комнату, танцоры реагируют на ваше присутствие. Они жестикулируют вам, танцуют вокруг вас, делают забавные движения за вашей спиной и быстро уходят с вашего пути, чтобы не столкнуться с вами. Желтый шар всегда в центре внимания.

Причина этого в том, что во время записи выступления желтый шар движется через центр комнаты синхронно с музыкой и возвращается обратно. Положение сферы дает исполнителю представление о том, где он находится во времени и сколько времени у него осталось в цикле. Это дает им естественный фокус, на котором можно построить представление.

2. Другая точка зрения

Мы не хотели оставлять без внимания пользователей без виртуальной реальности, тем более что они, вероятно, будут нашей самой большой аудиторией. Вместо того, чтобы создавать имитацию виртуальной реальности, мы хотели дать экранным устройствам свой собственный опыт. У нас была идея показать спектакли сверху, в изометрической перспективе. Эта точка зрения имеет богатую историю в компьютерных играх. Впервые он был использован в Zaxxon, космическом шутере 1982 года. В то время как пользователи виртуальной реальности находятся в гуще событий, изометрическая перспектива дает богоподобный вид на действие. Мы решили немного увеличить модели, придав им эстетику кукольного домика.

3. Тени: притворяйся, пока не добьешься успеха.

Мы обнаружили, что некоторым нашим пользователям было трудно увидеть глубину с нашей изометрической точки зрения. Я совершенно уверен, что именно по этой причине Zaxxon также была одной из первых компьютерных игр в истории, в которых проецировалась динамическая тень под летающими объектами.

Тени

Оказывается, создавать тени в 3D сложно. Особенно для устройств с ограниченными возможностями, таких как мобильные телефоны. Первоначально нам пришлось принять трудное решение исключить их из уравнения, но после того, как мы обратились за советом к автору Three.js и опытному демо-хакеру Mr Doob , ему в голову пришла новая идея… подделать их.

Вместо того, чтобы рассчитывать, как каждый из наших плавающих объектов затеняет наши источники света и, следовательно, отбрасывает тени различной формы, мы рисуем одно и то же круглое размытое текстурное изображение под каждым из них. Поскольку наши визуальные эффекты изначально не пытаются имитировать реальность, мы обнаружили, что можем легко справиться с этой задачей, внеся всего лишь несколько изменений. Когда объекты приближаются к земле, текстуры становятся темнее и меньше. Когда они перемещаются вверх, мы делаем текстуры прозрачнее и крупнее.

Для их создания мы использовали эту текстуру с мягким градиентом от белого к черному (без альфа-прозрачности). Мы устанавливаем материал как прозрачный и используем субтрактивное смешивание. Это помогает им хорошо сочетаться, когда они перекрываются:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. Быть там

Нажимая на головы артистов, посетители без виртуальной реальности могут увидеть происходящее с точки зрения танцора. С этого ракурса становится очевидным множество мелких деталей. Стараясь идти в ногу со своим выступлением, танцоры быстро переглядываются друг с другом. Когда шар входит в комнату, вы видите, как они нервно смотрят в его сторону. Хотя вы, как зритель, не можете повлиять на эти движения, они на удивление хорошо передают ощущение погружения. Опять же, мы предпочли сделать это, а не предлагать нашим пользователям простую версию искусственной виртуальной реальности, управляемую мышью.

5. Обмен записями

Мы знаем, как вы можете гордиться, создавая сложную хореографическую запись 20 слоев исполнителей, реагирующих друг на друга. Мы знали, что наши пользователи, вероятно, захотят показать это своим друзьям. Но неподвижное изображение этого подвига недостаточно передает информацию. Вместо этого мы хотели позволить нашим пользователям делиться видео своих выступлений. Собственно, почему не гифка? Наши анимации имеют плоскую заливку и идеально подходят для ограниченной цветовой палитры формата.

Обмен записями

Мы обратились к GIF.js , библиотеке JavaScript, которая позволяет кодировать анимированные GIF-изображения прямо в браузере. Он перекладывает кодирование кадров на веб-работников , которые могут работать в фоновом режиме как отдельные процессы, что дает возможность использовать преимущества нескольких процессоров, работающих бок о бок.

К сожалению, при таком количестве кадров, которое нам требовалось для анимации, процесс кодирования все еще был слишком медленным. GIF может создавать небольшие файлы, используя ограниченную цветовую палитру. Мы обнаружили, что большая часть времени тратится на поиск ближайшего цвета для каждого пикселя. Нам удалось оптимизировать этот процесс в десять раз, применив небольшой упрощенный подход: если цвет пикселя такой же, как и последний, используйте тот же цвет из палитры, что и раньше.

Теперь у нас были быстрые кодировки, но полученные GIF-файлы были слишком большого размера. Формат GIF позволяет указать, как каждый кадр должен отображаться поверх предыдущего, путем определения его метода удаления. Чтобы получить файлы меньшего размера, вместо обновления каждого пикселя в каждом кадре мы обновляем только те пиксели, которые изменились. Хотя процесс кодирования снова замедлился, это значительно уменьшило размеры наших файлов.

6. Твердая почва: Google Cloud и Firebase

Серверная часть сайта с «пользовательским контентом» часто может быть сложной и хрупкой, но мы придумали простую и надежную систему благодаря Google Cloud и Firebase. Когда исполнитель загружает в систему новый танец, он анонимно аутентифицируется с помощью Firebase Authentication . Им предоставляется разрешение загружать свои записи во временное пространство с помощью Cloud Storage for Firebase . Когда загрузка завершена, клиентский компьютер вызывает HTTP-триггер Cloud Functions for Firebase, используя свой токен Firebase. Это запускает серверный процесс, который проверяет отправку, создает запись в базе данных и перемещает запись в общедоступный каталог в Google Cloud Storage.

Твердая земля

Весь наш общедоступный контент хранится в виде простых файлов в сегменте облачного хранилища. Это означает, что наши данные быстро доступны по всему миру, и нам не нужно беспокоиться о высокой нагрузке на трафик, которая каким-либо образом повлияет на доступность данных.

Мы использовали базу данных Firebase Realtime и конечные точки облачных функций, чтобы создать простой инструмент модерации/курирования, который позволяет нам просматривать каждую новую публикацию в виртуальной реальности и публиковать новые плейлисты с любого устройства.

7. Работники сферы обслуживания

Сервисные работники — это относительно недавнее нововведение, которое помогает управлять кэшированием ресурсов веб-сайта. В нашем случае работники сервиса молниеносно загружают наш контент для повторных посетителей и даже позволяют сайту работать в автономном режиме. Это важные особенности, поскольку многие из наших посетителей будут пользоваться мобильной связью различного качества.

Добавить сервис-воркеров в проект было легко благодаря удобному плагину веб-пакета , который берет на себя большую часть тяжелой работы за вас. В приведенной ниже конфигурации мы создаем сервис-воркера, который автоматически кэширует все наши статические файлы. Он извлечет последний файл списка воспроизведения из сети, если он доступен, поскольку список воспроизведения будет постоянно обновляться. Все записываемые файлы JSON должны быть извлечены из кеша, если они доступны, поскольку они никогда не изменятся.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

В настоящее время плагин не обрабатывает постепенно загружаемые мультимедийные ресурсы, такие как наши музыкальные файлы, поэтому мы обошли эту проблему, установив для заголовка Cloud Storage Cache-Control в этих файлах значение public, max-age=31536000 , чтобы браузер кэшировал файл. на срок до одного года.

Заключение

Мы с нетерпением ждем того, как исполнители дополнят этот опыт и будут использовать его как инструмент для творческого самовыражения с помощью движения. Мы выложили весь код в открытый исходный код, который вы можете найти по адресу https://github.com/puckey/dance-tonite . На заре виртуальной реальности и особенно WebVR мы с нетерпением ждем возможности увидеть, какие новые творческие и неожиданные направления будет развиваться в этой новой среде. Танцуй дальше .