從0開發3D引擎(十一):使用領域驅動設計,從最小3D程序中提煉引擎(第二部分)

[TOC]html

你們好,本文根據領域驅動設計的成果,開始實現從最小的3D程序中提煉引擎。webpack

上一篇博文

從0開發3D引擎(十):使用領域驅動設計,從最小3D程序中提煉引擎(第一部分)git

本文流程

咱們根據上文的成果,按照下面的步驟開始實現從最小的3D程序中提煉引擎: 一、創建代碼的文件夾結構 二、index.html實現調用引擎API 三、根據用例圖和API層的設計,用僞代碼實現index.html 四、按照index.html從上往下的API調用順序,依次實現API:setCanvasById、setClearColor、addGLSL、createTriangleVertexData、addTriangle、setCameraes6

回顧上文

上文的領域驅動設計得到了下面的成果: 一、用戶邏輯和引擎邏輯 二、分層架構視圖和每一層的設計 三、領域驅動設計的戰略成果 1)引擎子域和限界上下文劃分 2)限界上下文映射圖 四、領域驅動設計的戰術成果 1)領域概念 2)領域視圖 五、數據視圖和PO的相關設計 六、一些細節的設計 七、基本的優化github

解釋基本的操做

  • 如何在瀏覽器上運行index.html 一、在TinyWonder項目根目錄上執行start命令:
yarn start

二、在瀏覽器地址中輸入下面的url並回車,便可運行index.html頁面web

http://127.0.0.1:8080

開始實現

打開最小3D程序的TinyWonder項目,如今咱們開始具體實現。npm

準備

咱們要徹底重寫src/的內容,所以在項目根目錄上新建mine/文件夾,將src/文件夾拷貝mine/中,並清空src/文件夾。json

經過備份src/文件夾,咱們能容易地調出最小3D程序的代碼供咱們參考。gulp

創建代碼的文件夾結構,約定模塊文件的命名規則

模塊文件的命名原則

  • 加上所屬層級/模塊的後綴名 這是爲了減小重名的概率
  • 儘可能簡潔 所以應該讓後綴名儘量地短,只要幾乎不會出現重名的狀況,那麼不只能夠省略一些層級/模塊的後綴名,並且有些模塊文件甚至徹底不加後綴名

一級和二級文件夾

以下圖所示: 截屏2020-03-04上午8.28.11.png-16.5kBcanvas

這是按照分層架構來劃分的文件夾:

  • 一級文件夾(xxx_layer/)對應每一個層級
  • 二級文件夾(xxx_layer/的子文件夾)對應每層的對象

api_layer的文件夾

api_layer/api/放置API模塊文件,如SceneJsAPI.re等

application_layer的文件夾

application_layer/service/放置應用服務模塊文件,如SceneApService.re等

domain_layer的文件夾

domain_layer/domain/放置領域服務、實體和值對象的模塊文件

domain_layer/repo/放置倉庫的模塊文件

domain/的子文件夾對應引擎的各個子域,以下圖所示: 截屏2020-03-04上午8.33.04.png-15.4kB

引擎子域文件夾的子文件夾對應該子域的限界上下文,以下圖所示: 截屏2020-03-04上午8.34.32.png-32.2kB

限界上下文文件夾的子文件均爲entity/、value_object/、service/,分別放置實體、值對象和領域服務的模塊文件。 部分截圖以下圖所示: 截屏2020-03-04上午8.37.22.png-22.3kB

entity/、value_object/、service/文件夾的模塊文件的命名規則分別爲:

  • 實體+限界上下文+Entity.re 如SceneSceneGraphEntity.re
  • 值對象+限界上下文+VO.re 如TriangleSceneGraphVO.re
  • 領域服務+限界上下文+DoService.re 如RenderRenderDoService.re

若是從這三個子文件夾的文件中提出公共代碼的模塊文件(如在後面,會從值對象ImmutableHashMap和值對象MutableHashMap中提出HashMap模塊),則該模塊文件的命名規則爲: 模塊名+限界上下文.re (如將HashMap模塊文件命名爲HashMapContainer.re)

infrastructure_layer的文件夾

infrastructure_layer/data/的文件夾結構以下圖所示: 截屏2020-03-04上午8.40.45.png-9.6kB

ContainerManager.re負責實現「容器管理」

container/放置PO Container相關的模塊文件

po/放置PO類型定義的文件

infrastructure_layer/external/文件夾結構以下圖所示: 截屏2020-03-04上午8.41.13.png-5.6kB

external_object/放置外部對象的FFI文件 library/放置js庫的FFI文件

index.html實現調用引擎API

index.html須要引入引擎文件,調用它的API。

咱們首先考慮的實現方案是: 與最小3D程序同樣,index.html以ES6 module的方式引入要使用的引擎的每一個模塊文件(一個.re的引擎文件就是一個模塊文件),調用暴露的API函數。 index.html的相關僞代碼以下:

<script type="module">
import { setCanvasById } from "./lib/es6_global/src/api_layer/api/CameraJsAPI.js";
import { start } from "./lib/es6_global/src/api_layer/api/DirectorJsAPI.js";

window.onload = () => {
    ...
    setCanvasById(canvasId);
    ...
    start();
    ...
};
</script>

這個方案有下面的缺點:

  • 用戶訪問的權限過大 用戶能夠訪問非API的函數,如引擎的私有函數
  • 用戶須要知道要調用的引擎API在引擎的哪一個模塊文件中,以及模塊文件的路徑,這樣會增長用戶的負擔 如用戶須要知道setCanvasById在CameraJsAPI.js中,而且須要知道CameraJsAPI.js的路徑
  • 瀏覽器須要支持ES6 module import

所以,咱們使用下面的方案來實現,該方案能夠解決上一個方案的缺點:

  • 把引擎全部的API模塊放到一個命名空間中,讓用戶經過它來調用API 用戶只能訪問到API,從而讓引擎控制了用戶訪問權限; 用戶只須要知道命名空間和API模塊的名字,減小了負擔。
  • 使用webpack,將與引擎API相關的文件打包成一個文件,在index.html中引入該文件 這樣瀏覽器就不須要支持ES6 module import了

咱們經過下面的步驟來實現該方案: 一、建立gulp任務 該任務會建立src/Index.re文件,它引用了引擎全部的API模塊。 經過下面的子步驟來實現該任務: 1)在項目根目錄上,加入gulpfile.js文件 2)在gulpfile.js中,加入gulp任務:generateIndex,該任務負責把引擎的全部API模塊放到Index.re文件中 3)實現generateIndex任務 由於咱們但願用Reason而不是用js來實現,並且考慮到該任務屬於通用任務,多個Reason項目都須要它,因此咱們經過下面的步驟來實現generateIndex任務: a)建立新項目:TinyWonderGenerateIndex b)進入新項目,用Reason代碼來實現generateIndex任務的邏輯 其中src/Generate.re的generate函數是提供給用戶(如TinyWonder項目的generateIndex任務)使用的API,它的代碼以下:

let generate =
    (
      globCwd: string,
      rootDir: string,
      sourceFileGlobArr: array(string),
      destDir: string,
      config,
    ) => {
  let excludeList = config##exclude |> Array.to_list;
  let replaceAPIModuleNameFunc =
    config##replaceAPIModuleNameFunc
    |> Js.Option.getWithDefault(moduleName =>
         moduleName |> Js.String.replace("JsAPI", "")
       );

  sourceFileGlobArr
  |> Array.to_list
  |> List.fold_left(
       (fileDataList, filePath) => {
         let fileName = Path.basename_ext(filePath, ".re");
         [
           syncWithConfig(Path.join([|rootDir, filePath|]), {"cwd": globCwd})
           |> Array.to_list
           |> List.filter(filePath =>
                excludeList
                |> List.filter(exclude =>
                     filePath |> Js.String.includes(exclude)
                   )
                |> List.length === 0
              )
           |> List.map(filePath =>
                (
                  Path.basename_ext(filePath, ".re")
                  |> replaceAPIModuleNameFunc,
                  Path.basename_ext(filePath, ".re"),
                  Fs.readFileAsUtf8Sync(filePath) |> _findPublicFunctionList,
                )
              ),
           ...fileDataList,
         ];
       },
       [],
     )
  |> List.flatten
  |> _buildContent
  |> _writeToIndexFile(destDir);
};

該函數使用glob庫遍歷sourceFileGlobArr數組,將「globCwd + rootDir + sourceFileGlob」路徑中的全部Reason文件的函數引入到destDir/Index.re的對應的模塊中。 可在config.exclude中定義要排除的文件路徑的數組,在config.replaceAPIModuleNameFunc函數中定義如何重命名模塊名。

該項目的完整代碼地址爲:Tiny-Wonder-GenerateIndex

舉個例子來講明用戶如何使用generate函數: 假設用戶工做在TinyWonder項目(項目根目錄爲/y/Github/TinyWonder/)上,建立了./src/api/AJsAPI.re,它的代碼爲:

let aFunc = v => 1;

它的模塊名爲「AJsAPI」。

用戶還建立了./src/api/ddd/BJsAPI.re,它的代碼爲:

let bFunc = v => v * 2;

它的模塊名爲「BJsAPI」

用戶能夠在TinyWonder項目根目錄上,用js代碼調用Tiny-Wonder-GenerateIndex項目的generate函數:

//將路徑爲「/y/Github/TinyWonder/src/**/api/**/*.re」(排除包含「src/Config」的文件路徑)的全部API模塊引入到./src/Index.re中
generate("/", "/y/Github/TinyWonder/src", ["**/api/**/*.re"], "./src/", {
        exclude: ["src/Config"],
        //該函數用於去掉API模塊名的「JsAPI」,如將「AJsAPI」重命名爲「A」
        replaceAPIModuleNameFunc: (moduleName) => moduleName.replace("JsAPI", "")
    })

用戶調用後,會在./src/中加入Index.re文件,它的代碼以下:

module A = {
  let aFunc = AJsAPI.aFunc;
};

module B = {
  let bFunc = BJsAPI.bFunc;
};

如今咱們清楚瞭如何使用generate函數,那麼繼續在TinyWonderGenerateIndex項目上完成剩餘工做: c)編譯該項目的Reason代碼爲Commonjs模塊規範的js文件 d)經過npm發佈該項目 package.json須要定義main字段:

"main": "./lib/js/src/Generate.js",

e)回到TinyWonder項目 f)在TinyWonder項目的gulpfile.js->generateIndex任務中,引入TinyWonderGenerateIndex項目,調用它的generate函數 TinyWonder項目的相關代碼爲: gulpfile.js

var gulp = require("gulp");
var path = require("path");

gulp.task("generateIndex", function (done) {
    var generate = require("tiny-wonder-generate-index");
    var rootDir = path.join(process.cwd(), "src"),
        destDir = "./src/";

    generate.generate("/", rootDir, ["**/api/**/*.re"], destDir, {
        exclude: [],
        replaceAPIModuleNameFunc: (moduleName) => moduleName.replace("JsAPI", "")
    });

    done();
});

二、建立gulp任務後,咱們須要引入webpack,它將Index.re關聯的引擎文件打包成一個文件 1)在項目的根目錄上,加入webpack.config.js文件:

const path = require('path');

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
  entry: {
      "wd": "./lib/es6_global/src/Index.js"
  },
  mode: isProd ? 'production' : 'development',
  output: {
      filename: '[name].js',
      path: path.join(__dirname, "dist"),
      library: 'wd',
      libraryTarget: 'umd'
  },
  target: "web"
};

2)在package.json中,加入script:

"scripts": {
    ...
    "webpack:dev": "NODE_ENV=development webpack --config webpack.config.js",
    "webpack": "gulp generateIndex && npm run webpack:dev"
}

三、運行測試 1)在src/api_layer/api/中加入一個用於測試的API模塊文件:TestJsAPI.re,定義兩個API函數:

let aFunc = v => Js.log(v);

let bFunc = v => Js.log(v * 2);

2)安裝gulp後,在項目根目錄上執行generateIndex任務,進行運行測試:

gulp generateIndex

能夠看到src/中加入了Index.re文件,Index.re代碼爲:

module Test = {
  let aFunc = TestJsAPI.aFunc;
  
  let bFunc = TestJsAPI.bFunc;
};

3)在安裝webpack後,在項目根目錄上執行下面命令,打包爲dist/wd.js文件,其中命名空間爲「wd」:

yarn webpack

4)index.html引入wd.js文件,調用引擎API

index.html的代碼爲:

<script src="./dist/wd.js"></script>

  <script>
    var a = wd.Test.aFunc(1);
    var b = wd.Test.bFunc(2);
  </script>

5)在瀏覽器上運行index.html,打開控制檯,能夠看到打印了"1"和「4」

用僞代碼實現index.html

咱們根據用例圖和設計的API來實現index.html: index.html須要使用最小3D程序的數據,來實現用例圖中「index.html」角色包含的用戶邏輯; index.html須要調用引擎API,來實現用例圖中的用例。

index.html的僞代碼實現以下所示:

<canvas id="webgl" width="400" height="400">
  Please use a browser that supports "canvas"
</canvas>

<script>
  //準備canvas的id
  var canvasId = "webgl";
  
  CanvasJsAPI.setCanvasById(canvasId);
  
  
  //準備清空顏色緩衝時的顏色值
  var clearColor = [0.0, 0.0, 0.0, 1.0];
  
  GraphicsJsAPI.setClearColor(clearColor);
  
  
  //準備兩組GLSL
  var vs1 = `
precision mediump float;
attribute vec3 a_position;
uniform mat4 u_pMatrix;
uniform mat4 u_vMatrix;
uniform mat4 u_mMatrix;

void main() {
gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
}
`;
  var fs1 = `
precision mediump float;

uniform vec3 u_color0;

void main(){
gl_FragColor = vec4(u_color0, 1.0);
}
`;
  var vs2 = `
precision mediump float;
attribute vec3 a_position;
uniform mat4 u_pMatrix;
uniform mat4 u_vMatrix;
uniform mat4 u_mMatrix;

void main() {
gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
}
`;
  var fs2 = `
precision mediump float;

uniform vec3 u_color0;
uniform vec3 u_color1;

void main(){
gl_FragColor = vec4(u_color0 * u_color1, 1.0);
}
`;
  //準備兩個Shader的名稱
  var shaderName1 = "shader1";
  var shaderName2 = "shader2";
  
  ShaderJsAPI.addGLSL(shaderName1, [vs1, fs1]);
  ShaderJsAPI.addGLSL(shaderName2, [vs2, fs2]);


  //調用API,準備三個三角形的頂點數據
  var [vertices1, indices1] = SceneJsAPI.createTriangleVertexData();
  var [vertices2, indices2] = SceneJsAPI.createTriangleVertexData();
  var [vertices3, indices3] = SceneJsAPI.createTriangleVertexData();
  //準備三個三角形的位置數據
  var [position1, position2, position3] = [
    [0.75, 0.0, 0.0],
    [-0.0, 0.0, 0.5],
    [-0.5, 0.0, -2.0]
  ];
  //準備三個三角形的顏色數據
  var [colors1, colors2, colors3] = [
    [[1.0, 0.0, 0.0]],
    [[0.0, 0.8, 0.0], [0.0, 0.5, 0.0]],
    [[0.0, 0.0, 1.0]]
  ];
  
  SceneJsAPI.addTriangle(position1, [vertices1, indices1], [shaderName1, colors1]);
  SceneJsAPI.addTriangle(position2, [vertices2, indices2], [shaderName2, colors2]);
  SceneJsAPI.addTriangle(position3, [vertices3, indices3], [shaderName1, colors3]);


  //準備相機數據
  var [eye, center, up] = [
    [0.0, 0.0, 5.0],
    [0.0, 0.0, -100.0],
    [0.0, 1.0, 0.0]
  ];
  var [near, far, fovy, aspect] = [
    1.0,
    100.0,
    30.0,
    canvas.width / canvas.height
  ];
  
  SceneJsAPI.setCamera([eye, center, up], [near, far, fovy, aspect]);


  //準備webgl上下文的配置項
  var contextConfig = {
    "alpha": true,
    "depth": true,
    "stencil": false,
    "antialias": true,
    "premultipliedAlpha": true,
    "preserveDrawingBuffer": false,
  };

  DirectorJsAPI.init(contextConfig);
  
  
  DirectorJsAPI.start();
</script>

咱們按照下面的步驟來具體實現index.html: 一、按照index.html從上往下的API調用順序,肯定要實現的API 二、按照引擎的層級,從上層的API開始,實現每一層的對應模塊 三、實現index.html的相關代碼 四、運行測試

如今咱們按照順序,肯定要實現API->「CanvasJsAPI.setCanvasById」 。

如今來實現該API:

實現「CanvasJsAPI.setCanvasById」

一、在src/api_layer/api/中加入CanvasJsAPI.re,實現API CanvasJsAPI.re代碼爲:

//由於應用服務CanvasApService的setCanvasById函數輸入和輸出的DTO與這裏(API)的setCanvasById函數輸入和輸出的VO相同,因此不須要在二者之間轉換
let setCanvasById = CanvasApService.setCanvasById;

二、在src/application_layer/service/中加入CanvasApService.re,實現應用服務 CanvasApService.re代碼爲:

let setCanvasById = canvasId => {
  CanvasCanvasEntity.setCanvasById(canvasId);

  //打印canvasId,用於運行測試
  Js.log(canvasId);
};

三、把最小3D程序的DomExtend.re放到src/instracture_layer/external/external_object/中,刪除目前沒用到的代碼(刪除requestAnimationFrame FFI) DomExtend.re代碼爲:

type htmlElement = {
  .
  "width": int,
  "height": int,
};

type body;

type document = {. "body": body};

[@bs.val] external document: document = "";

[@bs.send] external querySelector: (document, string) => htmlElement = "";

四、在src/domain_layer/domain/canvas/canvas/entity/中加入CanvasCanvasEntity.re,建立聚合根Canvas CanvasCanvasEntity.re代碼爲:

//這裏定義Canvas DO的類型(Canvas DO爲一個畫布)
type t = DomExtend.htmlElement;

let setCanvasById = canvasId =>
  Repo.setCanvas(
    DomExtend.querySelector(DomExtend.document, {j|#$canvasId|j}),
  );

五、由於須要使用Result來處理錯誤,因此加入值對象Result 1)在領域視圖的「容器」限界上下文中,加入值對象Result,負責操做錯誤處理的容器Result 2)在src/domain_layer/domain/structure/container/value_object/中加入ResultContainerVO.re,建立值對象Result ResultContainerVO.re代碼爲:

type t('a, 'b) =
  | Success('a)
  | Fail('b);

let succeed = x => Success(x);

let fail = x => Fail(x);

let _raiseErrorAndReturn = msg => Js.Exn.raiseError(msg);

let failWith = x => (x |> _raiseErrorAndReturn)->Fail;

let either = (successFunc, failureFunc, twoTrackInput) =>
  switch (twoTrackInput) {
  | Success(s) => successFunc(s)
  | Fail(f) => failureFunc(f)
  };

let bind = (switchFunc, twoTrackInput) =>
  either(switchFunc, fail, twoTrackInput);

let tap = (oneTrackFunc, twoTrackInput) =>
  either(
    result => {
      result |> oneTrackFunc |> ignore;
      result |> succeed;
    },
    fail,
    twoTrackInput,
  );

let tryCatch = (oneTrackFunc: 'a => 'b, x: 'a): t('b, Js.Exn.t) =>
  try(oneTrackFunc(x) |> succeed) {
  | Js.Exn.Error(e) => fail(e)
  | err => {j|unknown error: $err|j} |> _raiseErrorAndReturn |> fail
  };

let mapSuccess = (mapFunc, result) =>
  switch (result) {
  | Success(s) => mapFunc(s) |> succeed
  | Fail(f) => fail(f)
  };

let handleFail = (handleFailFunc: 'f => unit, result: t('s, 'f)): unit =>
  switch (result) {
  | Success(s) => ()
  | Fail(f) => handleFailFunc(f)
  };

六、在src/infrastructure_layer/data/po/中加入POType.re,定義PO的類型,目前PO只包含Canvas PO POType.re代碼爲:

//由於在建立PO時,Canvas PO(一個畫布)並不存在,因此它爲option類型
type po = {canvas: option(CanvasPOType.canvas)};

七、在src/infrastructure_layer/data/po/中加入CanvasPOType.re,定義Canvas PO類型 CanvasPOType.re代碼爲:

type canvas = DomExtend.htmlElement;

八、由於定義了option類型,因此加入領域服務Option 1)在領域視圖的「容器」限界上下文中,加入領域服務Option,負責操做Option容器 2)在src/domain_layer/domain/structure/container/service/中加入OptionContainerDoService.re,建立領域服務Option OptionContainerDoService.re代碼爲:

//若是optionData爲Some(v),返回v;不然拋出異常
let unsafeGet = optionData => optionData |> Js.Option.getExn;

//經過使用Result,安全地取出optionData的值
let get = optionData => {
  switch (optionData) {
  | None => ResultContainerVO.failWith({|data not exist(get by getExn)|})
  | Some(data) => ResultContainerVO.succeed(data)
  };
};

九、在src/domain_layer/repo/中加入Repo.re,實現倉庫對Canvas PO的操做 Repo.re代碼爲:

let getCanvas = () => {
  let po = ContainerManager.getPO();

  po.canvas;
};

let setCanvas = canvas => {
  let po = ContainerManager.getPO();

  {...po, canvas: Some(canvas)} |> ContainerManager.setPO;
};

十、在src/infrastructure_layer/data/中加入ContainerManager.re,實現PO Container中PO的讀寫 ContainerManager.re代碼爲:

let getPO = () => {
  Container.poContainer.po;
};

let setPO = po => {
  Container.poContainer.po = po;
};

十一、在src/infrastructure_layer/data/container/中加入ContainerType.re和Container.re,分別定義PO Container的類型和建立PO Container ContainerType.re代碼爲:

type poContainer = {mutable po: POType.po};

Container.re代碼爲:

let poContainer: ContainerType.poContainer = {
  po: CreateRepo.create(),
};

十二、在src/domain_layer/repo/中加入CreateRepo.re,實現建立PO CreateRepo.re代碼爲:

open POType;

let create = () => {canvas: None};

1三、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

1四、實現index.html相關代碼

index.html代碼爲:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <title>use engine</title>
</head>

<body>
  <canvas id="webgl" width="400" height="400">
    Please use a browser that supports "canvas"
  </canvas>

  <script src="./dist/wd.js"></script>

  <script>
    //準備canvas的id
    var canvasId = "webgl";

    //調用API
    wd.Canvas.setCanvasById(canvasId);
  </script>
</body>

</html>

1五、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了"webgl"

實現「GraphicsJsAPI.setClearColor」

一、在src/api_layer/api/中加入GraphicsJsAPI.re,實現API GraphicsJsAPI.re代碼爲:

let setClearColor = GraphicsApService.setClearColor;

二、在src/application_layer/service/中加入GraphicsApService.re,實現應用服務 GraphicsApService.re代碼爲:

let setClearColor = clearColor => {
  ContextContextEntity.setClearColor(Color4ContainerVO.create(clearColor));

  //用於運行測試
  Js.log(clearColor);
};

三、在src/domain_layer/domain/structure/container/value_object/中加入Color4ContainerVO.re,建立值對象Color4 Color4ContainerVO.re代碼爲:

type r = float;
type g = float;
type b = float;
type a = float;

type t =
  | Color4(r, g, b, a);

let create = ((r, g, b, a)) => Color4(r, g, b, a);

let value = color =>
  switch (color) {
  | Color4(r, g, b, a) => (r, g, b, a)
  };

四、在src/domain_layer/domain/webgl_context/context/entity/中加入ContextContextEntity.re,建立聚合根Context ContextContextEntity.re代碼爲:

let setClearColor = clearColor => {
  ContextRepo.setClearColor(clearColor);
};

五、在src/infrastructure_layer/data/po/中加入ContextPOType.re,定義Context PO類型 ContextPOType.re代碼爲:

type context = {clearColor: (float, float, float, float)};

六、修改POType.re POType.re相關代碼爲:

type po = {
  ...
  context: ContextPOType.context,
};

七、在src/domain_layer/repo/中加入ContextRepo.re,實現倉庫對Context PO的clearColor字段的操做 ContextRepo.re代碼爲:

let getClearColor = () => {
  Repo.getContext().clearColor;
};

let setClearColor = clearColor => {
  Repo.setContext({
    ...Repo.getContext(),
    clearColor: Color4ContainerVO.value(clearColor),
  });
};

八、修改Repo.re,實現倉庫對Context PO的操做 Repo.re相關代碼爲:

let getContext = () => {
  let po = ContainerManager.getPO();

  po.context;
};

let setContext = context => {
  let po = ContainerManager.getPO();

  {...po, context} |> ContainerManager.setPO;
};

九、修改CreateRepo.re,實現建立Context PO CreateRepo.re相關代碼爲:

let create = () => {
  ...
  context: {
    clearColor: (0., 0., 0., 1.),
  },
};

十、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

十一、實現index.html相關代碼

index.html代碼爲:

<script>
    ...
    //準備清空顏色緩衝時的顏色值
    var clearColor = [0.0, 0.0, 0.0, 1.0];

    wd.Graphics.setClearColor(clearColor);
  </script>

十二、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了數組:[0,0,0,1]

實現「ShaderJsAPI.addGLSL」

一、在src/api_layer/api/中加入ShaderJsAPI.re,實現API ShaderJsAPI.re代碼爲:

let addGLSL = ShaderApService.addGLSL;

二、設計領域模型ShaderManager、Shader、GLSL的DO

根據領域模型: 此處輸入圖片的描述 和識別的引擎邏輯: 得到全部Shader的Shader名稱和GLSL組集合

咱們能夠設計聚合根ShaderManager的DO爲集合list:

type t = {glsls: list(Shader DO)};

設計值對象GLSL的DO爲:

type t =
  | GLSL(string, string);

設計實體Shader的DO爲:

type shaderName = string;

type t =
  | Shader(shaderName, GLSL DO);

三、在src/application_layer/service/中加入ShaderApService.re,實現應用服務 ShaderApService.re代碼爲:

let addGLSL = (shaderName, glsl) => {
  ShaderManagerShaderEntity.addGLSL(
    ShaderShaderEntity.create(shaderName, GLSLShaderVO.create(glsl)),
  );

  //用於運行測試
  Js.log((shaderName, glsl));
};

四、在src/domain_layer/domain/shader/shader/entity/中加入ShaderShaderEntity.re,建立實體Shader ShaderShaderEntity.re代碼爲:

type shaderName = string;

type t =
  | Shader(shaderName, GLSLShaderVO.t);

let create = (shaderName, glsl) => Shader(shaderName, glsl);

let getShaderName = shader =>
  switch (shader) {
  | Shader(shaderName, glsl) => shaderName
  };

let getGLSL = shader =>
  switch (shader) {
  | Shader(shaderName, glsl) => glsl
  };

五、在src/domain_layer/domain/shader/shader/value_object/中加入GLSLShaderVO.re,建立值對象GLSL GLSLShaderVO.re代碼爲:

type t =
  | GLSL(string, string);

let create = ((vs, fs)) => GLSL(vs, fs);

let value = glsl =>
  switch (glsl) {
  | GLSL(vs, fs) => (vs, fs)
  };

六、在src/domain_layer/domain/shader/shader/entity/中加入ShaderManagerShaderEntity.re,建立聚合根ShaderManager ShaderManagerShaderEntity.re代碼爲:

type t = {glsls: list(ShaderShaderEntity.t)};

let addGLSL = shader => {
  ShaderManagerRepo.addGLSL(shader);
};

七、在src/infrastructure_layer/data/po/中加入ShaderManagerPOType.re,定義ShaderManager PO的類型 ShaderManagerPOType.re代碼爲:

//shaderId就是Shader的名稱
type shaderId = string;

type shaderManager = {glsls: list((shaderId, (string, string)))};

八、修改POType.re POType.re相關代碼爲:

type po = {
  ...
  shaderManager: ShaderManagerPOType.shaderManager,
};

九、在src/domain_layer/repo/中加入ShaderManagerRepo.re,實現倉庫對ShaderManager PO的glsls字段的操做 ShaderManagerRepo.re代碼爲:

open ShaderManagerPOType;

let _getGLSLs = ({glsls}) => glsls;

let addGLSL = shader => {
  Repo.setShaderManager({
    ...Repo.getShaderManager(),
    glsls: [
      (
        ShaderShaderEntity.getShaderName(shader),
        shader |> ShaderShaderEntity.getGLSL |> GLSLShaderVO.value,
      ),
      ..._getGLSLs(Repo.getShaderManager()),
    ],
  });
};

十、修改Repo.re,實現倉庫對ShaderManager PO的操做 Repo.re相關代碼爲:

let getShaderManager = () => {
  let po = ContainerManager.getPO();

  po.shaderManager;
};

let setShaderManager = shaderManager => {
  let po = ContainerManager.getPO();

  {...po, shaderManager} |> ContainerManager.setPO;
};

十一、修改CreateRepo.re,實現建立ShaderManager PO CreateRepo.re相關代碼爲:

let create = () => {
  ...
  shaderManager: {
    glsls: [],
  },
};

十二、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

1三、實現index.html相關代碼

index.html代碼爲:

<script>
    ...
    //準備兩組GLSL
    var vs1 = `
precision mediump float;
attribute vec3 a_position;
uniform mat4 u_pMatrix;
uniform mat4 u_vMatrix;
uniform mat4 u_mMatrix;

void main() {
gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
}
`;
    var fs1 = `
precision mediump float;

uniform vec3 u_color0;

void main(){
gl_FragColor = vec4(u_color0, 1.0);
}
`;
    var vs2 = `
precision mediump float;
attribute vec3 a_position;
uniform mat4 u_pMatrix;
uniform mat4 u_vMatrix;
uniform mat4 u_mMatrix;

void main() {
gl_Position = u_pMatrix * u_vMatrix * u_mMatrix * vec4(a_position, 1.0);
}
`;
    var fs2 = `
precision mediump float;

uniform vec3 u_color0;
uniform vec3 u_color1;

void main(){
gl_FragColor = vec4(u_color0 * u_color1, 1.0);
}
`;
    //準備兩個Shader的名稱
    var shaderName1 = "shader1";
    var shaderName2 = "shader2";

    wd.Shader.addGLSL(shaderName1, [vs1, fs1]);
    wd.Shader.addGLSL(shaderName2, [vs2, fs2]);
  </script>

1四、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了兩個Shader的數據: 截屏2020-02-29下午12.13.07.png-78.1kB

實現「SceneJsAPI.createTriangleVertexData」

一、在src/api_layer/api/中加入SceneJsAPI.re,實現API SceneJsAPI.re代碼爲:

let createTriangleVertexData = SceneApService.createTriangleVertexData;

二、在src/application_layer/service/中加入SceneApService.re,實現應用服務 SceneApService.re代碼爲:

let createTriangleVertexData = () => {
  //vertices和indices爲DO數據,分別爲值對象Vertices的DO和值對象Indices的DO
  let (vertices, indices) = GeometrySceneGraphVO.createTriangleVertexData();

  //將DO轉成DTO
  let data = (
    vertices |> VerticesSceneGraphVO.value,
    indices |> IndicesSceneGraphVO.value,
  );

  //用於運行測試
  Js.log(data);
  
  //將DTO返回給API層
  data;
};

三、在src/domain_layer/domain/scene/scene_graph/value_object/中加入GeometrySceneGraphVO.re,建立值對象Geometry GeometrySceneGraphVO.re代碼爲:

let createTriangleVertexData = () => {
  open Js.Typed_array;

  let vertices =
    Float32Array.make([|0., 0.5, 0.0, (-0.5), (-0.5), 0.0, 0.5, (-0.5), 0.0|])
    |> VerticesSceneGraphVO.create;

  let indices = Uint16Array.make([|0, 1, 2|]) |> IndicesSceneGraphVO.create;

  (vertices, indices);
};

四、在src/domain_layer/domain/scene/scene_graph/value_object/中加入VerticesSceneGraphVO.re和IndicesSceneGraphVO.re,建立值對象Vertices和值對象Indices VerticesSceneGraphVO.re代碼爲:

open Js.Typed_array;

type t =
  | Vertices(Float32Array.t);

let create = value => Vertices(value);

let value = vertices =>
  switch (vertices) {
  | Vertices(value) => value
  };

IndicesSceneGraphVO.re代碼爲:

open Js.Typed_array;

type t =
  | Indices(Uint16Array.t);

let create = value => Indices(value);

let value = indices =>
  switch (indices) {
  | Indices(value) => value
  };

五、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

六、實現index.html相關代碼

index.html代碼爲:

<script>
    ...
    //調用API,準備三個三角形的頂點數據
    var [vertices1, indices1] = wd.Scene.createTriangleVertexData();
    var [vertices2, indices2] = wd.Scene.createTriangleVertexData();
    var [vertices3, indices3] = wd.Scene.createTriangleVertexData();
  </script>

七、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了三次頂點數據

實現「SceneJsAPI.addTriangle」

一、修改SceneJsAPI.re,實現API SceneJsAPI.re相關代碼爲:

let addTriangle = (position, (vertices, indices), (shaderName, colors)) => {
  //這裏的VO與DTO有區別:VO的colors的類型爲array,而DTO的colors的類型爲list,因此須要將colors的array轉換爲list
  SceneApService.addTriangle(
    position,
    (vertices, indices),
    (shaderName, colors |> Array.to_list),
  );
};

二、設計聚合根Scene、值對象Triangle和它全部的值對象的DO

根據領域模型: 此處輸入圖片的描述

咱們按照Scene的聚合關係,從下往上開始設計: 設計值對象Vector的DO爲:

type t =
  | Vector(float, float, float);

設計值對象Position的DO爲:

type t =
  | Position(Vector.t);

設計值對象Vertices的DO爲:

open Js.Typed_array;

type t =
  | Vertices(Float32Array.t);

設計值對象Indices的DO爲:

open Js.Typed_array;

type t =
  | Indices(Uint16Array.t);

設計值對象Color3的DO爲:

type r = float;
type g = float;
type b = float;

type t =
  | Color3(r, g, b);

設計值對象Transform的DO爲:

type t = {position: Position DO};

設計值對象Geometry的DO爲:

type t = {
  vertices: Vertices DO,
  indices: Indices DO,
};

對於值對象Material的DO,咱們須要思考: 在領域模型中,Material組合了一個Shader,這應該如何體現到Material的DO中?

解決方案: 1)將Shader DO的Shader名稱和值對象GLSL拆開 2)Shader DO只包含Shader名稱,它即爲實體Shader的id 3)Material DO包含一個Shader的id

這樣就使Material經過Shader的id(Shader名稱),與Shader關聯起來了!

由於Shader DO移除了值對象GLSL,因此咱們須要重寫與Shader相關的代碼: 1)重寫ShaderShaderEntity.re ShaderShaderEntity.re代碼爲:

type shaderName = string;
type id = shaderName;

type t =
  | Shader(id);

let create = id => Shader(id);

let getId = shader =>
  switch (shader) {
  | Shader(id) => id
  };

2)重寫ShaderManagerShaderEntity.re ShaderManagerShaderEntity.re代碼爲:

type t = {glsls: list((ShaderShaderEntity.t, GLSLShaderVO.t))};

let addGLSL = (shader, glsl) => {
  ShaderManagerRepo.addGLSL(shader, glsl);
};

3)重寫ShaderApService.re ShaderApService.re代碼爲:

let addGLSL = (shaderName, glsl) => {
  ShaderManagerShaderEntity.addGLSL(
    ShaderShaderEntity.create(shaderName),
    GLSLShaderVO.create(glsl),
  );

  //用於運行測試
  Js.log((shaderName, glsl));
};

4)重寫ShaderManagerRepo.re ShaderManagerRepo.re代碼爲:

open ShaderManagerPOType;

let _getGLSLs = ({glsls}) => glsls;

let addGLSL = (shader, glsl) => {
  Repo.setShaderManager({
    ...Repo.getShaderManager(),
    glsls: [
      (ShaderShaderEntity.getId(shader), GLSLShaderVO.value(glsl)),
      ..._getGLSLs(Repo.getShaderManager()),
    ],
  });
};

如今咱們能夠設計值對象Material的DO爲:

type t = {
  shader: Shader DO,
  colors: list(Color3 DO),
};

注意:這裏的字段名是「shader」而不是「shaderName」或者「shaderId」,由於這樣才能直接體現Material組合了一個Shader,而不是組合了一個Shader名稱或Shader id

咱們繼續設計,設計值對象Triangle的DO爲:

type t = {
  transform: Transform DO,
  geometry: Geometry DO,
  material: Material DO,
};

設計聚合根Scene的DO爲:

type t = {triangles: list(Triangle DO)};

三、修改SceneApService.re,實現應用服務 SceneApService.re相關代碼爲:

let addTriangle = (position, (vertices, indices), (shaderName, colors)) => {
  SceneSceneGraphEntity.addTriangle(
    position |> VectorContainerVO.create |> PositionSceneGraphVO.create,
    (
      VerticesSceneGraphVO.create(vertices),
      IndicesSceneGraphVO.create(indices),
    ),
    (
      ShaderShaderEntity.create(shaderName),
      colors |> List.map(color => Color3ContainerVO.create(color)),
    ),
  );

  //用於運行測試
  Js.log(Repo.getScene());
};

四、加入值對象Triangle和它的全部值對象 1)在src/domain_layer/domain/structure/math/value_object/中加入VectorMathVO.re,建立值對象Vector VectorMathVO.re代碼爲:

type t =
  | Vector(float, float, float);

let create = ((x, y, z)) => Vector(x, y, z);

let value = vec =>
  switch (vec) {
  | Vector(x, y, z) => (x, y, z)
  };

2)在src/domain_layer/domain/scene/scene_graph/value_object/中加入PositionSceneGraphVO.re,建立值對象Position PositionSceneGraphVO.re代碼爲:

type t =
  | Position(VectorMathVO.t);

let create = value => Position(value);

let value = position =>
  switch (position) {
  | Position(pos) => pos
  };

3)在src/domain_layer/domain/structure/container/value_object/中加入Color3ContainerVO.re,建立值對象Color3 Color3ContainerVO.re代碼爲:

type r = float;
type g = float;
type b = float;

type t =
  | Color3(r, g, b);

let create = ((r, g, b)) => Color3(r, g, b);

let value = color =>
  switch (color) {
  | Color3(r, g, b) => (r, g, b)
  };

4)在src/domain_layer/domain/scene/scene_graph/value_object/中加入TransformSceneGraphVO.re,建立值對象Transform TransformSceneGraphVO.re代碼爲:

type t = {position: PositionSceneGraphVO.t};

5)修改GeometrySceneGraphVO.re,定義DO GeometrySceneGraphVO.re相關代碼爲:

type t = {
  vertices: VerticesSceneGraphVO.t,
  indices: IndicesSceneGraphVO.t,
};

6)在src/domain_layer/domain/scene/scene_graph/value_object/中加入MaterialSceneGraphVO.re,建立值對象Material MaterialSceneGraphVO.re代碼爲:

type t = {
  shader: ShaderShaderEntity.t,
  colors: list(Color3ContainerVO.t),
};

7)在src/domain_layer/domain/scene/scene_graph/value_object/中加入TriangleSceneGraphVO.re,建立值對象Triangle TriangleSceneGraphVO.re代碼爲:

type t = {
  transform: TransformSceneGraphVO.t,
  geometry: GeometrySceneGraphVO.t,
  material: MaterialSceneGraphVO.t,
};

五、在src/domain_layer/domain/scene/scene_graph/value_object/中加入SceneSceneGraphEntity.re,建立聚合根Scene SceneSceneGraphEntity.re代碼爲:

type t = {triangles: list(TriangleSceneGraphVO.t)};

let addTriangle = (position, (vertices, indices), (shader, colors)) => {
  SceneRepo.addTriangle(position, (vertices, indices), (shader, colors));
};

六、在src/infrastructure_layer/data/po/中加入ScenePOType.re,定義Scene PO的類型 ScenePOType.re代碼爲:

type transform = {position: (float, float, float)};

type geometry = {
  vertices: Js.Typed_array.Float32Array.t,
  indices: Js.Typed_array.Uint16Array.t,
};

type material = {
  shader: string,
  colors: list((float, float, float)),
};

type triangle = {
  transform,
  geometry,
  material,
};

type scene = {triangles: list(triangle)};

七、修改POType.re POType.re相關代碼爲:

type po = {
  ...
  scene: ScenePOType.scene,
};

八、實現Scene相關的倉庫

咱們按照倉庫依賴關係,從上往下開始實現: 1)建立文件夾src/domain_layer/repo/scene/ 2)在src/domain_layer/repo/scene/中加入SceneRepo.re,實現倉庫對Scene PO的triangles字段的操做 ShaderManagerRepo.re代碼爲:

open ScenePOType;

let _getTriangles = ({triangles}) => triangles;

let addTriangle = (position, (vertices, indices), (shader, colors)) => {
  Repo.setScene({
    ...Repo.getScene(),
    triangles: [
      TriangleSceneRepo.create(
        TransformSceneRepo.create(position),
        GeometrySceneRepo.create(vertices, indices),
        MaterialSceneRepo.create(shader, colors),
      ),
      ..._getTriangles(Repo.getScene()),
    ],
  });
};

3)在src/domain_layer/repo/scene/中加入TrianglerSceneRepo.re,實現建立Scene PO的一個Triangle數據 TriangleSceneRepo.re代碼爲:

open ScenePOType;

let create = (transform, geometry, material) => {
  transform,
  geometry,
  material,
};

4)在src/domain_layer/repo/scene/中加入TransformSceneRepo.re,實現建立Scene PO的一個Triangle的Transform數據 TransformSceneRepo.re代碼爲:

open ScenePOType;

let create = position => {
  position: position |> PositionSceneGraphVO.value |> VectorMathVO.value,
};

5)在src/domain_layer/repo/scene/中加入GeometrySceneRepo.re,實現建立Scene PO的一個Triangle的Geometry數據 GeometrySceneRepo.re代碼爲:

open ScenePOType;

let create = (vertices, indices) => {
  vertices: vertices |> VerticesSceneGraphVO.value,
  indices: indices |> IndicesSceneGraphVO.value,
};

6)在src/domain_layer/repo/scene/中加入MaterialSceneRepo.re,實現建立Scene PO的一個Triangle的Material數據 MaterialSceneRepo.re代碼爲:

open ScenePOType;

let create = (shader, colors) => {
  shader: shader |> ShaderShaderEntity.getId,
  colors: colors |> List.map(color => {color |> Color3ContainerVO.value}),
};

九、修改Repo.re,實現倉庫對Scene PO的操做 Repo.re相關代碼爲:

let getScene = () => {
  let po = ContainerManager.getPO();

  po.scene;
};

let setScene = scene => {
  let po = ContainerManager.getPO();

  {...po, scene} |> ContainerManager.setPO;
};

十、修改CreateRepo.re,實現建立Scene PO CreateRepo.re相關代碼爲:

let create = () => {
  ...
  scene: {
    triangles: [],
  },
};

十一、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

十二、實現index.html相關代碼

index.html代碼爲:

<script>
    ...
    //準備三個三角形的位置數據
    var [position1, position2, position3] = [
      [0.75, 0.0, 0.0],
      [-0.0, 0.0, 0.5],
      [-0.5, 0.0, -2.0]
    ];
    //準備三個三角形的顏色數據
    var [colors1, colors2, colors3] = [
      [[1.0, 0.0, 0.0]],
      [[0.0, 0.8, 0.0], [0.0, 0.5, 0.0]],
      [[0.0, 0.0, 1.0]]
    ];

    wd.Scene.addTriangle(position1, [vertices1, indices1], [shaderName1, colors1]);
    wd.Scene.addTriangle(position2, [vertices2, indices2], [shaderName2, colors2]);
    wd.Scene.addTriangle(position3, [vertices3, indices3], [shaderName1, colors3]);
  </script>

1三、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了三次Scene PO的數據

實現「SceneJsAPI.setCamera」

一、修改SceneJsAPI.re,實現API SceneJsAPI.re相關代碼爲:

let setCamera = SceneApService.setCamera;

二、修改SceneApService.re,實現應用服務 SceneApService.re相關代碼爲:

let setCamera = ((eye, center, up), (near, far, fovy, aspect)) => {
  SceneSceneGraphEntity.setCamera(
    (
      EyeSceneGraphVO.create(eye),
      CenterSceneGraphVO.create(center),
      UpSceneGraphVO.create(up),
    ),
    (
      NearSceneGraphVO.create(near),
      FarSceneGraphVO.create(far),
      FovySceneGraphVO.create(fovy),
      AspectSceneGraphVO.create(aspect),
    ),
  );

  //用於運行測試
  Js.log(Repo.getScene());
};

三、加入Camera的全部值對象 1)在src/domain_layer/domain/scene/scene_graph/value_object/中加入EyeSceneGraphVO.re、CenterSceneGraphVO.re、UpSceneGraphVO.re,建立值對象Eye、Center、Up EyeSceneGraphVO.re代碼爲:

type t =
  | Eye(VectorMathVO.t);

let create = value => Eye(value);

let value = eye =>
  switch (eye) {
  | Eye(value) => value
  };

CenterSceneGraphVO.re代碼爲:

type t =
  | Center(VectorMathVO.t);

let create = value => Center(value);

let value = center =>
  switch (center) {
  | Center(value) => value
  };

UpSceneGraphVO.re代碼爲:

type t =
  | Up(VectorMathVO.t);

let create = value => Up(value);

let value = up =>
  switch (up) {
  | Up(value) => value
  };

2)在src/domain_layer/domain/scene/scene_graph/value_object/中加入NearSceneGraphVO.re、FarSceneGraphVO.re、FovySceneGraphVO.re、AspectSceneGraphVO.re,建立值對象Near、Far、Fovy、Aspect NearSceneGraphVO.re代碼爲:

type t =
  | Near(float);

let create = value => Near(value);

let value = near =>
  switch (near) {
  | Near(value) => value
  };

FarSceneGraphVO.re代碼爲:

type t =
  | Far(float);

let create = value => Far(value);

let value = far =>
  switch (far) {
  | Far(value) => value
  };

FovySceneGraphVO.re代碼爲:

type t =
  | Fovy(float);

let create = value => Fovy(value);

let value = fovy =>
  switch (fovy) {
  | Fovy(value) => value
  };

AspectSceneGraphVO.re代碼爲:

type t =
  | Aspect(float);

let create = value => Aspect(value);

let value = aspect =>
  switch (aspect) {
  | Aspect(value) => value
  };

四、在src/domain_layer/domain/scene/scene_graph/value_object/中加入CameraSceneGraphVO.re,建立值對象Camera CameraSceneGraphVO.re代碼爲:

type t = {
  eye: EyeSceneGraphVO.t,
  center: CenterSceneGraphVO.t,
  up: UpSceneGraphVO.t,
  near: NearSceneGraphVO.t,
  far: FarSceneGraphVO.t,
  fovy: FovySceneGraphVO.t,
  aspect: AspectSceneGraphVO.t,
};

五、修改SceneSceneGraphEntity.re,將Camera DO做爲Scene DO 的camera字段的數據,並實現setCamera函數: SceneSceneGraphEntity.re相關代碼爲:

type t = {
  ...
  camera: option(CameraSceneGraphVO.t),
};

...

let setCamera = ((eye, center, up), (near, far, fovy, aspect)) => {
  SceneRepo.setCamera((eye, center, up), (near, far, fovy, aspect));
};

六、修改ScenePOType.re,加入Scene PO的camera字段的數據類型 ScenePOType.re相關代碼爲:

type camera = {
  eye: (float, float, float),
  center: (float, float, float),
  up: (float, float, float),
  near: float,
  far: float,
  fovy: float,
  aspect: float,
};

type scene = {
  ...
  camera: option(camera),
};

七、實現Scene->Camera相關的倉庫

1)在src/domain_layer/repo/scene/中加入CameraSceneRepo.re,實現建立Scene PO的一個Camera數據 CameraSceneRepo.re代碼爲:

open ScenePOType;

let create = ((eye, center, up), (near, far, fovy, aspect)) => {
  eye: eye |> EyeSceneGraphVO.value |> VectorMathVO.value,
  center: center |> CenterSceneGraphVO.value |> VectorMathVO.value,
  up: up |> UpSceneGraphVO.value |> VectorMathVO.value,
  near: NearSceneGraphVO.value(near),
  far: FarSceneGraphVO.value(far),
  fovy: FovySceneGraphVO.value(fovy),
  aspect: AspectSceneGraphVO.value(aspect),
};

2)修改SceneRepo.re,實現倉庫對Scene PO的camera字段的操做 SceneRepo.re相關代碼爲:

let setCamera = ((eye, center, up), (near, far, fovy, aspect)) => {
  Repo.setScene({
    ...Repo.getScene(),
    camera:
      Some(
        CameraSceneRepo.create(
          (eye, center, up),
          (near, far, fovy, aspect),
        ),
      ),
  });
};

八、修改CreateRepo.re,實現建立Scene PO的camera字段 CreateRepo.re相關代碼爲:

let create = () => {
  ...
  scene: {
    ...
    camera: None,
  },
};

九、在項目根目錄上執行webpack命令,更新wd.js文件

yarn webpack

十、實現index.html相關代碼

index.html代碼爲:

<script>
    ...
    //準備相機數據
    var [eye, center, up] = [
      [0.0, 0.0, 5.0],
      [0.0, 0.0, -100.0],
      [0.0, 1.0, 0.0]
    ];
    var canvas = document.querySelector("#webgl");
    var [near, far, fovy, aspect] = [
      1.0,
      100.0,
      30.0,
      canvas.width / canvas.height
    ];

    wd.Scene.setCamera([eye, center, up], [near, far, fovy, aspect]);
  </script>

十一、運行測試

運行index.html頁面

打開控制檯,能夠看到打印了一次Scene PO的數據,它包含Camera的數據

相關文章
相關標籤/搜索