Статьи

Визуализация потока Twitter в VR с Three.js и Node

Твиттер замечательный и богатый информацией зверь. Я хотел объединить некоторые возможности визуализации Three.js и его возможности виртуальной реальности с Socket.IO и Node, чтобы создать симпатичный мир частиц, генерируемых потоком Twitter.

Ранее я уже обсуждал все основы разработки веб-приложений для виртуальной реальности в статье SitePoint. Вывод виртуальной реальности в Интернет с помощью Google Cardboard и Three.js , поэтому, если вы новичок в этой идее, сначала прочтите ее и вернитесь. Это демо использует те же основы.

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

Демонстрационный код

Если вы заинтересованы в том, чтобы прямо в код и попробовать его, вы можете найти его здесь, на GitHub .

Хотите попробовать это в действии? У меня здесь запущенная версия: VR Twitter World .

Наш серверный код

Начнем с рассмотрения кода нашего Node-сервера. Он будет отображать наш плоский HTML, а также работать как сервер Socket.IO, который будет получать поток данных из Twitter.

Полный сервер относительно короткий и выглядит так:

var express = require('express'), app = express(), server = require('http').createServer(app), port = process.env.PORT || 80, io = require('socket.io')(server), config = require('./config.json'), Twitter = require('node-tweet-stream'), t = new Twitter(config); app.get('/', function(request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get(/^(.+)$/, function(req, res) { res.sendFile(__dirname + '/public/' + req.params[0]); }); app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); server.listen(port, function() { console.log('Listening on ' + port); }); t.track('pizza'); t.on('tweet', function(tweet){ console.log('Roger that. Tweets incoming!'); console.log(tweet); io.emit('tweet', tweet); }); t.on('error', function (err) { console.log('Brace yourself! We are goin doooowwwwwwnnnnnnnn! ', err); }); 

Наши первые строки настраивали сервер с использованием инфраструктуры Node Express. Это довольно простая настройка, которая включает в себя все наши зависимости и подготавливает переменную app для нас, чтобы получить доступ к функциональности нашего сервера. port определяет, на каком порту мы хотим, чтобы наш сервер работал ( process.env.PORT — это переменная сервера, которую будут определять некоторые настройки хостинга, такие как Heroku).

 var express = require('express'), app = express(), server = require('http').createServer(app), port = process.env.PORT || 80, 

Затем мы устанавливаем переменную io , одновременно запуская функциональность нашего сервера Socket.IO, присоединяя ее к серверу Express, который мы установили выше:

 io = require('socket.io')(server), 

Настройка доступа к Twitter

Переменная config — это хороший способ сохранить ключи аутентификации Twitter приложения и токены доступа в своем собственном файле. Для просмотра потока Twitter в режиме реального времени мы будем использовать модуль npm, называемый node-tweet-stream, который предоставляет все функции, которые нам понадобятся. Мы присваиваем объект для нашего доступа к Твиттеру и всем связанным функциям переменной t , передавая нашу конфигурацию JSON, чтобы доказать, что нам разрешен доступ к нему.

 config = require('./config.json'), Twitter = require('node-tweet-stream'), t = new Twitter(config), 

Если у вас нет ключей Twitter для доступа к API Twitter, не бойтесь! Вам просто нужно зарегистрировать приложение в Twitter. Перейдите на страницу управления приложениями Twitter , войдите в систему с учетными данными Twitter и нажмите «Создать новое приложение» .

Получив приложение, вы можете получить ключи и токены доступа, нажав ссылку «Ключи и токены доступа», которая появится на странице управления вашего приложения. Если вы не можете найти его, он будет по адресу: https://apps.twitter.com/app/0000000/keys (замените 0000000 на идентификатор вашего приложения).

Затем создайте файл на том же уровне, что и index.html именем config.json . В него добавьте следующее со значениями вашего собственного приложения:

 { "consumer_key": "YOURKEY", "consumer_secret": "YOURKEYSECRET", "token": "YOURTOKEN", "token_secret": "YOURTOKENSECRET" } 

Другие основы сервера

Далее в нашем файле index.js мы настроили вызовы к корню нашего сервера для загрузки /public/index.html :

 app.get('/', function(request, response) { response.sendFile(__dirname + '/public/index.html'); }); 

У нас также есть все остальные статические файлы в public каталоге на нашем сервере:

 app.get(/^(.+)$/, function(req, res) { res.sendFile(__dirname + '/public/' + req.params[0]); }); 

Если у нас есть ошибка, мы регистрируем эту ошибку в консоли нашего сервера и возвращаем ошибку 500:

 app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); 

Следующие строки запускают наш сервер со всеми вышеуказанными настройками.

 server.listen(port, function() { console.log('Listening on ' + port); }); 

Получение нашего живого потока Twitter

Наконец, мы настроили наш сервер на Twitter. Мы используем функцию track() чтобы указать, какое ключевое слово мы хотели бы отслеживать в постоянно расширяющемся потоке контента Twitter.

 t.track('pizza'); 

Затем мы настраиваем функцию обратного вызова для запуска в любое время, когда модуль node-tweet-stream обнаруживает твит с этим ключевым словом. Если он его видит, мы регистрируем его в журнале консоли нашего сервера (это необязательно, вы можете удалить его, если хотите), а затем отправлять этот твит как событие Socket.IO всем подключенным клиентам.

 t.on('tweet', function(tweet){ console.log('Roger that. Tweets incoming!'); console.log(tweet); io.emit('tweet', tweet); }); 

Если по какой-либо причине у нас будет ошибка с нашим Twitter API, она будет записана в журналы нашего сервера:

 t.on('error', function (err) { console.log('Brace yourself! We are goin doooowwwwwwnnnnnnnn! ', err); }); 

Все наши серверные зависимости и детали хранятся в package.json как и во всех приложениях Node. Если вы новичок в Node.js, вы можете прочитать немного о том, что все означает: package.json .

Наш код переднего плана

Наш интерфейсный код начинается с той же настройки, что и в статье « Вывод виртуальной реальности в Интернет» с помощью Google Cardboard и статьи Three.js, — сцена Three.js, которую мы отображаем с помощью стереоскопического эффекта, переводя нашу сцену в вид виртуальной реальности. Чтобы быть кратким и приятным, я не буду рассказывать о битах, которые совпадают с предыдущей демонстрацией из этой статьи. Если вы не уверены в том, что я здесь не объясняю, проверьте информацию в предыдущей статье.

Настройка Socket.IO

Единственный новый JS-файл, который мы добавим по сравнению с нашим предыдущим фундаментом, — это JavaScript-файл Socket.IO. Это простой вкладыш:

 <script src="/socket.io/socket.io.js"></script> 

Чтобы получить доступ к функциональности из Socket.IO, все, что нам нужно, это назначить эту функциональность переменной io , как вы увидите чуть ниже в нашем файле index.html :

 socket = io(), 

Готовим наши башни

Затем мы устанавливаем переменные для наших «башен» (в основном это наши вертикальные наборы частиц, которые представляют твит). Все наши башни хранятся в объекте tweetTowers называется tweetTowers . Это контейнерный объект, который позволяет нам отслеживать все наши башни:

 // Towers tweetTowers = new THREE.Object3D(), 

particleTexture и particleMaterial — наши переменные, которые будут представлять, как будут выглядеть наши частицы:

 particleTexture, particleMaterial, 

maxTowerCount — это максимальное количество башен, которые мы хотим видеть на нашей сцене — если оно установлено слишком высоко, мы можем получить запоздалый опыт. Я установил его на 6000, так как максимальное количество частиц составляет около миллиона. Разумное число на мой взгляд!

 maxTowerCount = 6000, 

range — насколько велика область вокруг зрителя, где мы хотим разместить эти башни. Башни будут размещены в случайных местах сцены, так что это ограничивает расстояние между ними. Я обнаружил, что это приятнее с ними ближе к пользователю. Если они находятся дальше от пользователя, похоже, их не так много (несмотря на то, что существуют тысячи и тысячи частиц!). Я установил его на 100:

 range = 100; 

Наша функция инициации

В нашей функции init() не так уж много нового. Он в основном настраивает нашу VR-камеру и элементы управления, как описано в предыдущей статье. Новые биты в конце.

Мы определили наше изображение particle-new.png как png с именем particle-new.png который мы имеем в нашей particle-new.png папке:

 particleTexture = THREE.ImageUtils.loadTexture('textures/particle-new.png'); 

Мы заканчиваем функцию init() , добавляя наш контейнер tweetTowers в нашу сцену. С этим в нашей сцене нам не нужно беспокоиться о добавлении любой из наших башен непосредственно в сцену, мы просто добавляем их непосредственно в наш объект tweetTowers .

 scene.add(tweetTowers); 

Реагируя на твиты

Вы помните, что как только наш сервер обнаруживает твиты через Twitter с нашим ключевым словом «пицца», он генерирует событие под названием «твит». Наш клиентский JavaScript теперь будет следить за этим событием и отвечать:

 socket.on('tweet', function(tweet) { // Our response }); 

Код ответа — это вызов функции generateTower() , которая добавит башню к нашей сцене, представляющую этот твит. Мы передаем ему четыре значения:

 generateTower({ color: parseInt('0x'+ tweet.user.profile_background_color), startingCoords: { x: getRandomArbitrary(-1*range, range), y: 0, z: getRandomArbitrary(-1*range, range) }, speed: 5, size: (tweet.text.length / 140) * 100 }); 
  • color — это цвет нашей частицы. Мы передаем цвет фона профиля пользователя. Это позволяет нам показывать разные цвета, чтобы представить разных голодных пользователей, пишущих в Твиттере о пицце.
  • startingCoords кордах башня будет расположена. Мы хотим, чтобы они располагались вокруг нас, поэтому мы помещаем их между нашей переменной диапазона выше (это должно быть в диапазоне от -100 до 100) по осям x и z. Если бы мы поместили их случайным образом на y, они начинались бы на разных уровнях выше и ниже от земли, а не выстраивались в линию как здания. Мы определенно не хотим этого, поэтому мы гарантируем, что все они расположены в позиции getRandomArbitrary() 0. getRandomArbitrary() — это простой генератор случайных чисел между двумя значениями.
  • speed определяет, как далеко друг от друга оказываются наши частицы (или скорость, с которой башня поднимается, если они движутся вверх).
  • size — это то, сколько частиц будет в нашей башне. Мы усредняем его в процентах, предполагая максимальную длину Твиттера в 140 символов.

Отображение башни

Наша функция generateTower() сама начинается с определения переменной towerGeometry . Это объект THREE.Geometry который будет содержать позиции всех наших частиц в башне. Сохранение всех точек, отслеживаемых в пределах одного объекта Geometry может помочь сократить время обработки, поскольку Three.js нужно только отслеживать каждый объект башни и его точки, а не диапазон независимых частиц. Позже в коде мы предоставим геометрию объекту THREE.PointCloud который может интерпретировать эти точки в наших частицах.

 function generateTower(options) { var towerGeometry = new THREE.Geometry(); // The rest of our code } 

Затем мы устанавливаем JavaScript-объект под названием particleMovements котором хранится информация о том, где наши частицы будут начинаться и заканчиваться в пределах башни, а также как далеко друг от друга они будут находиться (значения, которые мы передали ранее):

 var particleMovements = { start: 0, end: options.size, speed: options.speed }; 

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

 var currentCoords = {x: 0, y: 0, z: 0}, startingCoords = options.startingCoords ? options.startingCoords : currentCoords; 

Затем мы перебираем размер нашей башни, чтобы создать каждую частицу. Мы устанавливаем текущие координаты для увеличения y на наше значение скорости, умноженное на i . Наши значения x и z остаются в исходных точках, поскольку мы движемся только вверх.

 for (var i = 0; i With those co-ordinates defined for this particle, we attach that particle's position as a vertex in our towerGeometry object: [code language="js"] towerGeometry.vertices.push(new THREE.Vector3(currentCoords.x, currentCoords.y, currentCoords.z)); 

Это гарантирует, что наше расположение частиц установлено правильно. Далее, мы определяем, как будут выглядеть частицы в этой башне в пределах переменнойicleMaterial. Наши частицы будут помещены в объект THREE.PointCloud и поэтому для их оформления мы будем использовать материал THREE.PointCloudMaterial :

 particleMaterial = new THREE.PointCloudMaterial({ map: particleTexture, color: options.color, blending: THREE.AdditiveBlending, transparent: true, size: 4 }); 
  • map определяет изображение, которое мы будем использовать для частицы, мы передаем фактуру particleTexture мы определили ранее.
  • color переходит в цвет, которым мы хотим, чтобы частица была (по умолчанию 0xffffff в Three.js).
  • blending устанавливает, как частицы смешиваются в сцене. THREE.AdditiveBlending добавляет цвет текстуры к той, что за ней.
  • transparent гарантирует, что может произойти смешивание, так как для работы требуется уровень прозрачности.
  • size — это размер наших частиц.

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

 var tower = new THREE.PointCloud(towerGeometry, particleMaterial); 

Мы добавляем эту башню в наш tweetTowers коллекции tweetTowers и затем проверяем, сколько башен находится в сцене. Если у нас больше башен, чем максимально допустимый, мы скрываем самую старую башню, чтобы уменьшить нагрузку на устройство. Если у вас есть проблемы с производительностью, скорее всего, они будут немного лучше, если вы уменьшите maxTowerCount !

 tweetTowers.add(tower); if (tweetTowers.children.length > maxTowerCount) { tweetTowers.children[tweetTowers.children.length - maxTowerCount].visible = false; } 

Запуск нашего кода

Чтобы запустить эту демонстрацию локально, вам понадобится Node и вам нужно будет выполнить обычные команды. Установите все зависимости для проекта:

 npm install 

Затем запустите это:

 node index.js 

Чтобы проверить это на своем смартфоне, вам нужно либо убедиться, что ваш смартфон находится в той же локальной сети и найти IP-адрес вашего компьютера, либо использовать службу туннелирования, такую ​​как ngrok (я рассматриваю, как использовать ngrok в статье о доступе к Localhost Откуда угодно )

Вы также можете разместить сервер Node где-нибудь. Я лично использовал Heroku , однако это полностью личное предпочтение.

Как только вы где-нибудь запустите сервер, откройте Chrome для мобильных устройств и посетите его! Наденьте свой Google Cardboard или другую подобную гарнитуру, и вы должны увидеть, что примерно через полминуты это выглядит примерно так:

Наш виртуальный мир Twitter в действии!

Вывод

Это должно было дать вам хороший обзор использования Node, Socket.IO и Three.js для создания визуализации виртуальной реальности в 3D веб-API. Сама демоверсия может быть доработана, добавив больше ключевых слов, фильтров, сделав ее более гладкой с большим количеством частиц и так далее. Есть много возможностей! Не стесняйтесь выйти и попробовать сделать свой собственный фантастический опыт из этой демонстрации!

У меня также есть другие демонстрации здесь, в SitePoint, которые используют похожие концепции, но вместо этого приводят их в опыт дополненной реальности. Если вам интересно, Filtering Reality с помощью JavaScript и Google Cardboard исследует взятие камеры со своего смартфона и добавление к ней фильтров, а Augmented Reality в браузере с Awe.js исследует весь путь и расширяет элементы в поле вашего зрения с помощью всегда мощная комбинация Three.js и Awe.js!

Если вы решаете собрать собственную визуализацию виртуальной реальности из демонстрации в этой статье (или комбинировать ее с элементами из упомянутых примеров AR), оставьте примечание в комментариях или свяжитесь со мной в Twitter ( @thatpatrickguy) Я достану гарнитуру и посмотрю!