经纬

garfileo
上一篇:标准正交基

假设以 <0, 0, 0> 为中心,半径为 1 的球面是天球,构造天网的第一步是建立球面的经纬度模型,即对于球面上的任意一点 A,能够基于它的经度值 U 和纬度值 V 计算它在世界坐标系中的位置 <x, y, z>。我打算先通过直观的图形弄清楚 U, V<x, y, z> 的关系。

首先,构造一个天球,

// 天球
sphere {
  <0, 0, 0>, 1
  texture { pigment { color rgbt <0, 0, 1, 0.75> } }
  hollow // 令球体中空
}

为了更直观一些,使用之前定义的 MakeFrame 宏,把世界坐标系也画出来:

MakeFrame(<0, 0, 0>, x, y, z, 1.25, 0.05)

结果得到

现在考虑在球面上画几个大圆作为纬线和经线。POV Ray 没有线,只有体。大圆,最简单的办法是用呼啦圈或圆环来画。圆环的语法是

torus {
  主半径, 从半径
  texture { ... }
}

主半径是圆环中心线所构成的圆的半径,从半径是圆环截面圆的半径。例如,绘制赤道和北纬 34.61 对应的纬线:

#declare line_thickness = 0.015;

// 赤道
torus {
  1, line_thickness
  texture { pigment { color Orange } }
}

// 北纬 34.61
torus {
  #local theta = (34.61 / 180) * pi;
  cos(theta), line_thickness
  texture { pigment { color Orange } }
  translate sin(theta) * y
}

结果看上去还不错:

为什么北纬 34.61 要写成 (34.61 / 180) * pi 呢?因为 POV Ray 的三角函数接受的参数是弧度。pi 即圆周率,是 POV Ray 内部已经给出定义的常量。

接下来,绘制本初子午线和东经 118.41 度对应的经线:

#declare PrimeMeridian = torus {
  1, line_thickness
  rotate 90 * z
  texture { pigment { color Orange } }
}

// 本初子午线
PrimeMeridian

// 东经 118.41
object {
  PrimeMeridian
  rotate -118.41 * y
}

结果就是……有些眼花缭乱:

在上述代码中,为什么东经 118.41 要写成负数呢?因为 POV Ray 用的坐标系是左手系。绕某个坐标轴旋转时,需要遵守左手螺旋定则——左手的拇指指向坐标轴的正向,其他 4 指弯曲的方向即为旋转方向。

然而,我画的经线是错误的。我要画的是本初子午线以及东经 118.41 对应的经线,它们应该是半圆,而非整圆。用 POV Ray 画一个半圆,不是那么容易,需要构造一个足够大的几何体,比如盒子,让它的尺寸刚好能够容纳半个圆——不想要的那半个圆:

#declare Monster = box { <-1.5, -1.5, 0>, <1.5, 1.5, 1.5> }

然后通过 difference 运算,用这个盒子从整圆上吃掉半个圆:

#declare PrimeMeridian = difference {
  torus {
    1, line_thickness
    rotate 90 * z
    texture { pigment { color Orange } }
  }
  Monster
}

然后绘制本初子午线和东经 118.41 度经线:

// 本初子午线
PrimeMeridian

// 东经 118.41
object {
  PrimeMeridian
  rotate -118.41 * y
}

结果就是不那么眼花缭乱:

difference 和之前用过的 union,都是用于组合多个几何体。difference 的用法为

difference {
  foo
  bar
}

其作用是从 foo 上剔除 foobar 相交的区域。

对上述最终所得的图作一些标注:

回到本文开始时提出的问题,假设上图中红圈所标记的经线与纬线的交点为 A,那么 A 的经度为 +118.41,纬度为 +34.61,那么 A 在图中所示的直角坐标系里的各维坐标是多少?

我也不知道有多少种方法可以算出点 A 的坐标,但是此刻我知道一种最简单的方法,就是

// 点 A
#declare A = vrotate(vrotate(-z, 34.61 * x), -118.41 * y);
sphere {
  A, PointSize
  texture { PointColor }
}
:POV Ray 提供的 vrotate 函数对给定的向量,遵循左手螺旋定则,绕一个单位向量以给定的角度旋转。

若不信,请看:

之所以成功,原因是:

若早生 2000 年,我就是有着经天纬地之才的人?

附录

完整的代码:

#version 3.7;
#include "colors.inc"
global_settings {
  assumed_gamma 1.0
}

// 天球
sphere {
  <0, 0, 0>, 1
  texture { pigment { color rgbt <0, 0, 1, 0.75> } }
  hollow // 令球体中空
}

// 世界坐标系
#macro MakeAxis(Origin, Direction, Length, Thickness, Color)
  #local ArrowLength = 0.3 * Length;
  #local Direction = vnormalize(Direction);
  #local Begin = Origin;
  #local End = Begin + (Length - ArrowLength) * Direction;
  #local ArrowBegin = End;
  #local ArrowEnd = ArrowBegin + ArrowLength * Direction;
  object {
    union {
      cylinder {
        Begin, End
        Thickness
        texture { pigment { color Color } }
      }
      cone {
        ArrowBegin, 2 * Thickness
        ArrowEnd, 0
        texture { pigment { color Color } }
      }
    }
  }
#end

#macro MakeFrame(A, X_A, Y_A, Z_A, L, Thickness)
  object {
    union {
      sphere {
        A, 2 * Thickness
        texture { pigment { color Gray50 } }
      }
      MakeAxis(A, X_A, L, Thickness, Red)
      MakeAxis(A, Y_A, L, Thickness, Green)
      MakeAxis(A, Z_A, L, Thickness, Blue)
    }
  }
#end


MakeFrame(<0, 0, 0>, x, y, z, 2, 0.025)

#declare line_thickness = 0.015;

torus {
  1, line_thickness
  texture { pigment { color Orange } }
}

torus {
  #local theta = (34.61 / 180) * pi;
  cos(theta), line_thickness
  texture { pigment { color Orange } }
  translate sin(theta) * y
}

#declare Monster = box { <-1.5, -1.5, 0>, <1.5, 1.5, 1.5> }
#declare PrimeMeridian = difference {
  torus {
    1, line_thickness
    rotate 90 * z
    texture { pigment { color Orange } }
  }
  Monster
}

// 本初子午线
PrimeMeridian

// 东经 118.41
object {
  PrimeMeridian
  rotate -118.41 * y
}

// 点 A
#declare A = vrotate(vrotate(-z, 34.61 * x), -118.41 * y);
sphere {
  A, 0.05
  texture { pigment { color Gray50 } }
}

light_source {
  <5, 5, -5>
  color White
  shadowless // 无影灯
}

camera {
  orthographic
  location <2.0, 2.0, -1.5>
  look_at <0, 0, 0>
}
:上述代码中,光源的设置里使用了 shadowless 修饰,可以消除物体因光照而产生的影子。相机则使用了 orthographic 修饰,设定为平行投影,从而取代了默认的透视投影设定。
下一篇:第一颗卫星
阅读 596

5.7k 声望
1.8k 粉丝
0 条评论
你知道吗?

5.7k 声望
1.8k 粉丝
文章目录
宣传栏