zoukankan      html  css  js  c++  java
  • 从0开发3D引擎(十一):使用领域驱动设计,从最小3D程序中提炼引擎(第二部分)

    大家好,本文根据领域驱动设计的成果,开始实现从最小的3D程序中提炼引擎。

    上一篇博文

    从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)

    下一篇博文

    从0开发3D引擎(十二):使用领域驱动设计,从最小3D程序中提炼引擎(第三部分)

    本文流程

    我们根据上文的成果,按照下面的步骤开始实现从最小的3D程序中提炼引擎:
    1、建立代码的文件夹结构
    2、index.html实现调用引擎API
    3、根据用例图和API层的设计,用伪代码实现index.html
    4、按照index.html从上往下的API调用顺序,依次实现API:setCanvasById、setClearColor、addGLSL、createTriangleVertexData、addTriangle、setCamera

    回顾上文

    上文的领域驱动设计获得了下面的成果:
    1、用户逻辑和引擎逻辑
    2、分层架构视图和每一层的设计
    3、领域驱动设计的战略成果
    1)引擎子域和限界上下文划分
    2)限界上下文映射图
    3)流程图
    4、领域驱动设计的战术成果
    1)领域概念
    2)领域视图
    5、数据视图和PO的相关设计
    6、一些细节的设计
    7、基本的优化

    解释基本的操作

    如何在浏览器上运行index.html

    1、在TinyWonder项目根目录上执行start命令:

    yarn start
    

    2、在浏览器地址中输入下面的url并回车,即可运行index.html页面

    http://127.0.0.1:8080
    

    开始实现

    打开最小3D程序的TinyWonder项目,现在我们开始具体实现。

    准备

    我们要完全重写src/的内容,因此在项目根目录上新建mine/文件夹,将src/文件夹拷贝mine/中,并清空src/文件夹。

    通过备份src/文件夹,我们能容易地调出最小3D程序的代码供我们参考。

    建立代码的文件夹结构,约定模块文件的命名规则

    模块文件的命名原则

    • 加上所属层级/模块的后缀名
      这是为了减少重名的几率
    • 尽量简洁
      因此应该让后缀名尽可能地短,只要几乎不会出现重名的情况,那么不仅可以省略一些层级/模块的后缀名,而且有些模块文件甚至完全不加后缀名

    一级和二级文件夹

    如下图所示:
    截屏2020-03-04上午8.28.11.png-16.5kB

    这是按照分层架构来划分的文件夹:

    • 一级文件夹(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了

    我们通过下面的步骤来实现该方案:
    1、创建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();
    });
    

    2、创建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"
    }
    

    3、运行测试
    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:
    1、按照index.html从上往下的API调用顺序,确定要实现的API
    2、按照引擎的层级,从上层的API开始,实现每一层的对应模块
    3、实现index.html的相关代码
    4、运行测试

    现在我们按照顺序,确定要实现API->“CanvasJsAPI.setCanvasById” 。

    现在来实现该API:

    实现“CanvasJsAPI.setCanvasById”

    1、在src/api_layer/api/中加入CanvasJsAPI.re,实现API
    CanvasJsAPI.re代码为:

    //因为应用服务CanvasApService的setCanvasById函数输入和输出的DTO与这里(API)的setCanvasById函数输入和输出的VO相同,所以不需要在两者之间转换
    let setCanvasById = CanvasApService.setCanvasById;
    

    2、在src/application_layer/service/中加入CanvasApService.re,实现应用服务
    CanvasApService.re代码为:

    let setCanvasById = canvasId => {
      CanvasCanvasEntity.setCanvasById(canvasId);
    
      //打印canvasId,用于运行测试
      Js.log(canvasId);
    };
    

    3、把最小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 = "";
    

    4、在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}),
      );
    

    5、因为需要使用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)
      };
    

    6、在src/infrastructure_layer/data/po/中加入POType.re,定义PO的类型,目前PO只包含Canvas PO
    POType.re代码为:

    //因为在创建PO时,Canvas PO(一个画布)并不存在,所以它为option类型
    type po = {canvas: option(CanvasPOType.canvas)};
    

    7、在src/infrastructure_layer/data/po/中加入CanvasPOType.re,定义Canvas PO类型
    CanvasPOType.re代码为:

    type canvas = DomExtend.htmlElement;
    

    8、因为定义了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)
      };
    };
    

    9、在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;
    };
    

    10、在src/infrastructure_layer/data/中加入ContainerManager.re,实现PO Container中PO的读写
    ContainerManager.re代码为:

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

    11、在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(),
    };
    

    12、在src/domain_layer/repo/中加入CreateRepo.re,实现创建PO
    CreateRepo.re代码为:

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

    13、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    14、实现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>
    

    15、运行测试

    运行index.html页面

    打开控制台,可以看到打印了"webgl"

    实现“GraphicsJsAPI.setClearColor”

    1、在src/api_layer/api/中加入GraphicsJsAPI.re,实现API
    GraphicsJsAPI.re代码为:

    let setClearColor = GraphicsApService.setClearColor;
    

    2、在src/application_layer/service/中加入GraphicsApService.re,实现应用服务
    GraphicsApService.re代码为:

    let setClearColor = clearColor => {
      ContextContextEntity.setClearColor(Color4ContainerVO.create(clearColor));
    
      //用于运行测试
      Js.log(clearColor);
    };
    

    3、在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)
      };
    

    4、在src/domain_layer/domain/webgl_context/context/entity/中加入ContextContextEntity.re,创建聚合根Context
    ContextContextEntity.re代码为:

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

    5、在src/infrastructure_layer/data/po/中加入ContextPOType.re,定义Context PO类型
    ContextPOType.re代码为:

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

    6、修改POType.re
    POType.re相关代码为:

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

    7、在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),
      });
    };
    

    8、修改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;
    };
    

    9、修改CreateRepo.re,实现创建Context PO
    CreateRepo.re相关代码为:

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

    10、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    11、实现index.html相关代码

    index.html代码为:

      <script>
        ...
        //准备清空颜色缓冲时的颜色值
        var clearColor = [0.0, 0.0, 0.0, 1.0];
    
        wd.Graphics.setClearColor(clearColor);
      </script>
    

    12、运行测试

    运行index.html页面

    打开控制台,可以看到打印了数组:[0,0,0,1]

    实现“ShaderJsAPI.addGLSL”

    1、在src/api_layer/api/中加入ShaderJsAPI.re,实现API
    ShaderJsAPI.re代码为:

    let addGLSL = ShaderApService.addGLSL;
    

    2、设计领域模型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);
    

    3、在src/application_layer/service/中加入ShaderApService.re,实现应用服务
    ShaderApService.re代码为:

    let addGLSL = (shaderName, glsl) => {
      ShaderManagerShaderEntity.addGLSL(
        ShaderShaderEntity.create(shaderName, GLSLShaderVO.create(glsl)),
      );
    
      //用于运行测试
      Js.log((shaderName, glsl));
    };
    

    4、在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
      };
    

    5、在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)
      };
    

    6、在src/domain_layer/domain/shader/shader/entity/中加入ShaderManagerShaderEntity.re,创建聚合根ShaderManager
    ShaderManagerShaderEntity.re代码为:

    type t = {glsls: list(ShaderShaderEntity.t)};
    
    let addGLSL = shader => {
      ShaderManagerRepo.addGLSL(shader);
    };
    

    7、在src/infrastructure_layer/data/po/中加入ShaderManagerPOType.re,定义ShaderManager PO的类型
    ShaderManagerPOType.re代码为:

    //shaderId就是Shader的名称
    type shaderId = string;
    
    type shaderManager = {glsls: list((shaderId, (string, string)))};
    

    8、修改POType.re
    POType.re相关代码为:

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

    9、在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()),
        ],
      });
    };
    

    10、修改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;
    };
    

    11、修改CreateRepo.re,实现创建ShaderManager PO
    CreateRepo.re相关代码为:

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

    12、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    13、实现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>
    

    14、运行测试

    运行index.html页面

    打开控制台,可以看到打印了两个Shader的数据:
    截屏2020-02-29下午12.13.07.png-78.1kB

    实现“SceneJsAPI.createTriangleVertexData”

    1、在src/api_layer/api/中加入SceneJsAPI.re,实现API
    SceneJsAPI.re代码为:

    let createTriangleVertexData = SceneApService.createTriangleVertexData;
    

    2、在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;
    };
    

    3、在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);
    };
    

    4、在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
      };
    

    5、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    6、实现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>
    

    7、运行测试

    运行index.html页面

    打开控制台,可以看到打印了三次顶点数据

    实现“SceneJsAPI.addTriangle”

    1、修改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),
      );
    };
    

    2、设计聚合根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)};
    

    3、修改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());
    };
    

    4、加入值对象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,
    };
    

    5、在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));
    };
    

    6、在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)};
    

    7、修改POType.re
    POType.re相关代码为:

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

    8、实现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}),
    };
    

    9、修改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;
    };
    

    10、修改CreateRepo.re,实现创建Scene PO
    CreateRepo.re相关代码为:

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

    11、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    12、实现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>
    

    13、运行测试

    运行index.html页面

    打开控制台,可以看到打印了三次Scene PO的数据

    实现“SceneJsAPI.setCamera”

    1、修改SceneJsAPI.re,实现API
    SceneJsAPI.re相关代码为:

    let setCamera = SceneApService.setCamera;
    

    2、修改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());
    };
    

    3、加入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
      };
    

    4、在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,
    };
    

    5、修改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));
    };
    

    6、修改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),
    };
    

    7、实现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),
            ),
          ),
      });
    };
    

    8、修改CreateRepo.re,实现创建Scene PO的camera字段
    CreateRepo.re相关代码为:

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

    9、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    10、实现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>
    

    11、运行测试

    运行index.html页面

    打开控制台,可以看到打印了一次Scene PO的数据,它包含Camera的数据

  • 相关阅读:
    springboot集成thymeleaf中不能返回页面,只返回字符串
    MySql 视图
    边缘计算网关的作用
    什么是物联网网关?它有什么功能?
    RS232串口跟RS485串口有什么优缺点
    4G DTU在油田远程监控中的应用
    远程IO模块有何用途
    Spring5快乐教程(一)Spring概述
    vue水印-第一种方法
    js获取随机打乱的数组
  • 原文地址:https://www.cnblogs.com/chaogex/p/12411575.html
Copyright © 2011-2022 走看看