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|  |  | 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  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  51 52 (3)在编辑窗口右上角的工具栏,点击"运行"按钮运行。 53 54  55 56### 2、源码结构 57 58- 代码结构分析,整个工程的代码结构如下: 59 60  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 275 276 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