Skip to content

风车

一个风车的案例,模型用代码编写,可以改变风车的页片角度,不需要模型师修改模型点击下载案例

js
import "./style.scss";

import { DirectionalLight, AmbientLight, Clock, MeshLambertMaterial, CircleGeometry, ExtrudeGeometry, Shape, LatheGeometry, Path, Mesh, Vector2 } from "three";
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
import { Pane } from "tweakpane";

import { scene, camera, renderer, controls } from "./base.js";

const clock = new Clock();
let t = 0;

function animate() {
  // 获取时间增量
  const dt = clock.getDelta();
  t += dt;
  // 旋转速度与时间增量相关
  windmillRotor.rotation.z += PARAMS.speed * dt;
  controls.update();
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

camera.position.set(5, -5, 20);
const light = new DirectionalLight(0xffffff, Math.PI * 1.5);
light.position.setScalar(1);
scene.add(light, new AmbientLight(0xffffff, Math.PI * 0.5));

class WindmillRotor extends Mesh {
  // mainRadius 风页长度
  // bladeAmount 风页数量
  // bulbRadius 中间转轴大小
  constructor(mainRadius = 7, bladeAmount = 3, bulbRadius = 0.5) {
    super(
      mergeGeometries([
        new LatheGeometry(
          new Path()
            .moveTo(bulbRadius, 0)
            .lineTo(bulbRadius, 0)
            .absarc(0, 1, bulbRadius, 0, Math.PI * 0.5)
            .getPoints(50),
          72
        ).rotateX(Math.PI * 0.5),
        new CircleGeometry(bulbRadius, 72).rotateX(Math.PI),
      ]).translate(0, 0, -0.5),
      new MeshLambertMaterial({ color: 0xeeeeee })
    );

    const bladeThickness = 0.025;
    // 风页模型数据
    const bladeG = new ExtrudeGeometry(
      new Shape().moveTo(0, bulbRadius).splineThru(
        [
          [0, mainRadius],
          [0.1, mainRadius],
          [0.5, bulbRadius + 1],
          [0.1, bulbRadius],
        ].map((p) => {
          return new Vector2(...p);
        })
      ),
      {
        steps: 1, // 用于沿着挤出样条的深度细分的点的数量,默认值为1
        depth: 0.001, // 挤出的形状的深度,默认值为1
        bevelEnabled: true, // 对挤出的形状应用是否斜角,默认值为true
        bevelThickness: bladeThickness, // 设置原始形状上斜角的厚度。默认值为0.2
        bevelSize: bladeThickness, // 斜角与原始形状轮廓之间的延伸距离
      }
    )
      .translate(0, 0, -bladeThickness)
      .rotateY(Math.PI * 0.5);

    const bladeM = new MeshLambertMaterial({ color: 0x444444 });
    // bladeAmount 生成三个风页
    this.blades = Array.from({ length: bladeAmount }, (_, idx) => {
      const blade = new Mesh(bladeG, bladeM);
      blade.rotation.z = idx * ((Math.PI * 2) / bladeAmount);
      blade.rotation.order = "ZYX"; // important!
      this.add(blade);
      return blade;
    });
    this.setBladesAngle(Math.PI * 0.25);
  }

  setBladesAngle(angle) {
    this.blades.forEach((b) => {
      b.rotation.y = angle;
    });
  }
}

const windmillRotor = new WindmillRotor();
scene.add(windmillRotor);

const PARAMS = {
  angle: 1,
  speed: 0.5,
};

const pane = new Pane();

// 风页角度
pane.addBinding(PARAMS, "angle", {
  min: 0,
  max: 2,
});

// 转动速度
pane.addBinding(PARAMS, "speed", {
  min: 0,
  max: 20,
});

pane.on("change", (ev) => {
  if (ev.target.key === "angle") {
    windmillRotor.setBladesAngle(ev.value);
  }
});

应用场景

借鉴这个思路,某些模型代码生成,会方便很多。