threejs 多屏互动效果,居然还能这么玩

news2024/12/23 0:04:26

threejs 多屏互动效果

看别人做了多屏互动的效果,觉得还挺有意思的,也顺便自己动手操作一下试试。
先来张效果图:

请添加图片描述

项目地址

参考地址

项目基于vue+threejs。

思路

大体思路如下:

  1. 架设一个正投影摄像机,在屏幕中间划一个球。
  2. 显示球的网格并让球转起来。
  3. 转动的时候发送当前球的ID、旋转角度,窗口距离屏幕偏移,窗口大小,时间戳等信息。
  4. 接收到其他窗口的信息,根据计算相对于当前球的位置在屏幕上画其他球。

其中跨浏览器标签页通信用的是 BroadcastChannel API。

实践

下边就开始动手做起来。

环境安装

threejs安装命令如下:

npm install --save three

其他部分

架设一个正投影摄像机,在屏幕中间划一个球。

import * as THREE from 'three';
import { onMounted, onUnmounted } from 'vue';

const scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera(-window.innerWidth/2, window.innerWidth/2, window.innerHeight/2, -window.innerHeight/2, -1000, 1000);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

const geometry = new THREE.SphereGeometry( 100, 16, 16 ); 
// wireframe = true 的时候显示网格
const material = new THREE.MeshBasicMaterial( { color: 0xffff00, wireframe: true } ); 
const sphere = new THREE.Mesh( geometry, material );
scene.add(sphere)

var balls = {}
var ballsInfo = {}

function animate() {
    requestAnimationFrame(animate);

    // 让球动起来
    sphere.rotation.x += 0.01;
    sphere.rotation.y += 0.005;

    renderer.render(scene, camera);
}
animate();

onMounted(() => {
  let canvas = document.getElementById('can');
  renderer = new THREE.WebGLRenderer({canvas: canvas});

  resize()
});

window.addEventListener('resize', resize);

function resize() {
  renderer.setSize(window.innerWidth, window.innerHeight);

  camera = new THREE.OrthographicCamera(-window.innerWidth/2, window.innerWidth/2, window.innerHeight/2, -window.innerHeight/2, -1000, 1000);
}

</script>

<template>
  <div id="content">
    <canvas id="can"></canvas>
  </div>
</template>

<style scoped>
#content {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#can {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

动的时候发送当前球的信息。

const ballId = `ball-${(new Date()).getTime()}`
const channel = new BroadcastChannel('ball')
channel.postMessage({
  type: 'ball',
  id: ballId,
  rotation: {
    x: sphere.rotation.x,
    y: sphere.rotation.y,
    z: sphere.rotation.z,
  },
  offset: {
    x: window.screenX,
    y: window.screenY,
  },
  size: {
    width: window.innerWidth,
    height: window.innerHeight
  },
  timestamp: (new Date()).getTime()
})

监听其他窗口信息:

channel.addEventListener('message', function(e) {
  // console.log('message is ', e.data)
  if (e.data.id in balls) {

  } else {
    const bgeo = new THREE.SphereGeometry( 100, 16, 16 ); 
    const bmaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } ); 
    const ball = new THREE.Mesh( bgeo, bmaterial )
    balls[e.data.id] = ball;
    ball.name = e.data.id
    scene.add(balls[e.data.id])
  }

  ballsInfo[e.data.id] = e.data
})

并把其他球画在当前页面上。

let now = (new Date()).getTime()

for (var i in balls) {
  if (ballsInfo[i].id == ballId) {
    continue
  }
  if (now - ballsInfo[i].timestamp > 100) {
    let ball = scene.getObjectByName(ballsInfo[i].id)
    scene.remove(ball)
    delete balls[i]
    delete ballsInfo[i]
    continue
  }
  balls[i].position.x = ( (ballsInfo[i].offset.x + ballsInfo[i].size.width / 2) - (window.screenX + window.innerWidth / 2 ) )
  balls[i].position.y = ( (window.screenY + window.innerHeight / 2) - (ballsInfo[i].offset.y + ballsInfo[i].size.height / 2) ) 
  balls[i].position.z = 0
  balls[i].rotation.x = ballsInfo[i].rotation.x;
  balls[i].rotation.y = ballsInfo[i].rotation.y;
  balls[i].rotation.z = ballsInfo[i].rotation.z;
}

其中球的位置其实是有敞口相对于屏幕的偏移加上窗口的一半大小来确定。
这样所有的球的位置就都在同一个屏幕坐标系下了。
之后简单的加减就能确定球的相对位置了。

完整代码

import * as THREE from 'three';
import { onMounted, onUnmounted } from 'vue';

const scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera(-window.innerWidth/2, window.innerWidth/2, window.innerHeight/2, -window.innerHeight/2, -1000, 1000);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

const geometry = new THREE.SphereGeometry( 100, 16, 16 ); 
const material = new THREE.MeshBasicMaterial( { color: 0xffff00, wireframe: true } ); 
const sphere = new THREE.Mesh( geometry, material );
scene.add(sphere)

const ballId = `ball-${(new Date()).getTime()}`

var balls = {}
var ballsInfo = {}

const channel = new BroadcastChannel('ball')
channel.addEventListener('message', function(e) {
  // console.log('message is ', e.data)
  if (e.data.id in balls) {

  } else {
    const bgeo = new THREE.SphereGeometry( 100, 16, 16 ); 
    const bmaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } ); 
    const ball = new THREE.Mesh( bgeo, bmaterial )
    balls[e.data.id] = ball;
    ball.name = e.data.id
    scene.add(balls[e.data.id])
  }

  ballsInfo[e.data.id] = e.data
})

function animate() {
    requestAnimationFrame(animate);

    sphere.rotation.x += 0.01;
    sphere.rotation.y += 0.005;

    channel.postMessage({
      type: 'ball',
      id: ballId,
      rotation: {
        x: sphere.rotation.x,
        y: sphere.rotation.y,
        z: sphere.rotation.z,
      },
      offset: {
        x: window.screenX,
        y: window.screenY,
      },
      size: {
        width: window.innerWidth,
        height: window.innerHeight
      },
      timestamp: (new Date()).getTime()
    })

    let now = (new Date()).getTime()

    for (var i in balls) {
      if (ballsInfo[i].id == ballId) {
        continue
      }
      if (now - ballsInfo[i].timestamp > 100) {
        let ball = scene.getObjectByName(ballsInfo[i].id)
        scene.remove(ball)
        delete balls[i]
        delete ballsInfo[i]
        continue
      }
      balls[i].position.x = ( (ballsInfo[i].offset.x + ballsInfo[i].size.width / 2) - (window.screenX + window.innerWidth / 2 ) )
      balls[i].position.y = ( (window.screenY + window.innerHeight / 2) - (ballsInfo[i].offset.y + ballsInfo[i].size.height / 2) ) 
      balls[i].position.z = 0
      balls[i].rotation.x = ballsInfo[i].rotation.x;
      balls[i].rotation.y = ballsInfo[i].rotation.y;
      balls[i].rotation.z = ballsInfo[i].rotation.z;
    }

    renderer.render(scene, camera);
}
animate();

onMounted(() => {
  let canvas = document.getElementById('can');
  renderer = new THREE.WebGLRenderer({canvas: canvas});

  resize()
});

window.addEventListener('resize', resize);

function resize() {
  renderer.setSize(window.innerWidth, window.innerHeight);

  camera = new THREE.OrthographicCamera(-window.innerWidth/2, window.innerWidth/2, window.innerHeight/2, -window.innerHeight/2, -1000, 1000);
}

</script>

<template>
  <div id="content">
    <canvas id="can"></canvas>
  </div>
</template>

<style scoped>
#content {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#can {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1307262.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

技术阅读周刊第9️⃣期

技术阅读周刊&#xff0c;每周更新。 历史更新 20231107&#xff1a;第五期20231117&#xff1a;第六期20231124&#xff1a;第七期20231201&#xff1a;第八期 美团技术博客十周年&#xff0c;感谢一路相伴 - 美团技术团队 URL: https://tech.meituan.com/2023/12/04/ten-year…

java学生选课系统 数据库版

首先让我们创建一个数据库 让我们向表中插入数据然后查询它

AI全栈大模型工程师(二十八)如何做好算法备案

互联网信息服务算法 什么情况下要备案&#xff1f; 对于B2B业务&#xff0c;不需要备案。 但在B2C领域&#xff0c;一切要视具体情况而定。 如果我们自主训练大型模型&#xff0c;这是必要的。 但如果是基于第三方模型提供的服务&#xff0c;建议选择那些已获得备案并且具有较大…

光伏开发设计施工一体化系统都有哪些功能?

随着全球对可再生能源的需求不断增加&#xff0c;光伏行业得到了快速发展。同时也面临着一些挑战&#xff0c;例如初始投资成本高、需要大量土地和水资源等。鹧鸪云光伏与储能软件利用技术创新&#xff0c;促进光伏行业数字化升级。 一、智能测算 1.投融资表&#xff1a;采用…

如何处理PHP开发中的单元测试和自动化测试?

如何处理PHP开发中的单元测试和自动化测试&#xff0c;需要具体代码示例 随着软件开发行业的日益发展&#xff0c;单元测试和自动化测试成为了开发者们重视的环节。PHP作为一种广泛应用于Web开发的脚本语言&#xff0c;单元测试和自动化测试同样也在PHP开发中扮演着重要的角色…

java智慧工地系统:让工地管理可视化、数字化、智能化

智慧工地功能包括&#xff1a;劳务管理、施工安全管理、视频监控管理、机械安全管理、危大工程监管、现场物料监管、绿色文明施工、安全隐患排查、施工综合管理、施工质量管理、设备管理、系统管理等模块。 一、项目开发环境 技术架构&#xff1a;微服务 开发语言&#xff1a;…

Ubuntu 设置共享文件夹

一、在Windows中建立一个英文的文件夹 注意&#xff1a;新建文件夹的名称一定要是英文的&#xff0c;不能出现中文的路径&#xff08;可能出现问题&#xff09; 二、在VMware中添加共享文件 3: VMware安装VMware Tools 一般安装成功桌面上会显示这个安装包&#xff0c;&…

【从零开始学习JAVA集合 | 第一篇】深入解读HashMap源码(含面试题)

目录 目录 前言&#xff1a; HashMap简介&#xff1a; HashMap的常用常量和变量&#xff1a; HashMap的重要考点&#xff1a; HashMap的存储过程&#xff1a; HashMap的扩容过程&#xff1a; HashMap的初始化&#xff1a; 常见面试题&#xff1a; 总结&#xff1a;…

菜鸟学习日记(python)——迭代器与生成器

迭代器 迭代是 Python 最强大的功能之一&#xff0c;是访问集合元素的一种方式。 迭代器是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问&#xff0c;直到所有的元素被访问完结束。迭代器只能往前不会后退。 迭代器有两个基本的方法&#xff1a;it…

【大数据】Hudi 核心知识点详解(一)

&#x1f60a; 如果您觉得这篇文章有用 ✔️ 的话&#xff0c;请给博主一个一键三连 &#x1f680;&#x1f680;&#x1f680; 吧 &#xff08;点赞 &#x1f9e1;、关注 &#x1f49b;、收藏 &#x1f49a;&#xff09;&#xff01;&#xff01;&#xff01;您的支持 &#x…

win10中CMD找不到adb的解决方法

问题描述&#xff1a; 在cmd命令行输入”adb devices” 时就会出现”adb不是内部命令或者外部命令….”&#xff0c;出现这个问题主要是windows系统环境变量没设置好。 配置环境变量 找到本地 adb.exe 程序所在目录&#xff0c;复制当前目录&#xff1b;找到高级系统设置 &g…

CleanMyMac X2024(Mac优化清理工具)v4.14.5中文版

CleanMyMac X是一款颇受欢迎的专业清理软件&#xff0c;拥有十多项强大的功能&#xff0c;可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等&#xff0c;并且这款清理软件操作简易&#xff0c;非常好上手&#xff0c;特别适用于那些刚入手苹…

Xubuntu16.04系统中使用EDIMAX EW-7822UAC无线网卡开启5G自发AP

目录 1.关于 EDIMAX EW-7822UAC2.驱动安装3.查看无线网卡信息3.通过create_ap配置5G自发AP 1.关于 EDIMAX EW-7822UAC 官网介绍 https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_ac1200_dual-band/ew-7822uac/ 详细参数…

大数据企业如何使用IP代理进行数据抓取

目录 一、引言 二、IP代理概述 三、为什么大数据企业需要使用IP代理 四、使用IP代理进行数据抓取的步骤 1、获取可用的代理IP 2、配置代理IP 3、设置请求头部信息 4、开始数据抓取 5、错误处理和重试 五、IP代理的注意事项 六、总结 一、引言 随着互联网的快速发展…

C++笔记汇总(随时更新)

你好&#xff0c;这里是争做图书馆扫地僧的小白。 个人主页&#xff1a;争做图书馆扫地僧的小白_-CSDN博客 目标&#xff1a;希望通过学习技术&#xff0c;期待着改变世界。 目录 前言 一、C语言向C语言过度的知识点 二、C语言的相关知识 总结 前言 2023.12.13 之前撰写的笔…

解决maven报错 ‘parent.relativePath‘ of POM

错误提示 parent.relativePath of POM io.renren:renren-fast:3.0.0 (D:\wzyProjets\gulimail\renren-fast\pom.xml) points at com.wzy.gulimail:gulimail instead of org.springframework.boot:spring-boot-starter-parent, please verify your project structure错误分析 子…

记录 | vscode无法在这个大型工作区中监视文件更改,请按照说明链接解决问题

在 VSCode 上打开一个项目时&#xff0c;突然弹出以下错误&#xff1a; 无法在这个大型工作区中监视文件更改。请按照说明链接解决问题。 原因&#xff1a; 由于工作区太大包含太多文件导致vs code监视文件达到上限而因此这个错误。在 Linux 上执行以下命令&#xff1a; cat …

AI智能视界,视频监控技术的革新与突破

智能视频监控概述 TSINGSEE青犀智能监控系统是通过摄像头采集视频数据&#xff0c;经过压缩技术处理后传输至服务器&#xff0c;再由服务器进行存储和管理并汇聚到EasyCVR视频融合平台之中&#xff0c;进行统一的分发处理。采用先进的视频压缩技术&#xff0c;确保视频质量&am…

Java_Mybatis_缓存

缓存 1.概述 Mybatis 缓存&#xff1a;MyBatis 内置了一个强大的事务性查询缓存机制&#xff0c;它可以非常方便地配置和定制 2.会话缓存&#xff08;一级缓存&#xff09; sqlSession 级别的&#xff0c;也就是说&#xff0c;使用同一个 sqlSession 查询同一 sql 时&#x…

小新Air-14 Plus 2021款AMD ACN版(82L7)原装出厂Win11系统镜像

LENOVO联想笔记本开箱状态原厂Windows11系统包 链接&#xff1a;https://pan.baidu.com/s/1D_sYCJAtOeUu9RbTIXgI3A?pwd96af 提取码&#xff1a;96af 联想小新AIR14笔记本电脑原厂系统自带所有驱动、出厂主题壁纸、Office办公软件、联想电脑管家等预装程序 所需要工具&am…