three.js之动画Animation

动画

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";

  const gui = new GUI();

  function init() {
    window.addEventListener("resize", onResize, false);
    document.addEventListener("mousedown", onDocumentMouseDown, false);
    document.addEventListener("mousemove", onDocumentMouseMove, false);

    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const plane = addPlane();
    scene.add(plane);

    const cube = addBox({ color: 0xff0000, width: 4, height: 4, depth: 4 });
    cube.position.set(-10, 5, 0);
    scene.add(cube);

    const sphere = addSphere({ color: 0x7777ff, radius: 4 });
    sphere.position.set(20, 5, 0);
    scene.add(sphere);

    const cylinder = addCylinder({ color: 0x77ff77, tr: 2, br: 2, height: 20 });
    scene.add(cylinder);

    let step = 0;
    let scalingStep = 0;
    const controls = new (function () {
      this.rotationSpeed = 0.02;
      this.bouncingSpeed = 0.03;
      this.scalingSpeed = 0.03;
      this.showRay = false;
    })();
    gui.add(controls, "rotationSpeed", 0, 0.5);
    gui.add(controls, "bouncingSpeed", 0, 0.5);
    gui.add(controls, "scalingSpeed", 0, 0.5);
    gui.add(controls, "showRay").onChange(function (e) {
      if (tube) scene.remove(tube);
    });

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();
    let tube;

    render();

    function render() {
      trackballControls.update(clock.getDelta());

      cube.rotation.x += controls.rotationSpeed;
      cube.rotation.y += controls.rotationSpeed;
      cube.rotation.z += controls.rotationSpeed;

      step += controls.bouncingSpeed;
      sphere.position.x = 20 + 10 * Math.cos(step);
      sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));

      scalingStep += controls.scalingSpeed;
      const scaleX = Math.abs(Math.sin(scalingStep / 4));
      const scaleY = Math.abs(Math.cos(scalingStep / 5));
      const scaleZ = Math.abs(Math.sin(scalingStep / 7));
      cylinder.scale.set(scaleX, scaleY, scaleZ);

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

    function onResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function onDocumentMouseDown(event) {
      let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
      vector = vector.unproject(camera); //将此向量(坐标)从相机的标准化设备坐标 (NDC) 空间投影到世界空间

      // 光线投射Raycaster 用于进行raycasting(光线投射)。光线投射用于进行鼠标拾取
      const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
      const intersects = raycaster.intersectObjects([sphere, cylinder, cube]); //检测所有在射线与这些物体之间,包括或不包括后代的相交部分。
      if (intersects.length > 0) {
        console.log(intersects[0]);
        intersects[0].object.material.transparent = true; //未生效 ???
        intersects[0].object.material.opacity = 0.1;
      }
    }

    function onDocumentMouseMove(event) {
      if (controls.showRay) {
        let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
        vector = vector.unproject(camera);

        const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
        const intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
        if (intersects.length > 0) {
          const points = [];
          points.push(new THREE.Vector3(-30, 29.8, 30));
          points.push(intersects[0].point);

          const mat = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.6 });
          const tubeGeometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), 60, 0.001);

          if (tube) scene.remove(tube);

          if (controls.showRay) {
            tube = new THREE.Mesh(tubeGeometry, mat);
            scene.add(tube);
          }
        }
      }
    }
  }

  init();
</script>

TWEEN

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import * as TWEEN from "three/addons/libs/tween.module.js";
  import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight } from "./init.js";

  function init() {
    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const posSrc = { pos: 1 };
    const tween = new TWEEN.Tween(posSrc).to({ pos: 0 }, 2000);
    tween.easing(TWEEN.Easing.Bounce.InOut);
    const tweenBack = new TWEEN.Tween(posSrc).to({ pos: 1 }, 2000);
    tweenBack.easing(TWEEN.Easing.Bounce.InOut);
    tweenBack.chain(tween);
    tween.chain(tweenBack);
    tween.start();

    let group;
    const loader = new PLYLoader();
    loader.load("./models/ply/ascii/dolphins.ply", function (geometry) {
      const origPosition = geometry.attributes["position"].clone();
      geometry.origPosition = origPosition;

      const material = new THREE.PointsMaterial({
        color: 0xffffff,
        size: 1,
        opacity: 0.6,
        transparent: true,
        blending: THREE.AdditiveBlending,
        depthWrite: false,
        map: generateSprite(),
      });
      group = new THREE.Points(geometry, material);
      group.scale.set(0.05, 0.05, 0.05);
      scene.add(group);
    });

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();

    render();

    function render() {
      trackballControls.update(clock.getDelta());

      TWEEN.update();

      if (typeof group != "undefined") {
        const positionArray = group.geometry.attributes["position"];
        const origPosition = group.geometry.origPosition;
        for (let i = 0; i < positionArray.count; i++) {
          const oldPosX = origPosition.getX(i);
          const oldPosY = origPosition.getY(i);
          const oldPosZ = origPosition.getZ(i);
          positionArray.setX(i, oldPosX * posSrc.pos);
          positionArray.setY(i, oldPosY * posSrc.pos);
          positionArray.setZ(i, oldPosZ * posSrc.pos);
        }
        positionArray.needsUpdate = true;
      }

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

  function generateSprite() {
    const canvas = document.createElement("canvas");
    canvas.width = 16;
    canvas.height = 16;
    const context = canvas.getContext("2d");

    const gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
    gradient.addColorStop(0, "rgba(255,255,255,1)");
    gradient.addColorStop(0.2, "rgba(0,255,255,1)");
    gradient.addColorStop(0.4, "rgba(0,0,64,1)");
    gradient.addColorStop(1, "rgba(0,0,0,1)");
    context.fillStyle = gradient;
    context.fillRect(0, 0, canvas.width, canvas.height);

    const texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
    return texture;
  }

  init();
</script>

AnimationMixer

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";

  function init() {
    const gui = new GUI();

    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    camera.position.set(0, 15, 70);
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();

    let mixer = new THREE.AnimationMixer();
    let animationClip;
    let clipAction;
    let mesh;
    let controls;
    const mixerControls = {
      time: 0,
      timeScale: 1,
      stopAllAction: function () {
        mixer.stopAllAction();
      },
    };

    const loader = new GLTFLoader();
    loader.load("./models/Horse.glb", function (res) {
      res.scene.scale.set(0.1, 0.1, 0.1);
      res.scene.translateY(-3);
      res.scene.rotateY(-0.3 * Math.PI);
      scene.add(res.scene);

      // AnimationMixer 动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
      mixer = new THREE.AnimationMixer(res.scene);
      animationClip = res.animations[0];
      clipAction = mixer.clipAction(animationClip).play();
      animationClip = clipAction.getClip();

      enableControls();
    });

    render();

    function render() {
      const delta = clock.getDelta();
      trackballControls.update(delta);

      requestAnimationFrame(render);
      renderer.render(scene, camera);

      if (mixer && clipAction) {
        mixer.update(delta);
        controls.time = mixer.time;
        controls.effectiveTimeScale = clipAction.getEffectiveTimeScale();
        controls.effectiveWeight = clipAction.getEffectiveWeight();
      }
    }

    function enableControls() {
      const mixerFolder = gui.addFolder("AnimationMixer");
      mixerFolder.add(mixerControls, "time").listen();
      mixerFolder.add(mixerControls, "timeScale", 0, 5).onChange(function (timeScale) {
        mixer.timeScale = timeScale;
      });
      mixerFolder.add(mixerControls, "stopAllAction").listen();

      controls = addClipActionFolder("ClipAction 1", gui, clipAction, animationClip);
    }
  }

  init();

  function computeSize(obj) {
    const cbox = new THREE.Box3().setFromObject(obj);
    const size = cbox.getSize(new THREE.Vector3());

    if (obj instanceof THREE.Scene) {
      console.log("scene size", size);
    } else {
      console.log("obj size", size);
    }
  }

  function addClipActionFolder(folderName, gui, clipAction, animationClip) {
    const actionControls = {
      keyframe: 0,
      time: 0,
      timeScale: 1,
      repetitions: Infinity,
      // warp
      warpStartTimeScale: 1,
      warpEndTimeScale: 1,
      warpDurationInSeconds: 2,
      warp: function () {
        clipAction.warp(actionControls.warpStartTimeScale, actionControls.warpEndTimeScale, actionControls.warpDurationInSeconds);
      },
      fadeDurationInSeconds: 2,
      fadeIn: function () {
        clipAction.fadeIn(actionControls.fadeDurationInSeconds);
      },
      fadeOut: function () {
        clipAction.fadeOut(actionControls.fadeDurationInSeconds);
      },
      effectiveWeight: 0,
      effectiveTimeScale: 0,
    };

    const actionFolder = gui.addFolder(folderName);
    actionFolder.add(clipAction, "clampWhenFinished").listen();
    actionFolder.add(clipAction, "enabled").listen();
    actionFolder.add(clipAction, "paused").listen();
    actionFolder.add(clipAction, "loop", { LoopRepeat: THREE.LoopRepeat, LoopOnce: THREE.LoopOnce, LoopPingPong: THREE.LoopPingPong }).onChange(function (e) {
      if (e == THREE.LoopOnce || e == THREE.LoopPingPong) {
        clipAction.reset();
        clipAction.repetitions = undefined;
        clipAction.setLoop(parseInt(e), undefined);
      } else {
        clipAction.setLoop(parseInt(e), actionControls.repetitions);
      }
    });
    actionFolder
      .add(actionControls, "repetitions", 0, 100)
      .listen()
      .onChange(function (e) {
        if (clipAction.loop == THREE.LoopOnce || clipAction.loop == THREE.LoopPingPong) {
          clipAction.reset();
          clipAction.repetitions = undefined;
          clipAction.setLoop(parseInt(clipAction.loop), undefined);
        } else {
          clipAction.setLoop(parseInt(e), actionControls.repetitions);
        }
      });
    actionFolder.add(clipAction, "time", 0, animationClip.duration, 0.001).listen();
    actionFolder.add(clipAction, "timeScale", 0, 5, 0.1).listen();
    actionFolder.add(clipAction, "weight", 0, 1, 0.01).listen();
    actionFolder.add(actionControls, "effectiveWeight", 0, 1, 0.01).listen();
    actionFolder.add(actionControls, "effectiveTimeScale", 0, 5, 0.01).listen();
    actionFolder.add(clipAction, "zeroSlopeAtEnd").listen();
    actionFolder.add(clipAction, "zeroSlopeAtStart").listen();
    actionFolder.add(clipAction, "stop");
    actionFolder.add(clipAction, "play");
    actionFolder.add(clipAction, "reset");
    actionFolder.add(actionControls, "warpStartTimeScale", 0, 10, 0.01);
    actionFolder.add(actionControls, "warpEndTimeScale", 0, 10, 0.01);
    actionFolder.add(actionControls, "warpDurationInSeconds", 0, 10, 0.01);
    actionFolder.add(actionControls, "warp");
    actionFolder.add(actionControls, "fadeDurationInSeconds", 0, 10, 0.01);
    actionFolder.add(actionControls, "fadeIn");
    actionFolder.add(actionControls, "fadeOut");

    return actionControls;
  }
</script>

 

posted @ 2024-05-23 16:54  carol2014  阅读(9)  评论(0编辑  收藏  举报