You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用p5.js实现点在3D球体表面沿大圆无畸变移动?

实现球面无畸变行走的解决方案

原有实现的问题

  • 直接对球坐标的经纬度做线性增量更新,极区附近经度每单位角度对应的球面弧长会随纬度升高而缩短,自然会出现畸变
  • 这种更新方式的移动路径也不是大圆,不符合球面行走的直观预期

核心实现思路

抛弃球坐标增量更新逻辑,改用三维向量+轴角旋转实现:

  1. 存储单位长度的三维向量表示当前点在球面上的位置,该向量始终和球心指向表面点的法线重合
  2. 存储当前的前进方向向量,该向量始终和位置向量垂直(处于球面该点的切平面上)
  3. 转向操作:将前进方向绕位置向量(法线)旋转对应角度,实现原地转向不位移
  4. 移动操作:将位置向量绕「前进方向的垂直切向量」旋转对应步长角度,移动路径自然是大圆,全程无经纬度映射带来的畸变

修改后的完整可运行代码

var scaler = 100;
var moveSpeed = 0.02; // 每帧移动的角度(弧度)
var turnSpeed = PI/64; // 每帧转向的角度

var MOVE_FORWARD = false;
var TURN_LEFT = false;
var TURN_RIGHT = false;

// 核心状态:位置(单位向量)、前进方向(单位向量,和位置垂直)
let pos;
let forward;

function setup() {
  createCanvas(400, 400, WEBGL);
  // 初始位置:(1,0,0) 单位向量
  pos = createVector(1, 0, 0);
  // 初始前进方向:沿经度增加方向
  forward = createVector(0, 1, 0);
  background(220);
}

function keyPressed(){
  if(key == "w") MOVE_FORWARD = true;
  else if(keyCode == LEFT_ARROW) TURN_LEFT = true;
  else if(keyCode == RIGHT_ARROW) TURN_RIGHT = true;
}

function keyReleased(){
  if(key == "w") MOVE_FORWARD = false;
  else if(keyCode == LEFT_ARROW) TURN_LEFT = false;
  else if(keyCode == RIGHT_ARROW) TURN_RIGHT = false;
}

// 轴角旋转函数:将向量v绕轴axis旋转angle弧度
function rotateVector(v, axis, angle) {
  let cos = Math.cos(angle);
  let sin = Math.sin(angle);
  // 罗德里格斯旋转公式
  let x = axis.x, y = axis.y, z = axis.z;
  let cx = v.x * (cos + x*x*(1-cos)) + v.y * (x*y*(1-cos) - z*sin) + v.z * (x*z*(1-cos) + y*sin);
  let cy = v.x * (y*x*(1-cos) + z*sin) + v.y * (cos + y*y*(1-cos)) + v.z * (y*z*(1-cos) - x*sin);
  let cz = v.x * (z*x*(1-cos) - y*sin) + v.y * (z*y*(1-cos) + x*sin) + v.z * (cos + z*z*(1-cos));
  return createVector(cx, cy, cz).normalize();
}

function draw() {
  background(220);
  
  // 处理转向
  if(TURN_LEFT) {
    // 绕位置向量(法线)顺时针旋转
    forward = rotateVector(forward, pos, turnSpeed);
  }
  if(TURN_RIGHT) {
    forward = rotateVector(forward, pos, -turnSpeed);
  }
  
  // 处理前进
  if(MOVE_FORWARD) {
    // 旋转轴是前进方向叉乘位置向量,得到切平面上垂直前进的右方向
    let axis = p5.Vector.cross(forward, pos).normalize();
    // 位置绕轴旋转移动步长
    pos = rotateVector(pos, axis, moveSpeed);
    // 前进方向也同步旋转更新
    forward = rotateVector(forward, axis, moveSpeed);
  }
  
  // 绘制球体和当前点
  sphere(scaler);
  push();
  translate(pos.x * scaler, pos.y * scaler, pos.z * scaler);
  fill(255,0,0);
  sphere(5);
  pop();
}

扩展功能说明

如果要添加左右平移、后退功能,逻辑也很简单:

  • 后退就是把前进的旋转角度取负即可
  • 左右平移就是计算出切平面上的右方向向量,然后绕前进方向旋转位置向量即可,不需要改动整体逻辑

内容的提问来源于stack exchange,提问作者Joe Dimagio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 06:45:03