[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
yarn start
二、在瀏覽器地址中輸入下面的url並回車,便可運行index.html頁面web
http://127.0.0.1:8080
打開最小3D程序的TinyWonder項目,如今咱們開始具體實現。npm
咱們要徹底重寫src/的內容,所以在項目根目錄上新建mine/文件夾,將src/文件夾拷貝mine/中,並清空src/文件夾。json
經過備份src/文件夾,咱們能容易地調出最小3D程序的代碼供咱們參考。gulp
以下圖所示: canvas
這是按照分層架構來劃分的文件夾:
api_layer/api/放置API模塊文件,如SceneJsAPI.re等
application_layer/service/放置應用服務模塊文件,如SceneApService.re等
domain_layer/domain/放置領域服務、實體和值對象的模塊文件
domain_layer/repo/放置倉庫的模塊文件
domain/的子文件夾對應引擎的各個子域,以下圖所示:
引擎子域文件夾的子文件夾對應該子域的限界上下文,以下圖所示:
限界上下文文件夾的子文件均爲entity/、value_object/、service/,分別放置實體、值對象和領域服務的模塊文件。 部分截圖以下圖所示:
entity/、value_object/、service/文件夾的模塊文件的命名規則分別爲:
若是從這三個子文件夾的文件中提出公共代碼的模塊文件(如在後面,會從值對象ImmutableHashMap和值對象MutableHashMap中提出HashMap模塊),則該模塊文件的命名規則爲: 模塊名+限界上下文.re (如將HashMap模塊文件命名爲HashMapContainer.re)
infrastructure_layer/data/的文件夾結構以下圖所示:
ContainerManager.re負責實現「容器管理」
container/放置PO Container相關的模塊文件
po/放置PO類型定義的文件
infrastructure_layer/external/文件夾結構以下圖所示:
external_object/放置外部對象的FFI文件 library/放置js庫的FFI文件
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>
這個方案有下面的缺點:
所以,咱們使用下面的方案來實現,該方案能夠解決上一個方案的缺點:
咱們經過下面的步驟來實現該方案: 一、建立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」
咱們根據用例圖和設計的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:
一、在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"
一、在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]
一、在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的數據:
一、在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.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.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的數據