1、最近项目功能要实现步态采集实时传输数据3D动效,找了一只右脚的obj模型,展示左脚用到了镜像反转,数学知识欧拉角和四元组,首先介绍一下欧拉角和四元组的区别:
允澄 2023-09-09 22.30.22
“欧拉角是指三个旋转角度,分别为绕X轴旋转的角度、绕Y轴旋转的角度和绕Z轴旋转的角度,表示物体在空间中的位置和方向。而四元数是一种复数形式的数字,由实部和虚部组成,可以用来表示旋转。 欧拉角比较直观,易于理解,但是欧拉角存在万向锁问题,即当某一轴旋转角度为90度时,其他两个轴的旋转将变得不可控。而四元数则没有万向锁问题,并且可以实现平滑旋转,因此在计算机图形学和游戏开发中被广泛应用
2、pubspec.yaml
three_dart: ^0.0.16
three_dart_jsm: ^0.0.10
3、 下载three_dart,在MultiViews2里添加,3D模型下载链接
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide Matrix4;
import 'package:flutter/services.dart';
import 'package:flutter_gl/flutter_gl.dart';
import 'package:three_dart/three3d/math/index.dart';
import 'package:three_dart/three3d/math/matrix4.dart';
import 'package:three_dart/three_dart.dart' as three;
import 'package:three_dart_jsm/three_dart_jsm.dart' as three_jsm;
class MultiViews extends StatefulWidget {
final String fileName;
const MultiViews({Key? key, required this.fileName}) : super(key: key);
@override
State<MultiViews> createState() => _MyAppState();
}
class _MyAppState extends State<MultiViews> {
three.WebGLRenderer? renderer;
bool show = false;
@override
void initState() {
super.initState();
if (!kIsWeb) {
init();
}
}
init() async {
var three3dRender = FlutterGlPlugin();
await three3dRender.initialize(options: {"width": 1024, "height": 1024, "dpr": 1.0});
await three3dRender.prepareContext();
Map<String, dynamic> options = {
"width": 1024,
"height": 1024,
"gl": three3dRender.gl,
"antialias": true,
};
renderer = three.WebGLRenderer(options);
renderer!.autoClear = true;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.fileName),
),
body: SingleChildScrollView(child: _build(context)),
);
}
Widget _build(BuildContext context) {
return Column(
children: [
MultiViews2(renderer: renderer)
],
);
}
}
class MultiViews2 extends StatefulWidget {
final three.WebGLRenderer? renderer;
const MultiViews2({Key? key, this.renderer}) : super(key: key);
@override
State<MultiViews2> createState() => _MultiViews2State();
}
class _MultiViews2State extends State<MultiViews2> {
three.WebGLRenderer? renderer;
late FlutterGlPlugin three3dRender;
int? fboId;
double width = 300;
double height = 300;
List list = [];
List list_euler = [];
Size? screenSize;
late three.Scene scene;
late three.Camera camera;
late three.Mesh mesh;
double dpr = 1.0;
var amount = 4;
bool verbose = true;
bool disposed = false;
bool loaded = false;
int count = 0;
int count1 = 0;
late three.Object3D object;
late three.Object3D object1;
// late three.Texture texture;
late three.WebGLMultisampleRenderTarget renderTarget;
three.AnimationMixer? mixer;
three.Clock clock = three.Clock();
dynamic sourceTexture;
var angle = 0.0;
@override
void initState() {
super.initState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
width = screenSize!.width;
height = width;
three3dRender = FlutterGlPlugin();
Map<String, dynamic> options = {
"antialias": true,
"alpha": false,
"width": width.toInt(),
"height": height.toInt(),
"dpr": dpr
};
await three3dRender.initialize(options: options);
setState(() {});
Future.delayed(const Duration(milliseconds: 100), () async {
await three3dRender.prepareContext();
initScene();
// initScene();
});
}
initSize(BuildContext context) {
if (screenSize != null) {
return;
}
final mqd = MediaQuery.of(context);
screenSize = mqd.size;
dpr = mqd.devicePixelRatio;
initPlatformState();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: Builder(
builder: (BuildContext context) {
initSize(context);
return SingleChildScrollView(child: _build(context));
},
),
);
}
Widget _build(BuildContext context) {
return Column(
children: [
Stack(
children: [
Container(
width: width,
height: height,
color: Colors.black,
child: Builder(builder: (BuildContext context) {
if (kIsWeb) {
return three3dRender.isInitialized
? HtmlElementView(viewType: three3dRender.textureId!.toString())
: Container();
} else {
return three3dRender.isInitialized
? Texture(textureId: three3dRender.textureId!)
: Container(
color: Colors.yellow,
);
}
})),
],
),
],
);
}
clickRender() {
print(" click render... ");
animate();
}
render() {
int t = DateTime.now().millisecondsSinceEpoch;
final gl = three3dRender.gl;
if (!kIsWeb) renderer!.setRenderTarget(renderTarget);
renderer!.render(scene, camera);
int t1 = DateTime.now().millisecondsSinceEpoch;
if (verbose) {
print("render cost: ${t1 - t} ");
print(renderer!.info.memory);
print(renderer!.info.render);
}
// 重要 更新纹理之前一定要调用 确保gl程序执行完毕
gl.flush();
print("three3dRender 2: ${three3dRender.textureId} render: sourceTexture: $sourceTexture ");
if (!kIsWeb) {
three3dRender.updateTexture(sourceTexture);
}
}
initRenderer() {
renderer = widget.renderer;
if (renderer == null) {
Map<String, dynamic> options = {
"width": width,
"height": height,
"gl": three3dRender.gl,
"antialias": true,
};
renderer = three.WebGLRenderer(options);
renderer!.autoClear = true;
}
if (!kIsWeb) {
var pars = three.WebGLRenderTargetOptions({"format": three.RGBAFormat});
renderTarget = three.WebGLMultisampleRenderTarget((width * dpr).toInt(), (height * dpr).toInt(), pars);
renderTarget.samples = 4;
renderer!.setRenderTarget(renderTarget);
sourceTexture = renderer!.getRenderTargetGLTexture(renderTarget);
}
}
initScene() {
initRenderer();
initPage();
}
initPage() async {
camera = three.PerspectiveCamera(45, width / height, 1, 2200);
camera.position.set(3, 6, 100);
list = await loadData();
list_euler = await load_eulerData();
scene = three.Scene();
// scene.background = three.Color(1, 1, 0);
var ambientLight = three.AmbientLight(0xcccccc, 0.4);
scene.add(ambientLight);
var pointLight = three.PointLight(0xffffff, 0.6);
pointLight.position.set(10, 10, 5);
camera.add(pointLight);
scene.add(camera);
camera.lookAt(scene.position);
var loader = three_jsm.OBJLoader(null);
//添加本地obj模型
object = await loader.loadAsync('assets/11536_foot_V3.obj');
object.position.set(-10, 10, 5);
//镜像反转
object.scale.set(-1, 1);
scene.add(object);
object1 = await loader.loadAsync('assets/11536_foot_V3.obj');
object1.position.set(10, 10, 5);
object1.castShadow = true;
object1.receiveShadow = true;
scene.add(object1);
//这里的quaternion模拟6轴数据中结算后的四元数
// var quaternion = object.quaternion;
// quaternion.setFromAxisAngle(three.Vector3(1,0,0), angle);
// angle += 0.1;
// object.applyQuaternion(quaternion);
loaded = true;
animate();
}
animate() async {
var delta = clock.getDelta();
//这里quaternion模拟从6轴数据中解算后的四元组
object.rotation.x = list_euler[count][0];
object.rotation.y = list_euler[count][1];
object.rotation.z = list_euler[count][2];
count += 1;
object1.rotation.x = list_euler[count1][0];
object1.rotation.y = list_euler[count1][1];
object1.rotation.z = list_euler[count1][2];
count1 += 1;
mixer?.update(delta);
render();
Future.delayed(const Duration(milliseconds: 10), () {
animate();
});
}
@override
void dispose() {
print(" dispose ............. ");
disposed = true;
three3dRender.dispose();
super.dispose();
}
}
Future<String> loadJsonFromAssets(String assetsPath) async {
return await rootBundle.loadString(assetsPath);
}
Future<Map<String, dynamic>> loadAndDecodeJson(String assetsPath) async {
String jsonData = await rootBundle.loadString(assetsPath);
return jsonDecode(jsonData);
}
Future<List<List>> loadData() async {
// 1. 从 assets 中读取文件
String jsonString = await rootBundle.loadString('assets/data.json');
// 2. 将 JSON 字符串解析为 Dart 对象
List<dynamic> jsonList = jsonDecode(jsonString);
// 3. 转换每个对象为所需的数组格式
List<List<double>> result = jsonList.map((item) {
return [
(item['x'] as num).toDouble(),
(item['y'] as num).toDouble(),
(item['z'] as num).toDouble(),
(item['w'] as num).toDouble(),
];
}).toList();
return result;
}
Future<List<List>> load_eulerData() async {
// 1. 从 assets 中读取文件
String jsonString = await rootBundle.loadString('assets/data_euler.json');
// 2. 将 JSON 字符串解析为 Dart 对象
List<dynamic> jsonList = jsonDecode(jsonString);
// 3. 转换每个对象为所需的数组格式
List<List<double>> result = jsonList.map((item) {
return [
(item['roll'] as num).toDouble(),
(item['pitch'] as num).toDouble(),
(item['yaw'] as num).toDouble(),
];
}).toList();
return result;
}