基于p5.js实现WebGL 3D物体鼠标悬停Y轴180度旋转
解决p5.js WebGL 3D物体悬停旋转及批量重置的方案
核心思路
不用在创建物体阶段提前调用rotate(),而是给每个物体维护独立的旋转状态变量,在draw()循环中根据状态动态应用旋转。初始时状态为0,物体保持静止;鼠标悬停时触发状态变更,驱动平滑旋转过渡;最后检测所有物体的完成状态,统一触发重置动画。
实现步骤与代码示例
1. 定义物体状态结构
用数组存储每个物体的位置、当前旋转角度、目标角度、旋转状态等,确保每个物体的独立性:
let objects = []; let resetting = false; // 标记是否处于全局重置阶段 function setup() { createCanvas(800, 600, WEBGL); // 初始化4个物体,分布在不同X轴位置 objects = [ { x: -200, y: 0, z: 0, currentRot: 0, targetRot: 0, isRotating: false, isCompleted: false }, { x: -60, y: 0, z: 0, currentRot: 0, targetRot: 0, isRotating: false, isCompleted: false }, { x: 60, y: 0, z: 0, currentRot: 0, targetRot: 0, isRotating: false, isCompleted: false }, { x: 200, y: 0, z: 0, currentRot: 0, targetRot: 0, isRotating: false, isCompleted: false } ]; }
2. 鼠标悬停检测(3D简化版)
针对平面分布的物体,将3D位置转换为屏幕坐标,通过2D范围判断鼠标是否悬停:
function checkMouseHover(obj) { // 把物体3D坐标转成屏幕X坐标 const screenX = createVector(obj.x, obj.y, obj.z).screenX(); // 假设物体宽度为100,检测鼠标是否落在投影范围内 return mouseX > screenX - 50 && mouseX < screenX + 50; }
如果是不规则复杂3D物体,可改用射线检测(raycast)提升精度,上述简化版足够应对基础场景。
3. 旋转逻辑与物体绘制
在draw()中处理状态更新、平滑旋转过渡,再逐个绘制物体:
function draw() { background(220); orbitControl(); // 可选:允许拖拽旋转相机查看3D效果 // 检测是否所有物体都完成旋转,触发全局重置 const allCompleted = objects.every(obj => obj.isCompleted); if (allCompleted && !resetting) { resetting = true; objects.forEach(obj => { obj.targetRot = 0; obj.isRotating = true; obj.isCompleted = false; }); } // 遍历处理每个物体的旋转与绘制 objects.forEach(obj => { // 鼠标悬停触发旋转(未在旋转、未完成、未处于重置阶段时) if (checkMouseHover(obj) && !obj.isRotating && !obj.isCompleted && !resetting) { obj.targetRot = PI; // 180度(p5.js使用弧度) obj.isRotating = true; } // 平滑过渡旋转角度 if (obj.isRotating) { // 用lerp实现插值,0.05控制旋转速度,数值越大转得越快 obj.currentRot = lerp(obj.currentRot, obj.targetRot, 0.05); // 角度接近目标时,停止旋转并标记状态 if (abs(obj.currentRot - obj.targetRot) < 0.01) { obj.currentRot = obj.targetRot; obj.isRotating = false; obj.isCompleted = (obj.targetRot === PI); // 重置完成后清除全局重置标记 if (resetting && obj.targetRot === 0) { obj.isCompleted = false; resetting = objects.some(obj => obj.isRotating); } } } // 绘制物体:用push/pop隔离每个物体的变换状态 push(); translate(obj.x, obj.y, obj.z); rotateY(obj.currentRot); // 动态应用当前旋转角度 drawComplexObject(); // 替换成你的复杂3D物体绘制函数 pop(); }); } // 示例复杂物体(替换成你自己的3D模型/绘制逻辑) function drawComplexObject() { push(); box(100); translate(0, 0, 50); cylinder(20, 80); pop(); }
关键要点
- 状态驱动旋转:通过
currentRot(当前角度)和targetRot(目标角度)控制旋转,初始时两者均为0,物体静止;悬停时修改目标角度,在draw()循环中逐步插值过渡,实现平滑旋转。 - 独立状态管理:每个物体的状态单独存储,避免相互干扰。
- 重置触发逻辑:用
every()检测所有物体的完成状态,统一触发重置动画,同样通过插值过渡回到初始角度。 - 变换隔离:每个物体绘制时用
push()和pop()包裹,防止当前物体的旋转、平移操作影响其他物体。
内容的提问来源于stack exchange,提问作者torchic
相关产品推荐
相关产品推荐

