diff --git a/source/08-camera.md b/source/08-camera.md
new file mode 100644
index 0000000..99a76a9
--- /dev/null
+++ b/source/08-camera.md
@@ -0,0 +1,350 @@
+# 摄像机(Camera)
+
+在这个章节我们将学到如何渲染3D场景的画面,这个能力就像一个摄像机可以在3D世界穿梭,然而实际上是用来说明他的一种编程语言。
+
+但是如果你尝试在OpenGL寻找中这些特定的摄像机功能,你会发现这根本不是摄像机,换句话说摄像机一直是固定住的,以屏幕\(0, 0, 0\)的位置为中心点
+
+之所以这样,我们应该模拟出一个摄像机可以在三维度空间中移动的摄像机。但是如何做到呢?但是摄像机是不能移动的我们必须要移动全部的实体在我们的3D世界中。换句话说,如果移动不了摄像机我们得移动整个世界。
+
+因此,假设我们沿着Z轴移动摄像机从\(Cx, Cy, Cz\)到\(Cx, Cy, Cz+dz\),从而靠近在\(Ox, Oy, Oz\)放置的目标
+
+
+
+我们将要做的是如何精确的移动摄像机移动到相反的方向\(在我们的3D空间中的所有物体\)。想想看,其实就像物体在跑步机上跑步一样。
+
+
+
+摄像机可以沿着三个轴\(x, y and z\),也可以沿着他们旋转\(滚动, 俯视和偏斜"yaw"\).
+
+
+
+所以从基本上我们必须做的就是让移动和旋转对于我们所设置的3D世界全部实体。我们应该怎么做捏?答案是用另外一种转化方法,把他变化所有在摄像机运动方向上相反的顶点,从而根据摄像机的旋转进而旋转他们。当然,这将要用另外一个矩阵,所谓的视图矩阵来完成。这个矩阵首先执行平移,然后沿着轴线进行旋转。
+
+让我们看看如何构造这个矩阵。如果你记得变化章节(第6章)的转换方程式这样的:
+
+$$Transf = \lbrack ProjMatrix \rbrack \cdot \lbrack TranslationMatrix \rbrack \cdot \lbrack RotationMatrix \rbrack \cdot \lbrack ScaleMatrix \rbrack = \lbrack ProjMatrix \rbrack \cdot \lbrack WorldMatrix \rbrack$$
+
+在这投影的相乘矩阵之前,应该先应用视图矩阵,之后我们的方程式应该是这样的:
+
+$$Transf = \lbrack ProjMatrix \rbrack \cdot \lbrack ViewMatrix \rbrack \cdot \lbrack TranslationMatrix \rbrack \cdot \lbrack RotationMatrix \rbrack \cdot \lbrack ScaleMatrix \rbrack = \lbrack ProjMatrix \rbrack \cdot \lbrack ViewMatrix \rbrack \cdot \lbrack WorldMatrix \rbrack $$
+
+现在有三个矩阵,我们应该思考一下这些矩阵的生命的周期。在我们的游戏运行的时候,投影矩阵不应该改变的太多,在情况最不好的时候,每个渲染都要调用才可能改变一次。如果摄像机移动,则视图矩阵可以在每一次渲染改变一次。视图矩阵每`GameItem`项改变一次,所以每次渲染调用都会改变许多次。
+
+因此,如何把每一个矩阵推到顶点着色器呢?您可能会看到一些代码,使用三个统一的每一个矩阵,但原则上,最有效的方法是结合投影和视图矩阵,我们称之为`PV`矩阵,并推动`world`和`PV`矩阵到我们的着色器。通过这种方法,我们将有可以与世界坐标一起进行并且可以避免一些额外的运算。
+
+实际上,最方便的方法是将视图与世界矩阵相结合。为什么会这样?因为要记住整个摄像机的概念就是戏法,但要做的是推动整个世界来模拟世界的位移和只显示一小部分的3D世界。因此,如果直接联合世界坐标一起工作,这样可能会引起远离中心点的世界坐标系,会遇到一些精度的问题。如果在所谓的摄像机空间中工作利用点的性质,虽然远离世界的中心点,但也靠近摄像机。可以将视图和世界矩阵相结合的矩阵通常被称为模型视图矩阵。
+
+让我们开始修改代码来支持摄像机。首先,先创建一个新的类,称为`Camera`,它将确保持相机的位置与旋转的方向。该类将提供新位置或旋转方向\(`setPosition` or `setRotation`\)的方法,或在当前状态\(`movePosition` and `moveRotation`\)上用偏移量更新这些值。
+
+
+```java
+package org.lwjglb.engine.graph;
+
+import org.joml.Vector3f;
+
+public class Camera {
+
+ private final Vector3f position;
+
+ private final Vector3f rotation;
+
+ public Camera() {
+ position = new Vector3f(0, 0, 0);
+ rotation = new Vector3f(0, 0, 0);
+ }
+
+ public Camera(Vector3f position, Vector3f rotation) {
+ this.position = position;
+ this.rotation = rotation;
+ }
+
+ public Vector3f getPosition() {
+ return position;
+ }
+
+ public void setPosition(float x, float y, float z) {
+ position.x = x;
+ position.y = y;
+ position.z = z;
+ }
+
+ public void movePosition(float offsetX, float offsetY, float offsetZ) {
+ if ( offsetZ != 0 ) {
+ position.x += (float)Math.sin(Math.toRadians(rotation.y)) * -1.0f * offsetZ;
+ position.z += (float)Math.cos(Math.toRadians(rotation.y)) * offsetZ;
+ }
+ if ( offsetX != 0) {
+ position.x += (float)Math.sin(Math.toRadians(rotation.y - 90)) * -1.0f * offsetX;
+ position.z += (float)Math.cos(Math.toRadians(rotation.y - 90)) * offsetX;
+ }
+ position.y += offsetY;
+ }
+
+ public Vector3f getRotation() {
+ return rotation;
+ }
+
+ public void setRotation(float x, float y, float z) {
+ rotation.x = x;
+ rotation.y = y;
+ rotation.z = z;
+ }
+
+ public void moveRotation(float offsetX, float offsetY, float offsetZ) {
+ rotation.x += offsetX;
+ rotation.y += offsetY;
+ rotation.z += offsetZ;
+ }
+}
+```
+
+下一步 `Transformation` 到类中,将用一个新矩阵来保存视图矩阵的数值。
+
+```java
+private final Matrix4f viewMatrix;
+```
+
+我们将要提供一种更新这个值的方法。与投影矩阵一样,这个矩阵对于渲染周期中要渲染的所面对的对象都是相同的。
+
+```java
+public Matrix4f getViewMatrix(Camera camera) {
+ Vector3f cameraPos = camera.getPosition();
+ Vector3f rotation = camera.getRotation();
+
+ viewMatrix.identity();
+ // First do the rotation so camera rotates over its position
+ viewMatrix.rotate((float)Math.toRadians(rotation.x), new Vector3f(1, 0, 0))
+ .rotate((float)Math.toRadians(rotation.y), new Vector3f(0, 1, 0));
+ // Then do the translation
+ viewMatrix.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+ return viewMatrix;
+}
+```
+
+正如你所看到的,我们首先需要做旋转,然后翻译。如果我们做相反的事情,我们不会沿着摄像机位置旋转,而是沿着坐标原点旋转。请注意,在`Camera`类的`movePosition`方法中,我们不只是简单地增加相机位置的偏移量。我们还考虑了沿Y轴的旋转,偏航,以便计算最终位置。如果我们只是通过偏移来增加相机的位置,相机就不会朝着它的方向移动。
+
+
+
+正如你所看到的,我们首先需要做旋转,然后翻译。如果我们做相反的事情,我们不会沿着摄像机位置旋转,而是沿着坐标原点旋转。请注意,在“摄像机”类的“移动位置”方法中,我们不只是简单地增加相机位置的偏移量。我们还考虑了沿Y轴的旋转,偏航,以便计算最终位置。如果我们只是通过偏移来增加相机的位置,相机就不会朝着它的方向移动。
+
+除了上面提到的,我们这里没有一个完全自由移动的摄像机\(例如,如果我们沿着X轴旋转,当我们向前移动时,摄像机不会在空中向上或向下移动\)。这将在后面的章节中完成,因为这有点复杂。
+
+最后,我们将删除以前的方法`getWorldMatrix`,并添加一个新的名为`getModelViewMatrix`的方法。
+
+```java
+public Matrix4f getModelViewMatrix(GameItem gameItem, Matrix4f viewMatrix) {
+ Vector3f rotation = gameItem.getRotation();
+ modelViewMatrix.identity().translate(gameItem.getPosition()).
+ rotateX((float)Math.toRadians(-rotation.x)).
+ rotateY((float)Math.toRadians(-rotation.y)).
+ rotateZ((float)Math.toRadians(-rotation.z)).
+ scale(gameItem.getScale());
+ Matrix4f viewCurr = new Matrix4f(viewMatrix);
+ return viewCurr.mul(modelViewMatrix);
+}
+```
+
+
+这个`getModelViewMatrix`方法将在每个`GameItem`实例中调用,因此我们必须对视图矩阵的副本进行处理,因此在每次调用中都不会积累转换\(记住`Matrix4f`类不是不可变的\).
+
+
+在`Renderer`类的`render`方法中,在投影矩阵更新之后,我们只需要根据摄像机的更新视图矩阵的值,。
+
+```java
+// Update projection Matrix
+Matrix4f projectionMatrix = transformation.getProjectionMatrix(FOV, window.getWidth(), window.getHeight(), Z_NEAR, Z_FAR);
+shaderProgram.setUniform("projectionMatrix", projectionMatrix);
+
+// Update view Matrix
+Matrix4f viewMatrix = transformation.getViewMatrix(camera);
+
+shaderProgram.setUniform("texture_sampler", 0);
+// Render each gameItem
+for(GameItem gameItem : gameItems) {
+ // Set model view matrix for this item
+ Matrix4f modelViewMatrix = transformation.getModelViewMatrix(gameItem, viewMatrix);
+ shaderProgram.setUniform("modelViewMatrix", modelViewMatrix);
+ // Render the mes for this game item
+ gameItem.getMesh().render();
+}
+```
+
+就是这样对于基本代码支持摄像机的概念。现在我们需要用它。这样可以改变输入处理和更新相机的方式。我们将设置以下控件:
+
+* 键“A”和“D”到移动摄像机的左边和右边\(x axis\)。
+* 键“W”和“S”到移动摄像机的前面和后面\(z axis\)。
+* 键“Z”和“X”到移动摄像机的上面和下面的\(y axis\)。
+
+
+当鼠标按下右键时,我们可以使用鼠标位置沿X和Y轴旋转摄像机。
+正如你所看到的,我们将首次使用鼠标。我们将创建一个名为`MouseInput`的新类,该类将封装鼠标访问的代码。
+
+```java
+package org.lwjglb.engine;
+
+import org.joml.Vector2d;
+import org.joml.Vector2f;
+import static org.lwjgl.glfw.GLFW.*;
+
+public class MouseInput {
+
+ private final Vector2d previousPos;
+
+ private final Vector2d currentPos;
+
+ private final Vector2f displVec;
+
+ private boolean inWindow = false;
+
+ private boolean leftButtonPressed = false;
+
+ private boolean rightButtonPressed = false;
+
+ public MouseInput() {
+ previousPos = new Vector2d(-1, -1);
+ currentPos = new Vector2d(0, 0);
+ displVec = new Vector2f();
+ }
+
+ public void init(Window window) {
+ glfwSetCursorPosCallback(window.getWindowHandle(), (windowHandle, xpos, ypos) -> {
+ currentPos.x = xpos;
+ currentPos.y = ypos;
+ });
+ glfwSetCursorEnterCallback(window.getWindowHandle(), (windowHandle, entered) -> {
+ inWindow = entered;
+ });
+ glfwSetMouseButtonCallback(window.getWindowHandle(), (windowHandle, button, action, mode) -> {
+ leftButtonPressed = button == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS;
+ rightButtonPressed = button == GLFW_MOUSE_BUTTON_2 && action == GLFW_PRESS;
+ });
+ }
+
+ public Vector2f getDisplVec() {
+ return displVec;
+ }
+
+ public void input(Window window) {
+ displVec.x = 0;
+ displVec.y = 0;
+ if (previousPos.x > 0 && previousPos.y > 0 && inWindow) {
+ double deltax = currentPos.x - previousPos.x;
+ double deltay = currentPos.y - previousPos.y;
+ boolean rotateX = deltax != 0;
+ boolean rotateY = deltay != 0;
+ if (rotateX) {
+ displVec.y = (float) deltax;
+ }
+ if (rotateY) {
+ displVec.x = (float) deltay;
+ }
+ }
+ previousPos.x = currentPos.x;
+ previousPos.y = currentPos.y;
+ }
+
+ public boolean isLeftButtonPressed() {
+ return leftButtonPressed;
+ }
+
+ public boolean isRightButtonPressed() {
+ return rightButtonPressed;
+ }
+}
+```
+
+`MouseInput`类提供了一个初始化过程中应该调用的`init`方法,并注册一组回调来处理鼠标事件:
+
+
+* `glfwSetCursorPosCallback`:注册一个监听器,该监听器将在鼠标移动时调用。
+
+* `glfwSetCursorEnterCallback`:注册一个监听器,该监听器将在鼠标进入我们的窗口时调用。即使鼠标不在我们的窗口,我们也会收到鼠标事件。当鼠标在我们的窗口中时,我们使用这个监听器来进行跟踪。
+
+* `glfwSetMouseButtonCallback`:注册监听器在按下鼠标按钮时将调用。
+
+`MouseInput`类提供了一种输入方法,在处理游戏输入时应该调用该方法。该方法计算鼠标从先前位置的位移,并将其存储到 `Vector2f` `displVec`变量中,以便它可以被我们的游戏使用。
+
+`MouseInput`类将被实例化在我们的`GameEngine`类中,并且将作为游戏实现的`init`和`update`方法中的参数传递(因此我们需要相应地更改接口)。
+
+```java
+void input(Window window, MouseInput mouseInput);
+
+void update(float interval, MouseInput mouseInput);
+```
+
+鼠标输入将在`GameEngine`类的输入方法并传送控制到游戏执行前被处理。
+
+```java
+protected void input() {
+ mouseInput.input(window);
+ gameLogic.input(window, mouseInput);
+}
+```
+
+现在,已经准备好更新我们的`DummyGame`类处理键盘和鼠标输入。 该类的输入方法将如下所示:
+
+```java
+@Override
+public void input(Window window, MouseInput mouseInput) {
+ cameraInc.set(0, 0, 0);
+ if (window.isKeyPressed(GLFW_KEY_W)) {
+ cameraInc.z = -1;
+ } else if (window.isKeyPressed(GLFW_KEY_S)) {
+ cameraInc.z = 1;
+ }
+ if (window.isKeyPressed(GLFW_KEY_A)) {
+ cameraInc.x = -1;
+ } else if (window.isKeyPressed(GLFW_KEY_D)) {
+ cameraInc.x = 1;
+ }
+ if (window.isKeyPressed(GLFW_KEY_Z)) {
+ cameraInc.y = -1;
+ } else if (window.isKeyPressed(GLFW_KEY_X)) {
+ cameraInc.y = 1;
+ }
+}
+```
+
+现在,已经准备好更新我们的`DummyGame`类处理键盘和鼠标输入。 该类的输入方法将如下所示:
+
+```java
+@Override
+public void update(float interval, MouseInput mouseInput) {
+ // Update camera position
+ camera.movePosition(cameraInc.x * CAMERA_POS_STEP,
+ cameraInc.y * CAMERA_POS_STEP,
+ cameraInc.z * CAMERA_POS_STEP);
+
+ // Update camera based on mouse
+ if (mouseInput.isRightButtonPressed()) {
+ Vector2f rotVec = mouseInput.getDisplVec();
+ camera.moveRotation(rotVec.x * MOUSE_SENSITIVITY, rotVec.y * MOUSE_SENSITIVITY, 0);
+ }
+}
+```
+现在我们可以为我们的世界添加更多的立方体,将它们放置在特定位置并使用我们的新相机进行播放。 正如你可以看到所有的立方体共享相同的网格。
+
+```java
+GameItem gameItem1 = new GameItem(mesh);
+gameItem1.setScale(0.5f);
+gameItem1.setPosition(0, 0, -2);
+
+GameItem gameItem2 = new GameItem(mesh);
+gameItem2.setScale(0.5f);
+gameItem2.setPosition(0.5f, 0.5f, -2);
+
+GameItem gameItem3 = new GameItem(mesh);
+gameItem3.setScale(0.5f);
+gameItem3.setPosition(0, 0, -2.5f);
+
+GameItem gameItem4 = new GameItem(mesh);
+gameItem4.setScale(0.5f);
+
+gameItem4.setPosition(0.5f, 0, -2.5f);
+gameItems = new GameItem[]{gameItem1, gameItem2, gameItem3, gameItem4};
+```
+
+你会得到这样的东西。
+
+
+
diff --git a/source/24-hud-revisited.md b/source/24-hud-revisited.md
new file mode 100644
index 0000000..eee1cf9
--- /dev/null
+++ b/source/24-hud-revisited.md
@@ -0,0 +1,146 @@
+#HUD 重温 - NanoVG
+
+在之前的章节中,解释了如何使用正交投影在场景的顶部渲染形状和纹理。 在本章中,我们将学习如何使用 [NanoVG](https://github.com/memononen/nanovg) 库来渲染反锯齿矢量图形,以简单的方式构建更复杂的HUD。
+
+还有很多其他库可用于支持此任务,例如[Nifty GUI](https://github.com/nifty-gui/nifty-gui),[Nuklear](https://github.com/vurtun/nuklear)等。在本章中,将重点介绍Nanovg,因为它非常易于使用,但如果您想制作菜单和窗口,开发出复杂的GUI按钮交互,您应该查看[Nifty GUI](https://github.com/nifty-gui/nifty-gui)。
+
+开始使用[NanoVG](https://github.com/memononen/nanovg) 的第一步是在`pom.xml`文件中添加依赖关系\(一个用于编译时需要的依赖项,另一个用于编译时需要的依赖项 为运行时所需的本机\)。
+
+```xml
+...
+
+ org.lwjgl
+ lwjgl-nanovg
+ ${lwjgl.version}
+
+...
+
+ org.lwjgl
+ lwjgl-nanovg
+ ${lwjgl.version}
+ ${native.target}
+ runtime
+
+```
+在我们开始使用[NanoVG](https://github.com/memononen/nanovg)之前,我们必须在OpenGL里面设置一些东西,这样代码测试样本才能正常工作。 我们需要启用对模板缓冲区测试的支持。 到现在为止,已经讨论了颜色和深度缓冲区,但是我们没有提到模板缓冲区。 该缓冲区为每个像素存储一个值\(an integer\)\(整数值\),该像素用于控制应该绘制哪些像素。 该缓冲区用于根据其存储的值屏蔽或丢弃绘图区域。 例如,它可以用于以简单的方式剪切场景的某些部分。 我们通过将这一行添加到`Window`类\(after we enable depth testing\)\(在启用深度测试后\)来启用模板缓冲区测试:
+
+```java
+glEnable(GL_STENCIL_TEST);
+```
+
+由于程序正在使用另一个缓冲区,因此我们在每次渲染调用之前还必须注意删除其值。 因此,需要修改`Renderer`类的clear方法
+
+```java
+public void clear() {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+}
+```
+
+我们还将添加一个用于激活抗锯齿的新窗口选项。 因此,在Window类中,将通过以下方式启用它:
+
+```java
+if (opts.antialiasing) {
+ glfwWindowHint(GLFW_SAMPLES, 4);
+}
+```
+
+现在我们准备使用[NanoVG](https://github.com/memononen/nanovg)库。 我们要做的第一件事就是丢弃已经创建的HUD作品,那就是`IHud`接口的着色器,`Renderer`类中的HUD渲染方法等。你可以在源代码中找到这一点。
+
+在这种情况下,新的`Hud`类会照顾它的渲染,所以我们不需要将它委托给`Renderer`类。 让我们通过定义该类来开始讨论,它将有一个`init`方法来设置构建HUD所需的库和资源。 该方法是这样定义的:
+
+```java
+public void init(Window window) throws Exception {
+ this.vg = window.getOptions().antialiasing ? nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES) : nvgCreate(NVG_STENCIL_STROKES);
+ if (this.vg == NULL) {
+ throw new Exception("Could not init nanovg");
+ }
+
+ fontBuffer = Utils.ioResourceToByteBuffer("/fonts/OpenSans-Bold.ttf", 150 * 1024);
+ int font = nvgCreateFontMem(vg, FONT_NAME, fontBuffer, 0);
+ if (font == -1) {
+ throw new Exception("Could not add font");
+ }
+ colour = NVGColor.create();
+
+ posx = MemoryUtil.memAllocDouble(1);
+ posy = MemoryUtil.memAllocDouble(1);
+
+ counter = 0;
+}
+```
+我们要做的第一件事就是创建一个NanoVG环境。 在这种情况下,我们使用的是OpenGL3.0 后端,因为引用了`org.lwjgl.nanovg.NanoVGGL3`来命名空间。 如果要激活抗锯齿功能先得激活`NVG_ANTIALIAS`标志。
+
+接下来,我们使用先前加载到 `ByteBuffer` 中的True Type(全真字体)来创建。 给它分配一个名称,以便稍后在呈现文本时使用它。 其中一个重要的事情是,在使用字体时,用于加载字体的`ByteBuffer`必须保存在内存中。 也就是说,它不能被垃圾收集,除非你有一个很好的核心转储它。 这就是为什么它作为类属性存储的原因。
+
+然后,我们创建一个颜色实例和一些有用的变量,这些变量将在渲染时使用。 在渲染初始化之前,该方法在游戏`init`方法中被调用:
+
+```java
+@Override
+public void init(Window window) throws Exception {
+ hud.init(window);
+ renderer.init(window);
+ ...
+```
+
+`Hud`类还定义了一个渲染方法,在场景渲染完成之后应该调用这个渲染方法,这样HUD也会同时被绘制。
+
+```java
+@Override
+public void render(Window window) {
+ renderer.render(window, camera, scene);
+ hud.render(window);
+}
+```
+
+Hud类的`render`方法是从这里开始的:
+
+```java
+public void render(Window window) {
+ nvgBeginFrame(vg, window.getWidth(), window.getHeight(), 1);
+```
+
+我们必须做的第一件事就是调用`nvgBeginFrame`方法。 ```nvgBeginFrame`` method. 所有的NanoVG渲染操作都必须包含在两个之中```nvgBeginFrame`与`nvgEndFrame`.\`\`\`根据以下参数:
+
+* NanoVG的文本
+* 要渲染的窗口的大小 \(宽度和高度\).\(width an height\).
+* 像素比例。 如果您需要Hi-DPI的支持,则可以更改这个值。 对于这个示例,我们将它设置为1。
+
+ 然后我们创建几个条子,它会占据整个屏幕。 第一个是这样画的:
+
+```java
+// Upper ribbon
+nvgBeginPath(vg);
+nvgRect(vg, 0, window.getHeight() - 100, window.getWidth(), 50);
+nvgFillColor(vg, rgba(0x23, 0xa1, 0xf1, 200, colour));
+nvgFill(vg);
+```
+
+渲染一个形状时,第一个应该调用的方法是`nvgBeginPath`,它指示NanoVG开始绘制一个新的形状。 然后我们定义要绘制的内容,矩形,填充颜色以及调用我们绘制的`nvgFill`。
+
+您可以查看源代码的其余部分,以查看其余形状的绘制方式。 在渲染字体之前,不需要调用 nvgBeginPath。
+
+在完成绘制所有形状之后,我们只需调用`nvgEndFrame`来结束渲染,但在离开该方法之前还有一件重要的事情要做。 我们必须恢复OpenGL状态。 NanoVG修改OpenGL状态以执行其操作,如果状态未正确恢复,您可能会看到场景未正确呈现或者甚至已被消除。 因此,我们需要恢复我们渲染所需的相关OpenGL状态。 这是在`Window`类中委托的:
+
+```java
+// Restore state
+window.restoreState();
+```
+
+ 该方法是这样定义的:
+
+```java
+public void restoreState() {
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_STENCIL_TEST);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ if (opts.cullFace) {
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ }
+}
+```
+
+这就是所有的\(除了一些其他的方法来清除\)\(besides some additional methods to clear things up\),代码完成。 当你执行这个样本时,你会得到如下的结果:
+
+
+
diff --git a/source/_static/08/actual_movement.png b/source/_static/08/actual_movement.png
new file mode 100644
index 0000000..d23e177
Binary files /dev/null and b/source/_static/08/actual_movement.png differ
diff --git a/source/_static/08/camera_movement.png b/source/_static/08/camera_movement.png
new file mode 100644
index 0000000..1be3f3e
Binary files /dev/null and b/source/_static/08/camera_movement.png differ
diff --git a/source/_static/08/cubes.png b/source/_static/08/cubes.png
new file mode 100644
index 0000000..095ad90
Binary files /dev/null and b/source/_static/08/cubes.png differ
diff --git a/source/_static/08/new_transf_eq.png b/source/_static/08/new_transf_eq.png
new file mode 100644
index 0000000..904ca4f
Binary files /dev/null and b/source/_static/08/new_transf_eq.png differ
diff --git a/source/_static/08/prev_transformation_eq.png b/source/_static/08/prev_transformation_eq.png
new file mode 100644
index 0000000..a51942c
Binary files /dev/null and b/source/_static/08/prev_transformation_eq.png differ
diff --git a/source/_static/08/roll_pitch_yaw.png b/source/_static/08/roll_pitch_yaw.png
new file mode 100644
index 0000000..37bbb82
Binary files /dev/null and b/source/_static/08/roll_pitch_yaw.png differ
diff --git a/source/_static/24/hud.png b/source/_static/24/hud.png
new file mode 100644
index 0000000..cb7a02a
Binary files /dev/null and b/source/_static/24/hud.png differ