记录--ThreeJs手搓一个罗盘特效

news2024/11/29 22:45:47

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

先上效果

ThreeJs手搓一个罗盘特效

前言

最近在学Three.js.,对着文档看了一周多,正好赶上码上掘金的活动,就顺便写了一个小demo,手搓一个罗盘特效。

太极

先来看一下太极的实现方式,这里我们使用CircleGeometry,将其分解开来可以看出是由圆形和半圆形组成 。

CircleGeometry

CircleGeometry官网案例
radius半径
segments分段(三角面)的数量
thetaStart第一个分段的起始角度
thetaLength圆形扇区的中心角

这里不需要用到segments,但是需要颜色,所以定义一个函数传入半径、颜色、起始角度、中心角。

const createCircle = (r, color, thetaStart, thetaLength) => {
    const material = new THREE.MeshBasicMaterial({
    color: color,
    side: THREE.DoubleSide
    });
    const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
    const circle = new THREE.Mesh(geometry, material);
    return circle;
  };

我们只需要通过传参生产不同大小的圆或半圆,再进行位移就可以实现其效果。

参考代码/73-96行 还有一些需要注意的地方写在注释里了。

罗盘

接下来看罗盘的实现,罗盘由一个个圆环组成,一个圆环又由内圈、外圈、分隔线、文字、八卦构成。

内外圈

内外圈我们使用两个RingGeometry

RingGeometry官网案例
innerRadius内部半径
outerRadius外部半径
thetaSegments圆环的分段数
phiSegments圆环的分段数
thetaStart起始角度
thetaLength圆心角

通过circle控制内外圆圈的尺寸,circleWidth控制圆圈的线宽

  const circleWidth = [0.1, 0.1]
  const circle = [0, 1];
  circle.forEach((i, j) => {
    const RingGeo = new THREE.RingGeometry(
      innerRing + i,
      innerRing + i + circleWidth[j],
      64,
      1
    );
    const Ring = new THREE.Mesh(RingGeo, material);
    RingGroup.add(Ring);
  });

分隔线

分隔线使用的是PlaneGeometry

PlaneGeometry官网案例
width宽度
height高度
widthSegments宽度分段数
heightSegments高度分段数

关于分隔线,它的长度就是内外圈的差值,所以这里使用外圈的数值,确定与圆心的距离就要使用内圈的数值加上自身长度除2。除此之外,还需要计算分隔线与圆心的夹角。

  for (let i = 0; i < lineNum; i++) {
    const r = innerRing + circle[1] / 2;
    const rad = ((2 * Math.PI) / lineNum) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
    const line = new THREE.Mesh(planeGeo, material);

    line.position.set(x, y, 0);
    line.rotation.set(0, 0, rad + Math.PI / 2);
    RingGroup.add(line);
  }

文字

文字使用的是TextGeometry,定位与分隔线一致,只需要交错开来。

for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      var txtGeo = new THREE.TextGeometry(text[i % text.length], {
        font: font,
        size: size,
        height: 0.001,
        curveSegments: 12,
      });
      txtGeo.translate(offsetX, offsetY, 0);
      var txt = new THREE.Mesh(txtGeo, material);
      txt.position.set(x, y, 0);
      txt.rotation.set(0, 0, rad + -Math.PI / 2);
      RingGroup.add(txtMesh);

不过TextGeometry的使用有一个得注意得前提,我们需要引入字体文件。

const fontLoader = new THREE.FontLoader();
const fontUrl =
  "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
  fontLoader.load(
    fontUrl,
    function (loadedFont) {
      font = loadedFont;
      resolve();
    },
    undefined,
    function (err) {
      reject(err);
    }
  );
});

八卦

圆环中除了文字之外,还能展示八卦,通过传递baguaData给createBagua生成每一个符号。

const baguaData = [
      [1, 1, 1],
      [0, 0, 0],
      [0, 0, 1],
      [0, 1, 0],
      [0, 1, 1],
      [1, 0, 0],
      [1, 0, 1],
      [1, 1, 0],
    ];
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      RingGroup.add(
        createBagua(baguaData[i % 8], x, y, 0 , rad + Math.PI / 2, text[0]),
      );
    }

createBagua参考代码/114-146行 ,和分隔线是一样的,使用了PlaneGeometry只是做了一些位置的设置。

视频贴图

在罗盘外,还有一圈视频,这里是用到了VideoTexture,实现也很简单。唯一得注意的是视频的跨域问题,需要配置video.crossOrigin = "anonymous"

  const videoSrc = [
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = videoSrc[Math.floor(Math.random() * 2)];
  video.crossOrigin = "anonymous";
  const texture = new THREE.VideoTexture(video);
  ...
  
      const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      side: THREE.DoubleSide,
      map: texture,
    });

动画

动画总共分为三个部分,一块是旋转动画,一块是分解动画和入场动画,我们使用gsap实现。

旋转动画

  gsap.to(videoGroup.rotation, {
    duration: 30,
    y: -Math.PI * 2,
    repeat: -1,
    ease: "none",
  });

分解动画

      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        y: Math.random() * 10 - 5,
        delay: 5,
      })
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        delay: 5,
        y: 0,
      })
  }

入场动画

    item.scale.set(1.2, 1.2, 1.2);
    gsap.to(item.scale, {
      duration: 0.8,
      x: 1,
      y: 1,
      repeat: 0,
      ease: "easeInOut",
    });

旋转动画与分解动画可以写在生成函数内,也可以写在添加scene时,但是入场动画只能写到scene后,因为在生成时,动画就添加上了,当我们点击开始的时候才会将其加入场景中,而这时动画可能已经执行了。

总代码

html

<!-- 
灵感来源:一人之下里的八奇技————风后奇门,但是剧中和漫画中施展的罗盘有限,所以就参考了罗盘特效随便排布。
从抖音选取了两段剪辑随机播放。

实现方式:Three.js





 -->




<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
    <canvas class="webgl"></canvas>
    <div class="box">
        <div>大道五十,天衍四九,人遁其一</div>
        <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e80a3fa048e84f02bb5ef5b6b04af87f~tplv-k3u1fbpfcp-no-mark:240:240:240:160.awebp?">
        <div class="btn">推衍中...</div>
    </div>
</body>

</html>

style

*{
  margin: 0;
  padding: 0;
}
body {
  background-color: #3d3f42;
}

.box{
  width: 350px;
  height: 250px;
  background-color: #000;
  position:absolute;
  top: calc(50% - 75px);
  left: calc(50% - 150px);
  border-radius: 10px;
  font-size: 16px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
}
.btn {
  width: 120px;
  height: 35px;
  line-height: 35px;
  color: #fff;
  border: 2px solid #fff;
  border-radius: 10px;
  font-size: 20px;
  transition: 0.5s;
  text-align: center;
  cursor:default;
  opacity: 0.5;
}
img{
  width: 200px;
  height: 150px;
}

js

import * as THREE from "three@0.125.1";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { gsap } from "gsap@3.5.1";
// Canvas
const canvas = document.querySelector("canvas.webgl");
const box = document.querySelector(".box");
const btn = document.querySelector(".btn");
const video = document.createElement("video");

// Scene
const scene = new THREE.Scene();
 
//----------------------

const fontLoader = new THREE.FontLoader();
const fontUrl =
  "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
  fontLoader.load(
    fontUrl,
    function (loadedFont) {
      font = loadedFont;
      resolve();
    },
    undefined,
    function (err) {
      reject(err);
    }
  );
});
const text = {
  五行: ["金", "木", "水", "火", "土"],
  八卦: ["乾", "坤", "震", "巽", "坎", "艮", "离", "兑"],
  数字: ["壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾"],
  天干: ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"],
  地支: [
    "子",
    "丑",
    "寅",
    "卯",
    "辰",
    "巳",
    "午",
    "未",
    "申",
    "酉",
    "戌",
    "亥",
  ],
  方位: [
    "甲",
    "卯",
    "乙",
    "辰",
    "巽",
    "巳",
    "丙",
    "午",
    "丁",
    "未",
    "坤",
    "申",
    "庚",
    "酉",
    "辛",
    "戍",
    "干",
    "亥",
    "壬",
    "子",
    "癸",
    "丑",
    "艮",
    "寅",
  ],
  节气: [
    "立  春",
    "雨  水",
    "惊  蛰",
    "春  分",
    "清  明",
    "谷  雨",
    "立  夏",
    "小  满",
    "芒  种",
    "夏  至",
    "小  暑",
    "大  暑",
    "立  秋",
    "处  暑",
    "白  露",
    "秋  分",
    "寒  露",
    "霜  降",
    "立  冬",
    "小  雪",
    "大  雪",
    "冬  至",
    "小  寒",
    "大  寒",
  ],
  天星: [
    "天辅",
    "天垒",
    "天汉",
    "天厨",
    "天市",
    "天掊",
    "天苑",
    "天衡",
    "天官",
    "天罡",
    "太乙",
    "天屏",
    "太微",
    "天马",
    "南极",
    "天常",
    "天钺",
    "天关",
    "天潢",
    "少微",
    "天乙",
    "天魁",
    "天厩",
    "天皇",
  ],
  天干1: [
    "甲",
    " ",
    "乙",
    " ",
    "丙",
    " ",
    "丁",
    " ",
    "戊",
    " ",
    "己",
    " ",
    "庚",
    " ",
    "辛",
    " ",
    "壬",
    " ",
    "癸",
    " ",
    "甲",
    " ",
    "乙",
    " ",
  ],
  地支1: [
    "子",
    " ",
    "丑",
    " ",
    "寅",
    " ",
    "卯",
    " ",
    "辰",
    " ",
    "巳",
    " ",
    "午",
    " ",
    "未",
    " ",
    "申",
    " ",
    "酉",
    " ",
    "戌",
    " ",
    "亥",
    " ",
  ],
};
const data = [
  {
    innerRing: 2,
    outerRing: 1.5,
    lineWidth: 0.1,
    circleWidth: [0.1, 0.1],
    lineNum: 8,
    text: [0xffffff],
    offsetX: 0,
    offsetY: 0,
    size: 0.3,
    direction: -1,
    duration: 40,
  },
  {
    innerRing: 3.5,
    outerRing: 0.7,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["方位"],
    offsetX: -0.2,
    offsetY: -0.08,
    size: 0.3,
    direction: 1,
    duration: 10,
  },
  {
    innerRing: 4.2,
    outerRing: 0.7,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["八卦"],
    offsetX: -0.2,
    offsetY: -0.08,
    size: 0.3,
    direction: -1,
    duration: 20,
  },
  {
    innerRing: 4.9,
    outerRing: 1.3,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["方位"],
    offsetX: -0.4,
    offsetY: -0.2,
    size: 0.6,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 6.2,
    outerRing: 0.4,
    lineWidth: 0.15,
    circleWidth: [0, 0],
    lineNum: 60,
    text: text["地支"],
    offsetX: -0.13,
    offsetY: 0.01,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 6.6,
    outerRing: 0.4,
    lineWidth: 0.15,
    circleWidth: [0, 0],
    lineNum: 60,
    text: text["天干"],
    offsetX: -0.13,
    offsetY: -0.07,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 7,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 36,
    text: text["天星"],
    offsetX: -0.27,
    offsetY: -0.03,
    size: 0.2,
    direction: -1,
    duration: 20,
  },
  {
    innerRing: 7.5,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["节气"],
    offsetX: -0.36,
    offsetY: -0.03,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 8,
    outerRing: 0.8,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 48,
    text: text["方位"],
    offsetX: -0.3,
    offsetY: -0.1,
    size: 0.4,
    direction: 1,
    duration: 35,
  },
  {
    innerRing: 8.8,
    outerRing: 0.8,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 32,
    text: text["八卦"],
    offsetX: -0.3,
    offsetY: -0.1,
    size: 0.4,
    direction: -1,
    duration: 60,
  },
  {
    innerRing: 9.6,
    outerRing: 0.4,
    lineWidth: 0.18,
    circleWidth: [0, 0],
    lineNum: 120,
    text: text["地支1"],
    offsetX: -0.13,
    offsetY: 0.01,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 10,
    outerRing: 0.4,
    lineWidth: 0.18,
    circleWidth: [0, 0],
    lineNum: 120,
    text: text["天干1"],
    offsetX: -0.13,
    offsetY: -0.07,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 10.4,
    outerRing: 0.5,
    lineWidth: 0.1,
    circleWidth: [0.1, 0.1],
    lineNum: 60,
    text: text["数字"],
    offsetX: -0.13,
    offsetY: -0.02,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 10.9,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 50,
    text: text["五行"],
    offsetX: -0.13,
    offsetY: -0.02,
    size: 0.2,
    direction: 1,
    duration: 35,
  },
  {
    innerRing: 11.7,
    outerRing: 1,
    lineWidth: 0.1,
    circleWidth: [1, 0],
    lineNum: 64,
    text: [0x000000],
    offsetX: 0,
    offsetY: 0,
    size: 0.3,
    direction: 1,
    duration: 30,
  },
];
const Rings = [];
const duration = [
  0, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7,
];

//Ring
const Ring = ({
  innerRing,
  outerRing,
  lineWidth,
  circleWidth,
  lineNum,
  offsetX,
  offsetY,
  text,
  size,
  direction,
  duration,
}) => {
  const RingGroup = new THREE.Group();
  const circle = [0, outerRing];
  const material = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
  });

  // create ring
  circle.forEach((i, j) => {
    const RingGeo = new THREE.RingGeometry(
      innerRing + i,
      innerRing + circleWidth[j] + i,
      64,
      1
    );
    const Ring = new THREE.Mesh(RingGeo, material);
    RingGroup.add(Ring);
  });

  // create line
  for (let i = 0; i < lineNum; i++) {
    const r = innerRing + circle[1] / 2;
    const rad = ((2 * Math.PI) / lineNum) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
    const line = new THREE.Mesh(planeGeo, material);

    line.position.set(x, y, 0);
    line.rotation.set(0, 0, rad + Math.PI / 2);
    RingGroup.add(line);
  }

  // create text
  if (text.length > 1) {
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      var txtGeo = new THREE.TextGeometry(text[i % text.length], {
        font: font,
        size: size,
        height: 0.001,
        curveSegments: 12,
      });
      txtGeo.translate(offsetX, offsetY, 0);
      var txtMater = new THREE.MeshStandardMaterial({ color: 0xffffff });
      var txtMesh = new THREE.Mesh(txtGeo, txtMater);
      txtMesh.position.set(x, y, 0);
      txtMesh.rotation.set(0, 0, rad + -Math.PI / 2);
      RingGroup.add(txtMesh);
    }
  }

  // create bagua
  if (text.length == 1) {
    const baguaData = [
      [1, 1, 1],
      [0, 0, 0],
      [0, 0, 1],
      [0, 1, 0],
      [0, 1, 1],
      [1, 0, 0],
      [1, 0, 1],
      [1, 1, 0],
    ];
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      RingGroup.add(
        createBagua(baguaData[i % 8], x, y, 0.0001, rad + Math.PI / 2, text[0]),
        createBagua(baguaData[i % 8], x, y, -0.0001, rad + Math.PI / 2, text[0])
      );
    }
  }

  // animation
  {
    gsap.to(RingGroup.rotation, {
      duration: duration,
      z: Math.PI * 2 * direction,
      repeat: -1,
      ease: "none",
    });
    
    const amColor = { r: 1, g: 1, b: 1 };
    const explode = gsap.timeline({ repeat: -1, delay: 5 });
    explode
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        y: Math.random() * 10 - 5,
        delay: 5,
      })
      .to(amColor, {
        r: 133 / 255,
        g: 193 / 255,
        b: 255 / 255,
        duration: 2,
        onUpdate: () =>
          ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
      })
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        delay: 5,
        y: 0,
      })
      .to(amColor, {
        r: 1,
        g: 1,
        b: 1,
        duration: 3,
        onUpdate: () =>
          ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
      });
  }

  // rotate
  RingGroup.rotateX(-Math.PI / 2);
  return RingGroup;
};

//taiji
const createTaiji = (position, scale) => {
  const taiji = new THREE.Group();
  const createCircle = (r, color, thetaStart, thetaLength) => {
    const material = new THREE.MeshBasicMaterial({
      color: color,
      side: THREE.DoubleSide,
    });
    const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
    const circle = new THREE.Mesh(geometry, material);
    return circle;
  };

  const ying = createCircle(1.8, 0x000000, 0, Math.PI);
  const yang = createCircle(1.8, 0xffffff, Math.PI, Math.PI);
  const Lblack = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  const Lwhite = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  const Sblack = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  const Swhite = createCircle(0.25, 0xffffff, 0, Math.PI * 2);

  const Lblack1 = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  const Lwhite1 = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  const Sblack1 = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  const Swhite1 = createCircle(0.25, 0xffffff, 0, Math.PI * 2);

  Lblack.position.set(-0.9, 0, 0.001);
  Lwhite.position.set(0.9, 0, 0.001);
  Swhite.position.set(-0.9, 0, 0.002);
  Sblack.position.set(0.9, 0, 0.002);
  Lblack1.position.set(-0.9, 0, -0.001);
  Lwhite1.position.set(0.9, 0, -0.001);
  Swhite1.position.set(-0.9, 0, -0.002);
  Sblack1.position.set(0.9, 0, -0.002);

  taiji.add(
    ying,
    yang,
    Lblack,
    Lwhite,
    Swhite,
    Sblack,
    Lblack1,
    Lwhite1,
    Swhite1,
    Sblack1
  );
  gsap.to(taiji.rotation, {
    duration: 30,
    z: Math.PI * 2,
    repeat: -1,
    ease: "none",
  });
  taiji.rotateX(-Math.PI / 2);
  taiji.position.set(...position);
  taiji.scale.set(...scale);
  return taiji;
};
scene.add(createTaiji([0, 0, 0], [1, 1, 1]));

// bagua
const createBagua = (data, x, y, z, deg, color) => {
  const idx = [-0.32, 0, 0.32];
  const bagua = new THREE.Group();
  const material = new THREE.MeshStandardMaterial({
    color: color,
    side: THREE.DoubleSide,
  });
  data.forEach((i, j) => {
    if (i == 1) {
      const yang = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.2), material);
      yang.position.set(0, idx[j], 0);
      bagua.add(yang);
    }
    if (i == 0) {
      const ying1 = new THREE.Mesh(
        new THREE.PlaneGeometry(0.45, 0.2),
        material
      );
      const ying2 = new THREE.Mesh(
        new THREE.PlaneGeometry(0.45, 0.2),
        material
      );
      ying1.position.set(-0.275, idx[j], 0);
      ying2.position.set(0.275, idx[j], 0);
      bagua.add(ying1, ying2);
    }
  });
  bagua.position.set(x, y, z);
  bagua.rotation.set(0, 0, deg);
  return bagua;
};


const showVideo = () => {
  const videoSrc = [
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = videoSrc[Math.floor(Math.random() * 2)];
  video.crossOrigin = "anonymous";
  const texture = new THREE.VideoTexture(video);
  const videoGroup = new THREE.Group();
  for (let i = 0; i < 8; i++) {
    const r = 25;
    const rad = ((2 * Math.PI) / 8) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(16, 9);
    const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      side: THREE.DoubleSide,
      map: texture,
    });
    const plane = new THREE.Mesh(planeGeo, material);

    plane.position.set(x, 4.5, y);
    if (i % 2 == 0) plane.rotation.set(0, rad + Math.PI / 2, 0);
    else plane.rotation.set(0, rad, 0);
    videoGroup.add(plane);
  }
  gsap.to(videoGroup.rotation, {
    duration: 30,
    y: -Math.PI * 2,
    repeat: -1,
    ease: "none",
  });
  scene.add(videoGroup);
};

//loadFont, Rings
loadFont.then(() => {
  data.forEach((item) => {
    Rings.push(Ring(item));
  });
  btn.innerText = "入 局";
  btn.style.opacity = 1;
  btn.style.cursor = "pointer";
});

//start
const start = function () {
  const showRing = (item) => {
    scene.add(item);
    item.scale.set(1.2, 1.2, 1.2);
    gsap.to(item.scale, {
      duration: 0.8,
      x: 1,
      y: 1,
      repeat: 0,
      ease: "easeInOut",
    });
  };
  const tl = gsap.timeline();
  Rings.forEach((item, idx) => {
    tl.to(".webgl", { duration: duration[idx] }).call(() => {
      showRing(item);
    });
  });
};



btn.addEventListener("click", () => {
  box.style.display = "none";
  start();
  showVideo();
  video.play();
  video.loop = true;
});


//----------------------
 

//Light
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
 

//Sizes
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};


// Camera
const camera = new THREE.PerspectiveCamera(
  75,
  sizes.width / sizes.height,
  1,
  1000
);
camera.position.y = 10;
camera.position.x = 10;
camera.position.z = 10;
camera.lookAt(scene.position);
scene.add(camera);


//Renderer
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
  alpha: true,
});

renderer.setSize(sizes.width, sizes.height);


//controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.maxDistance = 50;
controls.enablePan = false;

const tick = () => {
  renderer.render(scene, camera);
  controls.update();
  window.requestAnimationFrame(tick);
};
tick();


window.addEventListener("resize", () => {
  sizes.height = window.innerHeight;
  sizes.width = window.innerWidth;

  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(window.devicePixelRatio);
});

本文转载于:

https://juejin.cn/post/7220629398965108794

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

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

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

相关文章

教你一招轻松搞定大量视频滚动字幕

如果你也想批量快速的给大量视频画面添加上滚动字幕的话&#xff0c;那你一定要看这个教程&#xff0c;今天小编要给大家分享一个可以批量给大量视频添加滚动字幕的简单方法&#xff0c;有需要的小伙伴们可以一起来看看具体的操作步骤&#xff01; 首先我们来看看用这个工具添加…

c语言-指针

指针详解 ​ 这段时间在看 Linux内核&#xff0c;深觉C语言功底不扎实&#xff0c;很多代码都看不太懂&#xff0c;深入学习巩固C语言的知识很有必要。先从指针开始。 什么是指针 ​ C语言里&#xff0c;变量存放在内存中&#xff0c;而内存其实就是一组有序字节组成的数组&…

跨域时怎么处理 cookie?

前言 一个请求从发出到返回&#xff0c;需要浏览器和服务端的协调配合。浏览器要把自己的请求参数带给服务端&#xff0c;服务端校验参数之后&#xff0c;除了返回数据&#xff0c;也可能会顺便把请求是否缓存&#xff0c;cookie等信息告诉浏览器。当请求是跨域请求的时候&…

uniapp仿淘宝购物车demo

项目是基于uview2.0的ui组件&#xff0c;并且在一定程度上修改过原本组件的代码&#xff08;app-navbar是使用u-navbar在进行二次封装的组件&#xff1b;u-number-box也进行了修改&#xff09;&#xff0c;符合项目需求&#xff08;这个看个人项目需求在进行修改&#xff09; u…

【 在线音乐平台(onlinemusic) 】

文章目录 一、核心功能二、效果演示三、创建项目四、数据库设计及配置数据库4.1 数据库和表设计4.2 配置连接数据库 五、创建配置类六、具体功能实现6.1 注册模块6.2 登录模块拓展&#xff1a;登录注册加密(MD5&#xff0c;BCrypt) 6.3 退出模块6.4 上传音乐模块知识拓展1&…

RabbitMQ详解(六):RabbitMQ集群搭建

集群 官方参考文档&#xff1a;https://www.rabbitmq.com/clustering.html RabbitMQ这款消息队列中间件产品本身是基于Erlang编写&#xff0c;Erlang语言天生具备分布式特性&#xff08;通过同步Erlang集群各节点的magic cookie来实现&#xff09;。因此&#xff0c;RabbitMQ天…

什么是柔性玻璃?

柔性玻璃(Flexible glass)是一种新型薄膜玻璃基板(Thin film glass substrate)材料&#xff0c;厚度极薄可以弯曲。 柔性玻璃定义有广义和狭义之分&#xff1a; 广义柔性玻璃泛指所有制成微米尺寸具有可弯曲特性的玻璃材料&#xff0c;如玻璃纤维、光纤、玻璃棉、玻璃布等。这些…

第10课【STM32 USB通讯协议实战】HID键盘+CDC虚拟串口组合设备

目录 前言USB设备类别未定义设备设备描述符/配置描述符分析如何配置从机类型如何配置设备专用的描述符如何配置从机端点 HID设备特点设备描述符/配置描述符分析HID报文描述符短条目前缀可选数据表现形式 层次结构实例分析总结 CDC设备特点设备描述符/配置描述符分析设备类特定请…

【LED子系统】四、核心层详解(一)

个人主页&#xff1a;董哥聊技术 我是董哥&#xff0c;嵌入式领域新星创作者 创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01; 文章目录 1、前言2、leds_init分析2.1 相关数据结构2.1.1 class 2.2 实现流程 3、leds_class_dev_pm_ops分析…

Mysql出现问题:ERROR 1062 (23000): Duplicate entry ‘‘ for key ‘PRIMARY‘解决方案

回城传送–》《数据库问题解决方案》 ❤️作者主页:小虚竹 ❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家🏆,华为云享专家🏆,掘金年度人气作者🏆,阿里云专家博主🏆,51CTO专家博主🏆 ❤️技术活,该赏 ❤️点赞 👍 收藏 ⭐再看,养成…

QT中的模态对话框及非模态对话框

QT中的模态对话框及非模态对话框 [1] QT中的模态对话框及非模态对话框[2] Qt工作笔记-主界面往模式对话框emit信号&#xff0c;有注意的问题正常情况下&#xff1a;不正常情况下&#xff1a;下面给出正常情况下的代码&#xff1a; [1] QT中的模态对话框及非模态对话框 原文链接…

KVM软件安装/Guest OS图形模式安装

KVM软件安装 首先你的Linux操作系统得带有图形化界面 虚拟机开启硬件虚拟化 关闭防火墙和selinux [rootserver-d ~]# systemctl stop firewalld [rootserver-d ~]# systemctl disable firewalld Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.ser…

RK3568修改调试串口的波特率

概述 使用了临滴 RK3568 开发板,其调试串口的默认波特率是 1500000 &#xff0c;但并不是所有的 USB 转 TTL 都能使用这么高的波特率&#xff0c;所以我们就将波特率修改为 115200 这个比较通用的波特率。 RK3568 调试串口修改波特率的方法 ddr 运行阶段串口波特率的修改 ddr…

linux利用定时任务提权

背景&#xff1a; 运维为了防止数据丢失等&#xff0c;写个定时任务进行数据的打包压缩。由于数据打包压缩命令为tar&#xff0c;tar可以尝试加参数调用其他命令执行。 压缩命令&#xff1a;tar zxf 1.tar.gz /var/www/* 查看定时任务&#xff1a;cat /etc/crontab root权限下…

WordPress入门之WordPress站点基本设置

在Wordpress站点搭建过程中,我们需要快速去熟悉Wordpress,并进行一些简单的基本设置,在开始设置之前,大家可以先熟悉左边的菜单栏的每个选项,了解它们都是做什么的,今天就简单为大家介绍Wordpress入门之Wordpress站点基本设置。 一、设置个人资料 建议大家完善基本信息…

电容笔一定要防误触吗?苹果平板平替电容笔排行

至于用ipad作为学习工具的学生们&#xff0c;更是将它当成了一种必不可少的工具。但是&#xff0c;由于苹果原装电容笔的价格过高&#xff0c;没有人能负担得起。所以&#xff0c;最好的办法就是使用普通的电容笔。我是IPAD的忠实用户&#xff0c;也是数码爱好者&#xff0c;这…

10.BOM浏览器对象模型

BOM 浏览器对象模型 1. BOM 概述 1.1 什么是 BOM BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;它提供了独立于内容而与**浏览器窗口进行交互的对象&#xff0c;其核心对象是 window BOM 由一系列相关的对象构成&#xff0c;并且每个对象都…

在Bamboo上怎么使用iOS的单元测试 | 京东云技术团队

作者&#xff1a;京东零售 吴滔 本教程将使用北汽登录模块为例&#xff0c;一步一步和大家一起搭建单元测试用例&#xff0c;并在Bamboo上跑起来&#xff0c;最终测试结果和代码覆盖率会Bamboo上汇总。 模块名称&#xff1a;BQLoginModule,是通过iBiu创建的一个模块工程 一 建…

浅尝Kubernetes

第一节 内容编排与Kubernetes 为什么要用k8s 集群环境容器部署的困境&#xff0c;假设我们有数十台服务器。分别部署Nginx&#xff0c;redis&#xff0c;mysql&#xff0c;业务服务。如何合理的分配这些资源。这里就需要用到容器编排 容器编排 在实际集群环境下&#xff0…

JAVA-抽象类和接口

文章目录 前言 大家好呀,今天给大家带来抽象类和接口的讲解,那么废话不多说,跟着我一起去学习吧! 1.1抽象类的概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类…