欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

旋转球体(bauble generator)

程序员文章站 2024-03-24 11:12:40
...

旋转球体

更多有趣示例 尽在小红砖社区

示例

旋转球体(bauble generator)

CSS

*
  box-sizing border-box
body
  background #111
  min-height 100vh

JS

const {
  THREE,
  gsap: { to, timeline, from, fromTo },
  dat: { GUI },
} = window
const {
  Group,
  Scene,
  PerspectiveCamera,
  WebGLRenderer,
  OrbitControls,
  PointLight,
} = THREE
let CAMERA
let CAM_CONTROLS
let BAUBLE
let X_SPIN
let Y_SPIN
let Z_SPIN
let X_ROTATION
let Y_ROTATION
let Z_ROTATION
const CONFIG = {
  UNIT: 1,
  FOV: 75,
  NEAR: 0.1,
  FAR: 1000,
  RADIUS: 15,
  DOTS: 15,
  RINGS: 9,
  TRANSITION: 1,
  RESET_CAMERA: () => {
    CAM_CONTROLS.reset()
  },
  LIGHT: {
    COLOR: 0xffffff,
    X: -20,
    Y: 20,
    Z: 40,
    HELP: false,
  },
  SPIN: {
    X: 0.01,
    Y: 0.001,
    Z: 0.01,
    STOP: () => {
      CONFIG.SPIN.X = 0
      CONFIG.SPIN.Y = 0
      CONFIG.SPIN.Z = 0
      X_SPIN.setValue(0)
      Y_SPIN.setValue(0)
      Z_SPIN.setValue(0)
    },
  },
  ROTATION: {
    X: 300,
    Y: 0,
    Z: 0,
    RESET: () => {
      BAUBLE.rotation.x = 300
      BAUBLE.rotation.y = 0
      BAUBLE.rotation.z = 0
      X_ROTATION.setValue(300)
      Y_ROTATION.setValue(0)
      Z_ROTATION.setValue(0)
    },
  },
  COLORS: {
    BODY: 0xff0000,
    TOP: 0xffffff,
    DOTS: 0xffffff,
  },
}


// These are the bauble colors
let TL
BAUBLE = new Group()
CAMERA = new PerspectiveCamera(
  CONFIG.FOV,
  window.innerWidth / window.innerHeight,
  CONFIG.NEAR,
  CONFIG.FAR
)
CAMERA.position.set(0, 0, 65)
// Set the scene
const SCENE = new Scene()
// Create the renderer
const RENDERER = new WebGLRenderer({
  alpha: false,
  logarithmicDepthBuffer: false,
  pixelRatio: window.devicePixelRatio || 1,
  antialias: true,
})
RENDERER.setSize(window.innerWidth, window.innerHeight)
// Set orbital controls so user can move things around
CAM_CONTROLS = new OrbitControls(CAMERA, RENDERER.domElement)
CAM_CONTROLS.enablePan = false


let DOTS = []
let UPDATING = true


const SPHERE_GEOMETRY = new THREE.SphereGeometry(CONFIG.RADIUS, 32, 32)
const SPHERE_MATERIAL = new THREE.MeshPhongMaterial({
  color: 0xe74c3c,
  opacity: 0.75,
  transparent: true,
  shininess: 50,
  specular: 0xffffff,
  reflectivity: 1,
  combine: THREE.MultiplyOperation,
})
const TOP_MATERIAL = SPHERE_MATERIAL.clone()
TOP_MATERIAL.color = new THREE.Color(0xfafafa)
const DOTS_MATERIAL = SPHERE_MATERIAL.clone()
DOTS_MATERIAL.color = new THREE.Color(0xffffff)
TOP_MATERIAL.opacity = DOTS_MATERIAL.opacity = 1
const GLOBAL_SPHERE = new THREE.Mesh(SPHERE_GEOMETRY, SPHERE_MATERIAL)
GLOBAL_SPHERE.scale.x = GLOBAL_SPHERE.scale.y = GLOBAL_SPHERE.scale.z = 0.1
const BAUBLE_TOP_GEOMETRY = new THREE.CylinderGeometry(4, 4, 6, 64)
const BAUBLE_TOP = new THREE.Mesh(BAUBLE_TOP_GEOMETRY, TOP_MATERIAL)
BAUBLE_TOP.rotateX(90 * (Math.PI / 180))
BAUBLE_TOP.translateY(CONFIG.RADIUS)
const TOP_RING_GEO = new THREE.TorusGeometry(3, 0.5, 16, 100)
const TOP_RING = new THREE.Mesh(TOP_RING_GEO, TOP_MATERIAL)
TOP_RING.rotateX(0 * (Math.PI / 180))
TOP_RING.translateY(5)
BAUBLE_TOP.add(TOP_RING)
GLOBAL_SPHERE.add(BAUBLE_TOP)
BAUBLE.add(GLOBAL_SPHERE)


const addRing = (RADIUS, HEIGHT) => {
  for (let d = 0; d < CONFIG.DOTS; d++) {
    const SPHERE_GEOMETRY = new THREE.SphereGeometry(0.5, 32, 32)
    const SPHERE = new THREE.Mesh(SPHERE_GEOMETRY, DOTS_MATERIAL)


    SPHERE.rotateZ((360 / CONFIG.DOTS) * d * (Math.PI / 180))
    SPHERE.scale.x = SPHERE.scale.y = SPHERE.scale.z = 0.1
    SPHERE.metadata = {
      HEIGHT,
    }
    SPHERE.translateX(RADIUS)
    SPHERE.translateZ(HEIGHT)
    DOTS.push(SPHERE)
    BAUBLE.add(SPHERE)
  }
}


const generateRings = () => {
  DOTS = []
  // Calculate the ring gap
  const GAP =
    CONFIG.RINGS % 2 === 0
      ? CONFIG.RADIUS / (CONFIG.RINGS / 2 + 1)
      : CONFIG.RADIUS / (CONFIG.RINGS / 2)
  // Iterate and create the rings that aren't the center ring
  for (
    let r = 0;
    r < (CONFIG.RINGS % 2 === 0 ? CONFIG.RINGS / 2 : (CONFIG.RINGS - 1) / 2);
    r++
  ) {
    // Generate a ring, work out its position. Add it to the scene ????
    const SPHERE_RADIUS = CONFIG.RADIUS
    // If I want the ring at height 5 that means a cap height of 10 for the equation.
    const HEIGHT = (r + 1) * GAP
    // This is the equation to grab the radius, thanks mathworld.wolfram.com/SphericalCap.html ????
    const RADIUS = Math.sqrt(
      (SPHERE_RADIUS - HEIGHT) * (2 * 15 - (SPHERE_RADIUS - HEIGHT))
    )
    addRing(RADIUS, HEIGHT)
    addRing(RADIUS, -HEIGHT)
  }
  // Add the center ring, if the number of rings is odd here ????
  if (CONFIG.RINGS % 2 !== 0) {
    addRing(CONFIG.RADIUS, 0)
  }
  TL = new timeline({
    onComplete: () => (UPDATING = false),
    onReverseComplete: generateScene,
  })
    .add(
      to(GLOBAL_SPHERE.scale, CONFIG.TRANSITION, {
        x: 1,
        y: 1,
        z: 1,
      }),
      0
    )
    .add(
      fromTo(
        SPHERE_MATERIAL,
        { opacity: 0 },
        {
          opacity: 0.75,
          duration: CONFIG.TRANSITION / 2,
        }
      ),
      0
    )
    .add(
      fromTo(
        [DOTS_MATERIAL, TOP_MATERIAL],
        {
          opacity: 0,
        },
        {
          opacity: 1,
          duration: CONFIG.TRANSITION / 2,
        }
      ),
      0
    )
    .add(
      from(DOTS.map(d => d.position), CONFIG.TRANSITION, {
        x: 0,
        y: 0,
        z: 0,
      }),
      0
    )
    .add(
      to(DOTS.map(d => d.scale), CONFIG.TRANSITION, {
        x: 1,
        y: 1,
        z: 1,
      }),
      0
    )
}
const generateScene = () => {
  while (BAUBLE.children.length > 1) {
    // Clear the children
    BAUBLE.remove(BAUBLE.children[1])
  }
  generateRings()
}
SCENE.add(BAUBLE)
generateScene()
/**
 * LIGHTING
 * Could loop this over a DAT GUI config?
 */ const LIGHT = new PointLight(0xfafafa, 1)
LIGHT.position.set(CONFIG.LIGHT.X, CONFIG.LIGHT.Y, CONFIG.LIGHT.Z)
SCENE.add(LIGHT)
const LIGHT_HELPER = new THREE.PointLightHelper(LIGHT)
/**
 * Set up renderer and scene
 */ BAUBLE.rotation.x = CONFIG.ROTATION.X
BAUBLE.rotation.y = CONFIG.ROTATION.Y
BAUBLE.rotation.z = CONFIG.ROTATION.Z
const animate = () => {
  BAUBLE.rotation.x += CONFIG.SPIN.X
  BAUBLE.rotation.y += CONFIG.SPIN.Y
  BAUBLE.rotation.z += CONFIG.SPIN.Z
  RENDERER.render(SCENE, CAMERA)
  requestAnimationFrame(animate)
}
document.body.appendChild(RENDERER.domElement)
animate() // On window resize, update the aspect ratio and renderer dimensions
window.addEventListener('resize', () => {
  RENDERER.setSize(window.innerWidth, window.innerHeight)
  CAMERA.aspect = window.innerWidth / window.innerHeight
  CAMERA.updateProjectionMatrix()
})
/**
 * Set up dat.GUI to play with different controls
 */ const CONTROLS = new GUI({ closed: false })
const LIGHTS = CONTROLS.addFolder('LIGHT')
const lightXController = LIGHTS.add(CONFIG.LIGHT, 'X', -40, 40)
lightXController.onChange(() => {
  LIGHT.position.set(CONFIG.LIGHT.X, CONFIG.LIGHT.Y, CONFIG.LIGHT.Z)
})
const lightYController = LIGHTS.add(CONFIG.LIGHT, 'Y', -100, 100)
lightYController.onChange(() => {
  LIGHT.position.set(CONFIG.LIGHT.X, CONFIG.LIGHT.Y, CONFIG.LIGHT.Z)
})
const lightZController = LIGHTS.add(CONFIG.LIGHT, 'Z', -100, 100)
lightZController.onChange(() => {
  LIGHT.position.set(CONFIG.LIGHT.X, CONFIG.LIGHT.Y, CONFIG.LIGHT.Z)
})
const lightHelpController = LIGHTS.add(CONFIG.LIGHT, 'HELP')
lightHelpController.onChange(() => {
  if (CONFIG.LIGHT.HELP) {
    SCENE.add(LIGHT_HELPER)
  } else {
    SCENE.remove(LIGHT_HELPER)
  }
})
const SPIN = CONTROLS.addFolder('SPIN')
X_SPIN = SPIN.add(CONFIG.SPIN, 'X', 0, 0.1)
Y_SPIN = SPIN.add(CONFIG.SPIN, 'Y', 0, 0.1)
Z_SPIN = SPIN.add(CONFIG.SPIN, 'Z', 0, 0.1)
SPIN.add(CONFIG.SPIN, 'STOP')
const ROTATION = CONTROLS.addFolder('ROTATION')
X_ROTATION = ROTATION.add(CONFIG.ROTATION, 'X', 0, 360).onChange(
  value => (BAUBLE.rotation.x = value * (Math.PI / 180))
)
Y_ROTATION = ROTATION.add(CONFIG.ROTATION, 'Y', 0, 360).onChange(
  value => (BAUBLE.rotation.y = value * (Math.PI / 180))
)
Z_ROTATION = ROTATION.add(CONFIG.ROTATION, 'Z', 0, 360).onChange(
  value => (BAUBLE.rotation.z = value * (Math.PI / 180))
)
ROTATION.add(CONFIG.ROTATION, 'RESET')
const COLORS = CONTROLS.addFolder('COLORS')
COLORS.addColor(CONFIG.COLORS, 'BODY').onChange(
  value => (SPHERE_MATERIAL.color = new THREE.Color(value))
)
COLORS.addColor(CONFIG.COLORS, 'DOTS').onChange(
  value => (DOTS_MATERIAL.color = new THREE.Color(value))
)
COLORS.addColor(CONFIG.COLORS, 'TOP').onChange(
  value => (TOP_MATERIAL.color = new THREE.Color(value))
)
const CONFIGCONTROLS = CONTROLS.addFolder('CONFIG')
const ringsController = CONFIGCONTROLS.add(CONFIG, 'RINGS', 2, 15, 1)
ringsController.onFinishChange(() => {
  if (!UPDATING) {
    TL.reverse()
  }
})
const dotsController = CONFIGCONTROLS.add(CONFIG, 'DOTS', 2, 20, 1)
dotsController.onFinishChange(() => {
  if (!UPDATING) {
    TL.reverse()
  }
})
CONTROLS.add(CONFIG, 'RESET_CAMERA').name('RESET CAMERA')
CONFIGCONTROLS.add(CONFIG, 'TRANSITION', 0.5, 5, 0.1)