1# 如何使用OpenGL绘制3D图形
2
3## 场景介绍
4
5XComponent控件常用于相机预览流的显示和游戏画面的绘制,在OpenHarmony上,可以配合Native Window创建OpenGL开发环境,并最终将OpenGL绘制的图形显示到XComponent控件。本文将采用"Native C++"模板,调用OpenGL ES图形库绘制3D图形(三棱锥),并将结果渲染到页面的XComponent控件中进行展示。同时,还可以在屏幕上通过触摸滑动手势对三棱锥进行旋转,最终得到不同角度的图形并显示到页面。
6
7## 效果展示
8
9| 首页                                                    | 滑动屏幕旋转变换                                                |
10| ----------------------------------------------------- | ------------------------------------------------------- |
11| ![3d-graphic-index.png](figures/3d-graphic-index.png) | ![3d-graphic-rotate.png](figures/3d-graphic-rotate.png) |
12
13## 环境要求
14
15- 本示例仅支持在标准系统上运行。
16
17- IDE:DevEco Studio 3.1 Beta2
18
19- SDK:Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
20
21## 实现思路
22
23利用XComponent组件,并配合OpenHarmony的native侧提供的native window用来创建EGL/OpenGLES环境,进而使用标准的OpenGL ES相关API进行应用开发。其中
24XComponent组件作为一种绘制组件,通常用于满足开发者较为复杂的自定义绘制需求,当XComponent设置为surface类型时通常用于EGL/OpenGLES和媒体数据写入,并将其显示在XComponent组件上。
25
26## 开发步骤
27
28### 1、环境搭建
29
30我们首先要完成应用开发环境的搭建,本示例运行DAYU200开发板上。
31
32- 搭建应用开发环境
33
34  (1)开始前,请参考完成[DevEco Studio的安装和开发环境配置](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-deveco-studio-overview-0000001263280421)35
36  > 说明:
37  >
38  > 为确保运行效果,本案例以使用DevEco Studio 3.1 Beta2 SDK:API9 (3.2.11.9)版本为例。
39
40  ![3d-graphic-creat-project.png](figures/3d-graphic-creat-project.png)
41
42  (2)开发环境配置完成后,创建工程(模板选择“Native C++”),选择eTS语言开发。
43
44- 应用调测工程创建完成后,选择使用真机进行调测。
45
46  (1)将搭载OpenHarmony标准系统的开发板与电脑连接。
47
48  (2)点击File> Project Structure... > Project>SigningConfigs界面勾选“Automatically generate signature”,等待自动签名完成即可,最后点击“OK”。如下图所示:
49
50  ![3d-graphic-creat-signature.png](figures/3d-graphic-signature.png)
51
52  (3)在编辑窗口右上角的工具栏,点击"运行"按钮运行。
53
54  ![3d-graphic-run.png](figures/3d-graphic-run.png)
55
56### 2、源码结构
57
58- 代码结构分析,整个工程的代码结构如下:
59
60  ![3d-graphic-creat-code-struct.png](figures/3d-graphic-code-struct.png)
61
62- 文件说明如下:
63
64  ```shell
65  .
66  └── main
67      ├── cpp
68      │   ├── app_napi.cpp      //C++与ArkTS中XComponent控件交互的napi接口实现
69      │   ├── CMakeLists.txt    //CMake规则配置文件,NAPI C/C++代码编译需要配置该文件
70      │   ├── include
71      │   │   ├── app_napi.h
72      │   │   ├── tetrahedron.h //三棱锥类实现头文件
73      │   │   └── util
74      │   ├── module.cpp        //NAPI模块注册
75      │   ├── napi_manager.cpp
76      │   ├── napi_util.cpp
77      │   ├── tetrahedron.cpp   //三棱锥的绘制OpenGL实现
78      │   └── type
79      │       └── libentry
80      ├── ets
81      │   ├── entryability
82      │   │   └── EntryAbility.ts
83      │   └── pages
84      │       └── Index.ets      //主页面
85      ├── module.json5
86      └── resources              //资源文件目录
87          ├── base
88          │   ├── element
89          │   ├── media
90          │   └── profile
91          ├── en_US
92          │   └── element
93          ├── rawfile
94          └── zh_CN
95              └── element
96  ```
97
98### 3、绘制流程
99
100- 3D绘制函数调用流程如下:
101
102```mermaid
103graph LR
104A[napi init]
105B[AppNapi Export]
106C[AppNapi OnSurfaceCreatedCB]
107D[AppNapi OnSurfaceCreated]
108E[triangles Init]
109F[triangles Update]
110
111
112A ----> B
113B ----> C
114C -- Xcomponent, window--> D
115D -- Native window --> E
116E ----> F
117```
118
119- 在Tetrahedron类的Update方法中使用GLES3库着色器绘制,最终通过ArkUI的XComponent组件显示,流程如下:
120
121```mermaid
122graph LR
123A[glClear]
124B[设置gl_Position]
125C[设置gl_FragColor]
126D[glDrawArrays]
127
128
129A ----> B
130B ----> C
131C ----> D
132```
133
134### 4、C++(OpenGL)实现
135
136C++端方法源码是工程的entry/src/main/cpp/tetrahedron.cpp文件。
137
138- 注册模块先定义一个模块,在entry/src/main/cpp/module.cpp文件中,对应结构体类型为napi_module,模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)注册到系统中;
139
140  ```cpp
141  /*
142   * Napi Module define
143   */
144  static napi_module appNapiModule = {
145      .nm_version = 1,
146      .nm_flags = 0,
147      .nm_filename = nullptr,
148      .nm_register_func = Init,
149      .nm_modname = "tetrahedron_napi",
150      .nm_priv = ((void*)0),
151      .reserved = { 0 },
152  };
153
154  /*
155   * Module register function
156   */
157  extern "C" __attribute__((constructor)) void RegisterModule(void)
158  {
159      napi_module_register(&appNapiModule);
160  }
161  ```
162
163- 调用OpenGL相关图形API绘制三棱锥
164
165  (1)初始化
166
167  ```cpp
168  int32_t Tetrahedron::Init(void *window, int32_t width,  int32_t height)
169  {
170      window_ = window;
171      width_ = width;
172      height_ = height;
173
174      LOGI("Init window = %{public}p, w = %{public}d, h = %{public}d.", window, width, height);
175      mEglWindow = reinterpret_cast<EGLNativeWindowType>(window);
176
177      // 1. create sharedcontext
178      mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
179      if (mEGLDisplay == EGL_NO_DISPLAY) {
180          LOGE("unable to get EGL display.");
181          return -1;
182      }
183
184      EGLint eglMajVers, eglMinVers;
185      if (!eglInitialize(mEGLDisplay, &eglMajVers, &eglMinVers)) {
186          mEGLDisplay = EGL_NO_DISPLAY;
187          LOGE("unable to initialize display");
188          return -1;
189      }
190
191      int version = 3;
192      mEGLConfig = getConfig(version, mEGLDisplay);
193      if (mEGLConfig == nullptr) {
194          LOGE("GLContextInit config ERROR");
195          return -1;
196      }
197
198      // 2. Create EGL Surface from Native Window
199      EGLint winAttribs[] = {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, EGL_NONE};
200      if (mEglWindow) {
201          mEGLSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mEglWindow, winAttribs);
202          if (mEGLSurface == nullptr) {
203              LOGE("eglCreateContext eglSurface is null");
204              return -1;
205          }
206      }
207
208      // 3. Create EGLContext from
209      int attrib3_list[] = {
210          EGL_CONTEXT_CLIENT_VERSION, 2,
211          EGL_NONE
212      };
213
214      mEGLContext = eglCreateContext(mEGLDisplay, mEGLConfig, mSharedEGLContext, attrib3_list);
215      if (!eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
216          LOGE("eglMakeCurrent error = %{public}d", eglGetError());
217      }
218
219      mProgramHandle = CreateProgram(vertexShader, fragmentShader);
220      if (!mProgramHandle) {
221          LOGE("Could not create CreateProgram");
222          return -1;
223      }
224
225      LOGI("Init success.");
226
227      return 0;
228  }
229  ```
230
231  其中,顶点着色器实现如下:
232
233  ```cpp
234  char vertexShader[] =
235      "attribute  vec4 apos;\n"
236      "attribute  vec4 a_color;\n"
237      "attribute  vec4 a_normal;\n"
238      "uniform vec3 u_lightColor;\n"
239      "uniform vec3 u_lightDirection;\n"
240      "uniform mat4 a_mx;\n"
241      "uniform mat4 a_my;\n"
242      "varying  vec4 v_color;\n"
243      "void main(){\n"
244      "float radian = radians(30.0);\n"
245      "float cos = cos(radian);\n"
246      "float sin = sin(radian);\n"
247      "  gl_Position = a_mx * a_my * vec4(apos.x, apos.y, apos.z, 1.0);\n"
248      "  vec3 normal = normalize((a_mx * a_my * a_normal).xyz);\n"
249      "  float dot = max(dot(u_lightDirection, normal), 0.0);\n"
250      "  vec3 reflectedLight = u_lightColor * a_color.rgb * dot;\n"
251      "  v_color = vec4(reflectedLight, a_color.a);\n"
252      "}\n\0";
253  ```
254
255  (2)图像渲染
256
257  ​    OpenGL ES图像渲染中着色器涉及到内置变量如下,所谓内置变量就是不用声明可以直接赋值,主要是为了实现特定的功能。
258
259| 序号  | 内置变量          | 含义                | 值数据类型 |
260| --- | ------------- | ----------------- | ----- |
261| 1   | gl_PointSize  | 点渲染模式,方形点区域渲染像素大小 | float |
262| 2   | gl_Position   | 顶点位置坐标            | vec4  |
263| 3   | gl_FragColor  | 片元颜色值             | vec4  |
264| 4   | gl_FragCoord  | 片元坐标,单位像素         | vec2  |
265| 5   | gl_PointCoord | 点渲染模式对应点像素坐标      | vec2  |
266
267  ​    而本次渲染涉及到两个内建变量:gl_Position和gl_FragColor;
268
269  ​    其中,gl_Position变量表示最终传入片元着色器片元化要使用的顶点位置坐标,取值范围为-1.0到1.0,点超过该范围将自动被裁剪。初始化代码如下:
270
271```cpp
272gl_Position = a_mx * a_my * vec4(apos.x, apos.y, apos.z, 1.0);
273```
274
275276
277  a_my为y轴旋转矩阵,获取到旋转角度后初始化旋转矩阵;a_mx为x轴旋转矩阵,apos为绘制多面体点矩阵;
278
279  这些值的初始化通过glUniformMatrix4fv函数实现:
280
281```cpp
282    mxGL_APICALL void GL_APIENTRY glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)
283```
284
285  其中参数的含义如下:
286
287| 序号  | 参数名       | 含义                                                            |
288| --- | --------- | ------------------------------------------------------------- |
289| 1   | location  | uniform对应的变量名                                                 |
290| 2   | count     | 需要加载数据的数组元素的数量或者需要修改的矩阵的数量                                    |
291| 3   | transpose | 指明矩阵是列优先(column major)矩阵(GL_FALSE)还是行优先(row major)矩阵(GL_TRUE) |
292| 4   | value     | 指向由count个元素的数组的指针                                             |
293
294​        gl_FragColor变量用于确定图形的颜色,可通过设置不同片段着色器的颜色,实现立体效果。
295
296        片段着色器实现如下:
297
298```cpp
299char fragmentShader[] =
300    "precision mediump float;\n"
301    "varying vec4 v_color;\n"
302    "void main () {\n"
303    "   gl_FragColor = v_color;\n"
304    "}\n\0";
305```
306
307       三棱锥核心绘制代码如下:
308
309```cpp
310 void Tetrahedron::Update(float angleX, float angleY)
311 {
312      angleY_ = angleY;
313      angleX_ = angleX;
314      glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
315      glClear(GL_COLOR_BUFFER_BIT);
316      glUseProgram(mProgramHandle);
317
318      unsigned int aposLocation = glGetAttribLocation(mProgramHandle, "apos");
319      unsigned int a_color = glGetAttribLocation(mProgramHandle, "a_color");
320      unsigned int a_normal = glGetAttribLocation(mProgramHandle, "a_normal");
321      unsigned int u_lightColor = glGetUniformLocation(mProgramHandle, "u_lightColor");
322      unsigned int u_lightDirection = glGetUniformLocation(mProgramHandle, "u_lightDirection");
323      unsigned int mx = glGetUniformLocation(mProgramHandle, "a_mx");
324      unsigned int my = glGetUniformLocation(mProgramHandle, "a_my");
325
326     /**
327     y轴旋转度
328     **/
329     float radianY = angleY * PI /180.0;
330     float cosY = cosf(radianY);
331     float sinY = sinf(radianY);
332     float myArr[] = {
333         cosY,0,-sinY,0,  0,1,0,0,  sinY,0,cosY,0,  0,0,0,1
334    };
335     glUniformMatrix4fv(my, 1,false, myArr);
336
337     /**
338     x轴旋转度
339     **/
340     float radianX = angleX * PI /180.0;
341     float cosX = cosf(radianX);
342     float sinX = sinf(radianX);
343     float mxArr[] = {
344         1,0,0,0,  0,cosX,-sinX,0,  0,sinX,cosX,0,  0,0,0,1
345    };
346     glUniformMatrix4fv(mx, 1,false, mxArr);
347
348     /**
349      给平行光传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
350      **/
351     glUniform3f(u_lightColor, 1.0, 1.0, 1.0);
352     // 保证向量(x,y,z)的长度为1,即单位向量
353     float x = 1.0/sqrt(15), y = 2.0/sqrt(15), z = 3.0/sqrt(15);
354     glUniform3f(u_lightDirection, x,-y,z);
355
356     /**
357      创建顶点位置数据数组data,原点到各顶点的距离都为1
358     **/
359     float data[] = {
360         -0.75, -0.50, -0.43, 0.75, -0.50, -0.43, 0.00, -0.50, 0.87,
361         0.75, -0.50, -0.43, 0.00, -0.50, 0.87, 0.00, 1.00, 0.00,
362         0.00, -0.50, 0.87, 0.00, 1.00, 0.00, -0.75, -0.50, -0.43,
363         0.00, 1.00, 0.00, -0.75, -0.50, -0.43, 0.75, -0.50, -0.43,
364     };
365
366     /**
367      创建顶点颜色数组colorData
368      **/
369     float colorData[] = {
370         1,0,0, 1,0,0, 1,0,0,//红色——面1
371         1,0,0, 1,0,0, 1,0,0,//红色——面2
372         1,0,0, 1,0,0, 1,0,0,//红色——面3
373         1,0,0, 1,0,0, 1,0,0 //红色——面4
374     };
375
376     /**
377      顶点法向量数组normalData
378      **/
379     float normalData[] = {
380         0.00, -1.00, 0.00,  0.00, -1.00, 0.00,  0.00, -1.00, 0.00,
381         -0.83, -0.28, -0.48,  -0.83, -0.28, -0.48,  -0.83, -0.28, -0.48,
382         -0.83, 0.28, 0.48,  -0.83, 0.28, 0.48,  -0.83, 0.28, 0.48,
383         0.00, -0.28, 0.96,  0.00, -0.28, 0.96,  0.00, -0.28, 0.96,
384     };
385
386     /**
387      创建缓冲区buffer,传入顶点位置数据data
388      **/
389     unsigned int buffer;
390     glGenBuffers(1, &buffer);
391     glBindBuffer(GL_ARRAY_BUFFER, buffer);
392     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
393     glVertexAttribPointer(aposLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
394     glEnableVertexAttribArray(aposLocation);
395
396     unsigned int normalBuffer;
397     glGenBuffers(1, &normalBuffer);
398     glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
399     glBufferData(GL_ARRAY_BUFFER, sizeof(normalData), normalData, GL_STATIC_DRAW);
400     glVertexAttribPointer(a_normal, 3, GL_FLOAT, GL_FALSE, 0, 0);
401     glEnableVertexAttribArray(a_normal);
402
403     /**
404      创建缓冲区colorBuffer,传入顶点颜色数据colorData
405      **/
406     unsigned int colorBuffer;
407     glGenBuffers(1, &colorBuffer);
408     glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
409     glBufferData(GL_ARRAY_BUFFER, sizeof(colorData), colorData, GL_STATIC_DRAW);
410     glVertexAttribPointer(a_color, 3, GL_FLOAT, GL_FALSE, 0, 0);
411     glEnableVertexAttribArray(a_color);
412
413     /* 执行绘制命令 */
414     glDrawArrays(GL_TRIANGLES, 0, 12);
415 }
416```
417
418### 5、NAPI接口定义
419
420接口定义为固定写法,在napi_property_descriptor desc[]中,我们需要使用DECLARE_NAPI_FUNCTION宏,以Add函数为例,将函数名字符串"Add"与具体的实现方法napi_value Add(napi_env env, napi_callback_info info)进行关联,即DECLARE_NAPI_FUNCTION("Add", Add)最终添加到desc[]。如下所示,其中UpdateAngle对应的是Native C++的接口,其应用端的接口对应为UpdateAngle,NAPI通过napi_define_properties接口将napi_property_descriptor结构体中的2个接口绑定在一起,并通过exports变量对外导出,使应用层可以调用UpdateAngle和getContext方法。
421
422```cpp
423/*
424 * function for module exports
425 */
426EXTERN_C_START
427static napi_value Init(napi_env env, napi_value exports)
428{
429    LOGE("Init");
430    napi_property_descriptor desc[] = {
431        DECLARE_NAPI_FUNCTION("getContext", NapiManager::GetContext),
432        DECLARE_NAPI_FUNCTION("UpdateAngle", AppNapi::UpdateAngle),
433    };
434
435    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
436
437    bool ret = NapiManager::GetInstance()->Export(env, exports);
438    if (!ret) {
439        LOGE("Init failed");
440    }
441
442    return exports;
443}
444EXTERN_C_END
445```
446
447### 6、NAPI接口实现
448
449​    Tetrahedron::UpdateAngle:传入angleX和angleY两个参数,分别为为绕X,Y轴的旋转角度;作为参数调用```Tetrahedron::UpdateAngle(float angleX, float angleY)```重新渲染,具体代码如下:
450
451```cpp
452napi_value AppNapi::UpdateAngle(napi_env env, napi_callback_info info){
453    LOGE("Tetrahedron UpdateAngle");
454    size_t requireArgc = 2;
455    size_t argc = 2;
456    int speed = 3;
457    napi_value args[2] = {nullptr};
458
459    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
460
461    napi_valuetype valuetype0;
462    napi_typeof(env, args[0], &valuetype0);
463
464    napi_valuetype valuetype1;
465    napi_typeof(env, args[1], &valuetype1);
466
467    double offsetX;
468    napi_get_value_double(env, args[0], &offsetX);
469
470    double offsetY;
471    napi_get_value_double(env, args[1], &offsetY);
472
473    /* 处理offsetX偏移角度 */
474    float tetrahedron_angleX = tetrahedron_->GetAngleX();
475    float tetrahedron_angleY = tetrahedron_->GetAngleY();
476
477    /* 上下滑动绕x轴 */
478    if(offsetY < 0){
479        tetrahedron_angleX = tetrahedron_angleX + speed;
480    }
481    else{
482        tetrahedron_angleX = tetrahedron_angleX - speed;
483    }
484
485    /* 左右滑动绕y轴 */
486    if(offsetX < 0){
487        triangles_angleY = triangles_angleY + speed;
488    }
489    else{
490        triangles_angleY = triangles_angleY - speed;
491    }
492
493    tetrahedron_angleY = normalize(tetrahedron_angleY);
494    tetrahedron_angleX = normalize(tetrahedron_angleX);
495    tetrahedron_->Update(tetrahedron_angleX, tetrahedron_angleY);
496
497    /* 创建一个数组 */
498    napi_value ret;
499    napi_create_array(env, &ret);
500
501    /* 设置数组并返回 */
502    napi_value num;
503    napi_create_int32(env, tetrahedron_angleX, &num);
504    napi_set_element(env, ret, 0, num);
505    napi_create_int32(env, tetrahedron_angleY, &num);
506    napi_set_element(env, ret, 1, num);
507
508    return ret;
509}
510```
511
512​    GetContext:得到渲染所XComponent的上下文context,以便后续绑定XComponentID渲染,具体代码如下:
513
514```cpp
515napi_value NapiManager::GetContext(napi_env env, napi_callback_info info)
516{
517    napi_status status;
518    napi_value exports;
519    size_t argc = 1;
520    napi_value args[1];
521    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
522
523    if (argc != 1) {
524        napi_throw_type_error(env, NULL, "Wrong number of arguments");
525        return nullptr;
526    }
527
528    napi_valuetype valuetype;
529    status = napi_typeof(env, args[0], &valuetype);
530    if (status != napi_ok) {
531        return nullptr;
532    }
533    if (valuetype != napi_number) {
534        napi_throw_type_error(env, NULL, "Wrong arguments");
535        return nullptr;
536    }
537
538    int64_t value;
539    NAPI_CALL(env, napi_get_value_int64(env, args[0], &value));
540    NAPI_CALL(env, napi_create_object(env, &exports));
541
542    switch (value) {
543        case int64_t(ContextType::APP_LIFECYCLE):
544            {
545                /* AppInit 对应 app.ets中的应用生命周期 onCreate, onShow, onHide, onDestroy */
546                LOGD("GetContext APP_LIFECYCLE");
547                /* Register App Lifecycle */
548                napi_property_descriptor desc[] = {
549                    DECLARE_NAPI_FUNCTION("onCreate", NapiManager::NapiOnCreate),
550                    DECLARE_NAPI_FUNCTION("onShow", NapiManager::NapiOnShow),
551                    DECLARE_NAPI_FUNCTION("onHide", NapiManager::NapiOnHide),
552                    DECLARE_NAPI_FUNCTION("onDestroy", NapiManager::NapiOnDestroy),
553                };
554                NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
555            }
556
557            break;
558        case int64_t(ContextType::JS_PAGE_LIFECYCLE):
559            {
560                /* JS Page */
561                LOGD("GetContext JS_PAGE_LIFECYCLE");
562                napi_property_descriptor desc[] = {
563                    DECLARE_NAPI_FUNCTION("onPageShow", NapiManager::NapiOnPageShow),
564                    DECLARE_NAPI_FUNCTION("onPageHide", NapiManager::NapiOnPageHide),
565                };
566                NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
567            }
568            break;
569        default:
570            LOGE("unknown type");
571    }
572    return exports;
573}
574```
575
576​    Export:先拿到XComponentID等信息后,通过NapiManager得到context,再通过context得到处理3D绘画的appNapi类并进行相应输出处理。部分代码如下(具体请查看源码):
577
578```cpp
579bool NapiManager::Export(napi_env env, napi_value exports)
580{
581    napi_status status;
582    napi_value exportInstance = nullptr;
583    OH_NativeXComponent *nativeXComponent = nullptr;
584    int32_t ret;
585    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { };
586    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
587
588    status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
589    if (status != napi_ok) {
590        return false;
591    }
592
593    status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));
594    if (status != napi_ok) {
595        return false;
596    }
597
598    ret = OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize);
599    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
600        return false;
601    }
602
603    std::string id(idStr);
604    auto context = NapiManager::GetInstance();
605    if (context) {
606        context->SetNativeXComponent(id, nativeXComponent);
607        auto app = context->GetApp(id);
608        app->SetNativeXComponent(nativeXComponent);
609        app->Export(env, exports);
610        return true;
611    }
612
613    return false;
614}
615```
616
617### 7、ArkTS接口定义
618
619(1)修改 index.d.ts 用于对外提供方法、说明(命名为tetrahedron_napi.d.ts)。
620
621```ts
622//传入x,y偏移量并返回x,y旋转角
623export const UpdateAngle:(offsetX:number,offsetY:number)=>Array;
624```
625
626(2)在同目录下的 oh-package.json5 文件中将 tetrahedron_napi.d.ts 与cpp文件关联起来。
627
628```json
629{
630  "name": "libtetrahedron_napi.so",
631  "types": "./tetrahedron_napi.d.ts",
632  "version": "1.0.0",
633  "description": "Please describe the basic information."
634}
635```
636
637(3)修改项目的oh-package.json5文件,添加动态库。
638
639```json
640{
641  "license": "",
642  "devDependencies": {
643    "@types/libtetrahedron_napi.so": "file:./src/main/cpp/type/libentry"
644  },
645  "author": "",
646  "name": "entry",
647  "description": "Please describe the basic information.",
648  "main": "",
649  "version": "1.0.0",
650  "dependencies": {}
651}
652```
653
654### 8、CMake规则配置
655
656entry/src/main/cpp/CMakeLists.txt是CMake规则文件。
657
658`project`:用于设置项目(project)的名称。
659
660`set(CMAKE_CXX_STANDARD 11)`:设置C++标准。
661
662`include_directories`:用于包含头文件。
663
664`add_library`:编译产生链接库。
665
666`target_link_libraries`:指定链接给定目标和/或其依赖项时要使用的库或标志,在PUBLIC字段后的库会被链接到tetrahedron_napi中。
667
668```textile
669# the minimum version of CMake.
670cmake_minimum_required(VERSION 3.4.1)
671
672project(TetrahedronHap)
673
674set(NATIVE_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
675
676include_directories(${NATIVE_ROOT_PATH}
677                    ${NATIVE_ROOT_PATH}/include
678                    ${NATIVE_ROOT_PATH}/include/util)
679
680add_library(triangles_napi SHARED
681            module.cpp
682            app_napi.cpp
683            tetrahedron.cpp
684            napi_manager.cpp
685            napi_util.cpp)
686
687
688target_link_libraries(tetrahedron_napi PUBLIC EGL)
689target_link_libraries(tetrahedron_napi PUBLIC GLESv3)
690target_link_libraries(tetrahedron_napi PUBLIC hilog_ndk.z)
691target_link_libraries(tetrahedron_napi PUBLIC ace_ndk.z)
692target_link_libraries(tetrahedron_napi PUBLIC ace_napi.z)
693target_link_libraries(tetrahedron_napi PUBLIC libc++.a)
694target_link_libraries(tetrahedron_napi PUBLIC z)
695target_link_libraries(tetrahedron_napi PUBLIC uv)
696target_link_libraries(tetrahedron_napi PUBLIC libace_napi.z.so)
697```
698
699### 9、ArkTS实现
700
701界面实现部分代码如下(具体请参考源码),其中:libraryname参数对应先前设置的模块名:tetrahedron_napi
702
703```ts
704import hilog from '@ohos.hilog';
705import tetrahedron_napi from 'libtetrahedron_napi.so'
706
707@Entry
708@Component
709struct Index {
710  private xcomponentContext = null;
711  private xcomponentId = 'tetrahedron';
712  private offset_x: number = 0.000;
713  private offset_y: number = 0.000;
714  private index: number = 0;
715  private type_: number = 5;
716  private touchTypeDown: number = 0;
717  private touchTypeUp: number = 1;
718  private touchTypeMove: number = 2;
719  private touchTypeCancel: number = 3;
720  @State startVisible: Visibility = Visibility.Visible;
721
722  @State angleArray: Array<number> = new Array<number>();
723  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All })
724  @State offsetX: number = 0
725  @State offsetY: number = 0
726  @State positionX: number = 0
727  @State positionY: number = 0
728  @State message: string = 'wu'
729
730  async aboutToAppear() {
731  }
732
733  build() {
734    Column() {
735      Text($r('app.string.EntryAbility_desc'))
736        .fontSize($r('app.float.head_font_24'))
737        .lineHeight($r('app.float.wh_value_33'))
738        .fontFamily('HarmonyHeiTi-Bold')
739        .fontWeight(FontWeight.Bold)
740        .fontColor($r('app.color.font_color_182431'))
741        .textOverflow({ overflow: TextOverflow.Ellipsis })
742        .textAlign(TextAlign.Start)
743        .margin({ top: $r('app.float.wh_value_13'), bottom: $r('app.float.wh_value_15') });
744
745      Text(this.angleArray[0]&this.angleArray[1]?'X轴旋转:'+this.angleArray[0].toString() +'°\nY轴旋转:'+this.angleArray[1].toString() + '°':'')
746        .fontSize($r('app.float.head_font_24'))
747        .lineHeight($r('app.float.wh_value_33'))
748        .fontFamily('HarmonyHeiTi-Bold')
749        .fontWeight(FontWeight.Bold)
750        .fontColor($r('app.color.font_color_182431'))
751        .textOverflow({ overflow: TextOverflow.Ellipsis })
752        .textAlign(TextAlign.Start)
753        .margin({ top: $r('app.float.wh_value_13'), bottom: $r('app.float.wh_value_15') });
754
755      Stack({ alignContent: Alignment.Center }) {
756        XComponent({ id: this.xcomponentId, type: 'surface', libraryname: 'tetrahedron_napi' })
757          .onLoad((context) => {
758            hilog.info(0x0000, 'Xcomponent', 'onLoad')
759            this.xcomponentContext = context;
760            globalThis.xcomponentContext = this.xcomponentContext;
761            globalThis.xcomponentId = this.xcomponentId;
762            globalThis.touchTypeDown = this.touchTypeDown;
763            globalThis.touchTypeUp = this.touchTypeUp;
764            globalThis.type_ = this.type_;
765            globalThis.index = this.index;
766            globalThis.touchTypeMove = this.touchTypeMove;
767            globalThis.touchTypeCancel = this.touchTypeCancel;
768            globalThis.offset_x = this.offset_x;
769            globalThis.offset_y = this.offset_y;
770          })
771          .width($r('app.float.wh_value_362'))
772          .height($r('app.float.wh_value_362'))
773          .key('tetrahedron')
774          .backgroundColor('#00000000')
775          .onDestroy(() => {
776            globalThis.flag = false;
777            hilog.info(0x0000, "Xcomponent", 'onDestroy')
778          })
779      }
780      .gesture(
781        PanGesture(this.panOption)
782          .onActionStart((event: GestureEvent) => {
783            console.info('onActionStart');
784          })
785          .onActionUpdate((event: GestureEvent) => {
786            this.angleArray = tetrahedron_napi.UpdateAngle(event.offsetX, event.offsetY);
787            hilog.info(0x0000, "Gesture", 'offSet:' + event.offsetX + "," + event.offsetY);
788          })
789          .onActionEnd(() => {
790            this.positionX = this.offsetX;
791            this.positionY = this.offsetY;
792            console.info('onActionEnd');
793          })
794      )
795      .width('100%')
796      .height('100%')
797      .backgroundColor('#00000000')
798    }
799  }
800}
801```
802