【React-Native开发3D应用】React Native加载GLB格式3D模型并打包至Android手机端

news2024/12/25 10:15:14

【React-Native开发3D应用】React Native加载GLB格式3D模型并打包至Android手机端

  • 【加载3D模型】**React Native上如何加载glb格式的模型**
    • 第零步,选择相关模型
    • 第一步,导入相关模型加载库
    • 第二步,自定义GLB模型加载钩子
    • 第三步,借助gltf.pmnd.rs与自定义的模型加载钩子加载模型
  • 【打包APK】解决**React Native上的打包问题**
    • 生成数字签名
    • 配置项目以使用签名密钥
    • 构建 APK
    • 注意事项
  • 【3D内容的开发工具】高效率3D内容开发工具
    • gltf.report 查看模型信息,调整模型大小
    • 本地的`gltfjsx`或者在线的`https://gltf.pmnd.rs/`
    • model-viewer
    • A-Frame - Make WebVR
  • 相关参考

最近在Native上开发3D应用,选用的是React-Native(https://github.com/facebook/react-native),原因如下:

  • 开发语言使用JS,和ThreeJS(https://github.com/mrdoob/three.js)的适配性比较好。(Three.js 是一个轻量级的JavaScript库,用于在网页上创建和显示动画的3D计算机图形)
  • 多端部署,开发一次,可部署到Android、IOS、Web等平台

但是,这里我们忽略了一个问题,ThreeJS主要用于Web端的3D开发,而移动端这边,虽然React-three-fiber(https://github.com/pmndrs/react-three-fiber)可以可用于react-native的开发

但是还是会存在一些Web端和移动端的库的适配性问题,如下:

  • 3D模型的加载,Web端的模型加载非常简单,但是移动端的模型加载可能需要通过一些复杂的手段实现。(甚至打包APK都可能会出现例如3D模型没有打包进去等问题)
  • 物理引擎,大部分物理引擎对Web端的适配性比较好,但是很少有支持移动端的物理引擎

这里,我们重点讨论一下如何较好的解决【React Native上3D模型加载和打包的问题

【加载3D模型】React Native上如何加载glb格式的模型

如果大家之前还没有接触过React Native开发3D应用,可以看一下这篇Youtube。

A Beginner’s Guide to 3D Animations in React Native with three.js

但是这一块有一个问题,就是正常情况下移动端可以较好加载obj格式模型,但是无法像Web端那样直接加载例如glb格式的模型,我在网上找了众多解决办法,一个合理的解释是R3F库的适配性问题,库中的有一些方法无法很好的适用于移动端,你得配合其他的模型加载库。

直到我找到这个Issues,我最终可以很完美的加载glb格式模型:

https://github.com/pmndrs/react-three-fiber/issues/2992

接下来我们梳理一下这个Issues的解题步骤:

第零步,选择相关模型

这里推荐一个模型网站:

Sketchfab - The best 3D viewer on the web

这里我们以网站上的一只滑板兔模型为例,下载好模型:
在这里插入图片描述

第一步,导入相关模型加载库

这是我的dependencies:

"dependencies": {
    "@react-three/drei": "^9.82.1",
    "@react-three/fiber": "^8.14.1",
    "assert": "^2.1.0",
    "base64-arraybuffer": "^1.0.2",
    "expo": "^49.0.0",
    "expo-asset-utils": "^3.0.0",
    "expo-file-system": "^15.4.4",
    "expo-gl": "~13.0.1",
    "r3f-native-orbitcontrols": "^1.0.8",
    "react": "18.2.0",
    "react-native": "0.72.4",
    "three": "^0.156.1",
    "three-stdlib": "^2.25.1"
  }

第二步,自定义GLB模型加载钩子

我们需要自定义一个模型加载的钩子useGLTFCustom

// src\useGLTFCustom.tsx
import assert from 'assert';
import {decode} from 'base64-arraybuffer';
import {resolveAsync} from 'expo-asset-utils';
import * as FileSystem from 'expo-file-system';
import {suspend} from 'suspend-react';
import THREE from 'three';
import {GLTF, GLTFLoader} from 'three-stdlib';

async function loadFileAsync({
  asset,
  funcName,
}: {
  asset: unknown;
  funcName: string;
}) {
  if (!asset) {
    throw new Error(`ExpoTHREE.${funcName}: Cannot parse a null asset`);
  }
  return (await resolveAsync(asset)).localUri ?? null;
}

type ObjectGraph = {
  nodes: Record<string, THREE.Mesh>;
  materials: Record<string, THREE.Material>;
};

// Collects nodes and materials from a THREE.Object3D
export function buildGraph(object: THREE.Object3D) {
  const data: ObjectGraph = {nodes: {}, materials: {}};
  if (object) {
    object.traverse((obj: any) => {
      if (obj.name) {
        data.nodes[obj.name] = obj;
      }
      if (obj.material && !data.materials[obj.material.name]) {
        data.materials[obj.material.name] = obj.material;
      }
    });
  }
  return data;
}
async function loadGLTFAsync({
  asset,
}: {
  asset: unknown;
}): Promise<GLTF & ObjectGraph> {
  const uri = await loadFileAsync({
    asset,
    funcName: 'loadGLTFAsync',
  });

  assert(uri, 'loadGLTFAsync uri should exist');

  const base64 = await FileSystem.readAsStringAsync(uri, {
    encoding: FileSystem.EncodingType.Base64,
  });

  const arrayBuffer = decode(base64);
  const loader = new GLTFLoader();

  const res = await loader.parseAsync(arrayBuffer, 'beb');

  if (res.scene) {
    Object.assign(res, buildGraph(res.scene));
  }

  return res as GLTF & ObjectGraph;
}

export const useGLTFCustom = (asset: unknown) =>
  suspend(async () => loadGLTFAsync({asset}), ['useGLTFCustom', asset]);

第三步,借助gltf.pmnd.rs与自定义的模型加载钩子加载模型

GLTF -> React Three Fiber

这个网站是一个在线工具,它可以将GLTF/GLB模型转换为React Three Fiber组件。通过这个工具,开发者可以轻松地将3D内容转换为JSX组件,这是与React兼容的格式,从而能够便捷地将3D模型集成到网页应用程序中。如下:

在这里插入图片描述

以这只滑板兔为例,我们直接复制代码,并修改使用我们自定义的useGLTFCustom模型加载钩子

// src\assets\Bunny.jsx
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

import React from 'react';
import {useGLTFCustom} from '../useGLTFCustom';    // 导入自定义模型加载钩子
import modelPath from './silent_ash_simple.glb';
const Bunny = props => {
  const {nodes, materials} = useGLTFCustom(modelPath);

  // console.log('materials:', materials);
  // console.log('nodes:', nodes);

  return (
    <group {...props} dispose={null}>
      <group rotation={[-Math.PI / 2, 0, 0]} scale={0.398}>
        <group rotation={[Math.PI / 2, 0, 0]}>
          <group position={[0.002, 2.616, 0.003]}>
            <mesh
              castShadow
              receiveShadow
              geometry={nodes.Object_4.geometry}
              material={materials.Bunny}
            />
            <mesh
              castShadow
              receiveShadow
              geometry={nodes.Object_5.geometry}
              material={materials.Skateboard}
            />
          </group>
        </group>
      </group>
    </group>
  );
};

export default Bunny;

然后直接在App.tsx中引用即可

import React, {Suspense} from 'react';
import {Canvas} from '@react-three/fiber/native';
import {StyleSheet, View} from 'react-native';
import useControls from 'r3f-native-orbitcontrols';    // 3D 视图控制轨道
import Bunny from './src/assets/Bunny';                // 导入3D模型的jsx

const App = () => {
  const [OrbitControls, events] = useControls();
  return (
    <View style={styles.container} {...events}>
      <Canvas>
        <OrbitControls enableZoom={false} enablePan={false} />
        <ambientLight intensity={2} />
        <directionalLight position={[0, 1, 0]} args={['white', 2]} />
        <Suspense fallback={null}>
          <Bunny />
        </Suspense>
      </Canvas>
    </View>
  );
};

export default App;

const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: 'white'},
});

在这里插入图片描述

可以看到这个是手机端的一个效果,也是非常不错!

【打包APK】解决React Native上的打包问题

生成数字签名

keytool -genkeypair -v -keystore boardrop.keystore -alias boardrop -keyalg RSA -keysize 2048 -validity 10000

在这里插入图片描述

keytool 命令是用于生成和管理密钥库的,它是 Java Development Kit (JDK) 的一部分。

这个命令使用 Java 的 keytool 工具来生成一个新的密钥库(keystore)和密钥对(key pair),通常用于为 Android 应用程序签名。下面是对该命令每个参数的详细解释:

  • keytool: 这是 Java 开发工具包(JDK)提供的一个命令行工具,用于管理密钥和证书。
  • genkeypair: 这个选项指示 keytool 生成一个新的密钥对(即一个公钥和一个私钥)。
  • v: 表示在执行过程中输出详细信息(verbose output),帮助你了解 keytool 在做什么。
  • keystore boardrop.keystore: 这指定了密钥库的文件名。如果该文件不存在,keytool 将会创建一个新文件。如果没有指定路径,它将被创建在命令执行的当前目录中。
  • alias boardrop: 这是你为密钥对设置的别名。在签名 Android 应用时,你将使用这个别名来引用密钥库中的密钥对。
  • keyalg RSA: 指定密钥算法为 RSA。这是一种公钥加密算法,广泛用于 SSL/TLS 和数字签名。
  • keysize 2048: 这指定了密钥的大小。2048 位是一个常用的大小,它提供了足够的安全性。
  • validity 10000: 这指定了证书的有效期(以天为单位)。在这个例子中,密钥对和其生成的证书将在大约 27 年后过期。对于 Android 应用的签名密钥,Google 建议至少设置为 25 年的有效期,以确保密钥的有效期超过任何应用的预期寿命。

执行该命令时,它会提示你输入密钥库的密码,这是保护密钥库的密码。你还需要为你的密钥对设置密码(如果你希望它与密钥库密码不同)。此外,它还会询问一些组织信息,如你的名字和姓氏、组织单位、组织名称、城市或地区名、州或省份名和国家代码。这些信息将被用来创建一个自签名的证书,它与密钥对一起存储在密钥库中。

这个自签名的证书不应该用于生产环境的应用发布,因为它不由证书颁发机构(CA)签发,但它可以用于测试和开发目的。

一旦你生成了密钥库和密钥对,你就可以使用它们来签名你的 Android 应用了。在你的 build.gradle 文件中,你会引用这个密钥库和别名,并提供相应的密码,以便在构建过程中签名 APK。

  1. 当提示输入密钥库口令时,你需要输入一个安全的密码,并记住它。这个密码将用于保护你的密钥库文件。
  2. 你会被要求再次确认密钥库口令。
  3. 接下来,keytool 会询问你一系列问题,用于设置密钥库条目的证书颁发者信息。这包括你的名字、组织单位、组织名称、城市或地区名、省/市/自治区名和国家代码。
  4. 完成后,keytool 会要求你确认信息的正确性,然后输入密钥的密码(如果和密钥库密码一致,可以直接回车)。
  5. 一旦完成,一个包含你的私钥和公钥的密钥库文件(在你的例子中是 boardrop.keystore)将会被创建。

请确保在安全的地方备份你的 keystore 文件和密码,因为如果你想要更新应用或将其上传到应用商店,你将需要它们。

完成了密钥库的创建后,你现在应该已经有了一个 .keystore 文件。接下来的步骤是配置你的 Expo 应用来使用这个密钥库进行签名。

这里是你需要继续的步骤:

配置项目以使用签名密钥

  1. 将密钥库文件 (boardrop.keystore) 移动到你的 Expo 项目中的 android/app 文件夹内。
  2. 打开你的项目中的 android/gradle.properties 文件,并添加以下行:
MYAPP_RELEASE_STORE_FILE=boardrop.keystore
MYAPP_RELEASE_KEY_ALIAS=boardrop
MYAPP_RELEASE_STORE_PASSWORD=你的密钥库密码
MYAPP_RELEASE_KEY_PASSWORD=你的密钥密码
  1. 替换 你的密钥库密码你的密钥密码 为你创建密钥库时所使用的密码。确保这些信息是正确的,因为它们将用于签名你的 APK。
  2. 确认或更新 android/app/build.gradle 文件中的签名配置部分以确保它使用上述属性:
android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(密钥文件(这里应该是'boardrop.keystore'))
                storePassword 你的密钥库密码
                keyAlias keystore别名
                keyPassword 你的密钥密码
            }
        }
    }
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
}

在这里:

  • storeFile是你的keystore文件的路径,如果它位于android/app文件夹,你可以直接用文件名。
  • storePassword是打开keystore时需要的密码。
  • keyAlias是你在创建keystore时指定的别名。
  • keyPassword是你的别名对应的密码。

需要注意的是,这里的keystore_passwordkey_aliaskey_password需要替换为你创建keystore时设置的实际密码和别名。这些信息通常是敏感的,应当保密处理

构建 APK

接下来,通过命令行构建 APK:

  1. 打开终端或命令提示符。
  2. 导航到你的 Expo 项目的 android 目录:
cd android
  1. 清理旧的构建文件:
./gradlew clean
  1. 构建 APK:
./gradlew assembleRelease

这将启动 APK 的构建过程。完成后,你会在 android/app/build/outputs/apk/release 文件夹下找到 APK 文件。

发送给手机安装后的运行效果如下:

React Native手机端运行打包后的GLB格式3D模型

注意事项

如果你遇到构建错误,请检查错误信息以确定问题所在,并适当调整你的配置或修复问题。

  • 错误信息提示 Gradle 无法为它的 JVM 进程分配足够的堆空间。这通常意味着你的系统上可用的物理内存或者虚拟内存不足以满足 Xmx2048m(即 2048MB 或 2GB)的配置。

    减少 JVM 的最大堆大小:
    你可以尝试在 gradle.properties 文件中减少 -Xmx 参数的值。例如,你可以将其设置为 1024MB(1GB):

    org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -XX\:MaxMetaspaceSize\=512m
    
    
    • org.gradle.jvmargs=-Xmx1024M:这个参数设置Gradle守护进程的最大堆内存大小为1024MB(即1GB)。Xmx 是JVM的标准参数之一,用于指定能够使用的最大堆内存量。
    • Dkotlin.daemon.jvm.options="-Xmx2048M":这是一个传递给Kotlin编译守护进程的JVM参数,指定它可以使用的最大堆内存大小为2048MB(即2GB)。这个参数是为了确保在编译Kotlin代码时有足够的内存可用。
    • XX:MaxMetaspaceSize=512m:这个参数设置JVM的Metaspace(元空间)的最大大小为512MB。Metaspace是用于存放类的元数据的内存区域,这是Java 8引入的概念,它取代了以前的永久代(PermGen space)。

    简而言之,这些配置确保了Gradle和Kotlin编译器守护进程有足够的内存来执行构建任务,同时也限制了它们使用内存的上限以防止占用过多的系统资源。

    请注意,如果你的系统内存不足,这些设置可能会导致JVM无法启动,正如你之前遇到的错误所描述的那样。这种情况下,你可能需要降低这些内存设置的值,或者尝试关闭其他内存占用高的应用程序。

    但是如果你确定你的内存是足够的话,系统仍然报错内存不足满足2G配置,这个时候你也许需要考虑你是不是安装错了JDK(你应该安装64位,而不是32位)

    输出JAVA_HOME,如果位于x86下,如下:

在这里插入图片描述

由于路径中包含 "Program Files (x86)",这表明你当前设置的 `JAVA_HOME` 是指向一个32位版本的JDK。这就是为什么你无法为Gradle分配更多内存的原因(32位通常只能分配1~1.5G的虚拟内存)。你需要安装一个64位版本的JDK并更新 `JAVA_HOME` 环境变量。
  • 错误信息显示网络问题导致的,可能是因为你的网络连接不稳定,或者是网络配置(如代理设置)阻止了连接。

    gradle.properties中配置本地代理,例如我用的是clash

    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.proxyPort=7890
    

【3D内容的开发工具】高效率3D内容开发工具

gltf.report 查看模型信息,调整模型大小

glTF Report

在这里插入图片描述

在这里插入图片描述

本地的gltfjsx或者在线的https://gltf.pmnd.rs/

将 gltf 变成 jsx 组件

https://github.com/pmndrs/gltfjsx 或者 https://gltf.pmnd.rs/
在这里插入图片描述

🧑‍💻 它创建所有对象和材质的虚拟图。现在您可以轻松更改内容并重新使用。

🏎️ 图表被修剪(空组、不必要的转换……)并且性能会更好。

⚡️ 它可以选择压缩您的模型,尺寸减少高达 70%-90%。

  • 随意调整mesh
  • 随意组合mesh

model-viewer

3D model-viewer embed

在网络和 AR 中轻松显示交互式 3D 模型

在这里插入图片描述

A-Frame - Make WebVR

A-Frame – Make WebVR

A-Frame 是一个用于构建虚拟现实 (VR) 体验的 Web 框架。 A-Frame 基于 HTML 之上,因此上手简单。但 A-Frame 不仅仅是 3D 场景图或标记语言;它还是一种标记语言。其核心是一个强大的实体组件框架,为 Three.js 提供了声明性、可扩展和可组合的结构。
在这里插入图片描述

相关参考

3D Optimization for Web—How I Got a Model From 26MB Down to 560KB

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1181602.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅析淘宝为什么会严查套红包行为,如何从技术层面实现红包检测规避

最近不少做淘系电商的商家&#xff0c;遇到了一个普遍的问题就是&#xff1a;订单存在买手套红包导致被平台稽查的情况。这种情况&#xff0c;东哥了解到不是发生在某一两个商家身上&#xff0c;而是一个普遍现象。 下面东哥从为什么会稽查套红包的行为、稽查后会有什么后果、如…

学习使用JS实现Echarts的图表保存为图片功能:saveAsImage和getDataURL

学习使用JS实现Echarts的图表保存为图片功能 接口getDataURL实现思路 需求分析 实际项目开发过程中经常会有图表展示功能&#xff0c;同时为了满足用户需要&#xff0c;会附带着图表导出功能&#xff0c;主要形式就是保存为图片。在Echarts中本身就提供这种配置项&#xff0c;…

期中考试后,如何DIY一个成绩发布系统?

期中考试结束后&#xff0c;对于老师们来说&#xff0c;一项重要的任务就是公布考试成绩。然而&#xff0c;传统的成绩公布方式不仅耗时&#xff0c;而且容易出错。为了提高效率&#xff0c;减少误差&#xff0c;我们可以通过各种代码和Excel来实现一个让学生自助查询成绩的系统…

一物一码需求,标签制作功能轻松解决

许多行业存在为人员、物品、设备等做一物一码标签的需求&#xff0c;可使用草料标签制作功能。直接选择标签样式&#xff0c;填入数据&#xff0c;即可批量生成标签&#xff0c;还可批量排版&#xff0c;更易落地。还可保存标签样式&#xff0c;后续多次复用样式&#xff0c;批…

基于java web的计算机office课程平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

linux入门---消费者生产者模型模拟实现

目录标题 消费者生产者模型的理解单生产单消费模拟实现blockqueue.cpp准备工作MainCp.cpp的准备工作构造函数和析构函数的模拟实现push函数的实现pop函数的实现poductor_func函数的实现consumer_func函数的实现程序的测试程序改进一程序的改进二程序的改进三 多生产多消费模拟实…

什么是CCS Concepts

在撰写论文时&#xff0c;看到了CCS Concepts&#xff0c;注意这是对自己论文的分类&#xff0c;不能随便填写。 在ACM的网页"http://dl.acm.org/ccs/ccs.cfm"中选择自己论文的分类&#xff1a; 然后点击左侧的“Assign This CCS Concept”&#xff0c;再选择相关性…

【TDK 电容 】介电质 代码 对应温度及变化率

JB 电解质是什么&#xff1f;没找到&#xff0c;只有TDK有&#xff0c;也只有这个温度的区别&#xff0c;并且已经停产在售。 对比发现是mouser网站关于电容的描述错误。下图显示正确的&#xff0c;再然后是错误的。 在TDK官网&#xff0c;这样的描述 温度特性 分类标准代码温…

制作电子画册的有好帮手---FLBOOK

随着互联网的发展&#xff0c;越来越多的人开始使用电子书来阅读书籍。而将PDF文件转换成在线翻页电子书&#xff0c;则是一种非常方便的方式。今天&#xff0c;给大家推荐一个可以将PDF转在线翻页电子书的网站。 这个网站就是FLBOOK在线制作电子杂志平台&#xff0c;只需要三步…

C++——类和对象(初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类)

初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类 本章思维导图&#xff1a; 注&#xff1a;本章思维导图对应的xmind文件和.png文件都已同步导入至资源 文章目录 初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类1.…

案例-注册页面(css)

html页面用css控制样式&#xff0c;画一个注册页面。 页面最终效果如下&#xff1a; 页面代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>注册页面</title> <style>*{…

文献阅读 - JADE:具有可选外部存档的自适应差分进化

文章目录 标题摘要关键字结论研究背景I. INTRODUCTION 常用基础理论知识II. BASIC OPERATIONS OF DEIII. ADAPTIVE DE ALGORITHMSA. DESAPB. FADEC. SaDED. jDE 研究内容、成果IV. JADEA. DE/Current-to-pbestB. Parameter AdaptationC. Explanations of the Parameter Adaptat…

WSGI与ASGI:两种Python Web服务器网关接口的比较

在当今的Web开发领域&#xff0c;选择合适的服务器网关接口&#xff08;Server Gateway Interface&#xff0c;简称SGI&#xff09;对于提高Web应用程序的性能和并发性至关重要。在Python中&#xff0c;有两种常见的SGI&#xff1a;WSGI和ASGI。本文将深入探讨这两种SGI的异同点…

中国人民大学与加拿大女王大学金融硕士——在职读研,让人生的火花迸发

每个人都像是一块未经雕琢的宝石&#xff0c;隐藏着无尽的光芒。然而&#xff0c;生活、工作中的困难、挫折和压力&#xff0c;就像尘土一样&#xff0c;掩盖了我们的闪亮之处。只有当我们冲破这些阻碍&#xff0c;才能让内在的光芒照亮世界。中国人民大学与加拿大女王大学金融…

Q-Vision+CANpro Max总线解决方案

智能联网技术在国内的发展势头迅猛&#xff0c;随着汽车智能化、网联化发展大潮的到来&#xff0c;智能网联汽车逐步成为汽车发展的主要趋势。越来越多整车厂诉求&#xff0c;希望可以提供本土的测量软件&#xff0c;特别是关于ADAS测试。而风丘科技推出的Q-Vision软件不仅可支…

一键批量剪辑:视频随机分割新玩法,高效剪辑不再难

随着视频内容的日益丰富&#xff0c;人们对于视频剪辑的需求也日益增长。而传统的视频剪辑方式往往需要耗费大量的时间和精力&#xff0c;让许多非专业人士望而却步。然而&#xff0c;现在有一款名为“云炫AI智剪”的软件&#xff0c;它为我们提供了一种全新的视频剪辑方式——…

数据结构:AVL树的旋转(平衡搜索二叉树)

1、AVL树简介 AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1&#xff0c;所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis&#xff0c;他们…

uniapp原生插件之安卓串口操作原生插件

插件介绍 安卓串口操作原生插件&#xff0c;支持设置串口&#xff0c;波特率&#xff0c;停止位&#xff0c;数据位&#xff0c;校验位&#xff0c;流控以及延迟&#xff0c;支持粘包处理解决分包问题&#xff0c;支持多串口操作&#xff0c;无需root 插件地址 安卓串口操作…

2023年【危险化学品经营单位安全管理人员】考试资料及危险化学品经营单位安全管理人员考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年危险化学品经营单位安全管理人员考试资料为正在备考危险化学品经营单位安全管理人员操作证的学员准备的理论考试专题&#xff0c;每个月更新的危险化学品经营单位安全管理人员考试试卷祝您顺利通过危险化学品经…

【深度神经网络(DNN)】实现车牌识别

文章目录 前言一、数据集介绍二、步骤1.导包2.参数配置3.数据处理4.模型定义5.模型训练6.模型预测 总结 前言 课内实践作业 车牌识别 一、数据集介绍 1.车牌识别数据集&#xff1a;VehicleLicense车牌识别数据集包含16151张单字符数据&#xff0c;所有的单字符均为严格切割且…