├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── chipmunk.fbx ├── chipmunk.png ├── knight.fbx ├── knight.png ├── shark.fbx ├── shark.png ├── shark2.png ├── teddy.png ├── teddy_idle.fbx ├── teddy_walk.fbx ├── turtle.fbx └── turtle.png ├── example └── example.dart ├── lib ├── fbx_parser │ ├── fbx.dart │ └── fbx │ │ ├── bit_operators.dart │ │ ├── fbx_ascii_parser.dart │ │ ├── fbx_binary_parser.dart │ │ ├── fbx_element.dart │ │ ├── fbx_loader.dart │ │ ├── fbx_parser.dart │ │ ├── input_buffer.dart │ │ ├── matrix_utils.dart │ │ └── scene │ │ ├── fbx_anim_curve.dart │ │ ├── fbx_anim_curve_node.dart │ │ ├── fbx_anim_evaluator.dart │ │ ├── fbx_anim_key.dart │ │ ├── fbx_anim_layer.dart │ │ ├── fbx_anim_stack.dart │ │ ├── fbx_camera.dart │ │ ├── fbx_camera_switcher.dart │ │ ├── fbx_cluster.dart │ │ ├── fbx_deformer.dart │ │ ├── fbx_display_mesh.dart │ │ ├── fbx_edge.dart │ │ ├── fbx_frame_rate.dart │ │ ├── fbx_geometry.dart │ │ ├── fbx_global_settings.dart │ │ ├── fbx_layer.dart │ │ ├── fbx_layer_element.dart │ │ ├── fbx_light.dart │ │ ├── fbx_mapping_mode.dart │ │ ├── fbx_material.dart │ │ ├── fbx_mesh.dart │ │ ├── fbx_node.dart │ │ ├── fbx_node_attribute.dart │ │ ├── fbx_null.dart │ │ ├── fbx_object.dart │ │ ├── fbx_polygon.dart │ │ ├── fbx_pose.dart │ │ ├── fbx_property.dart │ │ ├── fbx_reference_mode.dart │ │ ├── fbx_scene.dart │ │ ├── fbx_skeleton.dart │ │ ├── fbx_skin_deformer.dart │ │ ├── fbx_texture.dart │ │ └── fbx_video.dart └── fbx_viewer │ ├── fbx3d_model.dart │ ├── fbx3d_object.dart │ ├── fbx3d_viewer.dart │ ├── flutter_fbx3d_viewer.dart │ ├── painter │ ├── globals.dart │ ├── texture_data.dart │ └── vertices_painter.dart │ ├── utils │ ├── converter.dart │ ├── logger.dart │ ├── math_utils.dart │ ├── screen_utils.dart │ └── utils.dart │ └── widgets │ └── zoom_gesture_detector.dart ├── pix ├── pic1.jpg ├── pic2.jpg ├── pic3.jpg ├── pic4.jpg ├── pic5.jpg └── pic6.jpg ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | * Textured version update 4 | 5 | ## 0.0.2 6 | 7 | * Weights FIX 8 | 9 | ## 0.0.1 10 | 11 | * First release 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License") 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter FBX 3D Viewer 2 | 3 | Flutter package for viewing FBX 3D animated files with textures 4 | 5 | **This library is experimental. Some FBX files, particularly older fbx files, may not load correctly. No guarantee is provided as FBX is a closed proprietary format.** 6 | 7 | This library is based on the [dart_fbx](https://github.com/brendan-duncan/dart_fbx) library 8 | 9 | ## Pictures 10 | 11 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic1.jpg) 12 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic2.jpg) 13 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic3.jpg) 14 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic4.jpg) 15 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic5.jpg) 16 | ![alt text](https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/master/pix/pic6.jpg) 17 | 18 | ## Video 19 | 20 | [![Video](http://img.youtube.com/vi/hTnkwEGFu9k/0.jpg)](https://www.youtube.com/watch?v=hTnkwEGFu9k) 21 | 22 | 23 | ## Example 24 | 25 | [FBX Viewer Example](https://github.com/klaszlo8207/Flutter-FBX-3D-Viewer/blob/master/example/example_app.dart) 26 | 27 | ## Properties 28 | ``` 29 | Fbx3DViewer({ 30 | @required this.size, 31 | @required this.fbxPath, // "assets/asd.fbx" or sd card path 32 | @required this.lightPosition, 33 | @required this.initialZoom, 34 | @required this.animationSpeed, // 0-1 35 | @required this.fbx3DViewerController, 36 | @required this.refreshMilliseconds, 37 | @required this.endFrame, //max frame to play 38 | this.texturePath, // "assets/asd.png" or sd card path 39 | this.backgroundColor = const Color(0xff353535), 40 | this.showInfo = false, 41 | this.showWireframe = false, 42 | this.wireframeColor = Colors.black, 43 | this.initialAngles, 44 | this.panDistanceToActivate = 10, //pan distance to activate swype 45 | this.onZoomChangeListener, 46 | this.onRotationChangeListener, 47 | this.onHorizontalDragUpdate, 48 | this.onVerticalDragUpdate, 49 | this.color = Colors.white, 50 | this.lightColor = Colors.white, 51 | this.showWireFrame = true, 52 | this.showGrids = true, 53 | this.gridsColor = const Color(0xff4b4b4b), 54 | this.gridsMaxTile = 10, 55 | this.gridsTileSize = 1.0, 56 | }); 57 | ``` 58 | 59 | ## Convert an FBX binary file to an FBX ASCII file that can this library handle 60 | 61 | 1, First step is to download an animated/rigged fbx binary file from the net. 62 | 63 | 2, Second is to load that modell with **AUTODESK MotionBuilder 2020** or **AUTODESK 3DS Max**! 64 | 65 | 3, 66 | **Python Tools -> FBX Export on the MotionBuilder** 67 | 68 | FBX Version: FBX 2014/2015 -> Export 69 | 70 | SAVE -> .fbx (ASCII) 71 | 72 | **Embed medias checked only** 73 | 74 | **Save options:** 75 | 76 | **Remove: (Settings)** 77 | 78 | Base Cameras 79 | 80 | Camera switchers 81 | 82 | Current camera 83 | 84 | Global Lighting 85 | 86 | Transport 87 | 88 | **Remove: (Scene)** 89 | 90 | Cameras (all) 91 | 92 | Textures (all) 93 | 94 | Video 95 | 96 | **TRIANGULATE** 97 | 98 | 4, SAVE 99 | 100 | Now if everything is went good in the fbx file header you can see this: **; FBX 7.4.0 project file** 101 | 102 | ## Limits 103 | 104 | **FBX is a closed format, so while this library does it's best to interpret the data in an FBX file, I cannot guarantee that it will read all FBX files, or all data within FBX files. You can play with FBX version 7.4 files ASCII text format** 105 | 106 | **Please don't use this library with a lot of vertices/polygons. Speed will be very low on huge point count.** 107 | 108 | Normal speed will be on an fbx that is **max 3000-5000 vertices** 109 | 110 | ## Author 111 | 112 | **Kozári László** in **2020.01.16** 113 | 114 | ## License 115 | 116 | Licensed under the Apache License, Version 2.0 (the "License") 117 | 118 | -------------------------------------------------------------------------------- /assets/chipmunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/chipmunk.png -------------------------------------------------------------------------------- /assets/knight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/knight.png -------------------------------------------------------------------------------- /assets/shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/shark.png -------------------------------------------------------------------------------- /assets/shark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/shark2.png -------------------------------------------------------------------------------- /assets/teddy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/teddy.png -------------------------------------------------------------------------------- /assets/turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/assets/turtle.png -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_fbx3d_viewer/fbx_viewer/flutter_fbx3d_viewer.dart'; 6 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/screen_utils.dart'; 7 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/utils.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:vector_math/vector_math.dart' as Math; 10 | 11 | class Example extends StatefulWidget { 12 | @override 13 | _ExampleState createState() => _ExampleState(); 14 | } 15 | 16 | class Object3DDetails { 17 | String animationPath; 18 | String animationTexturePath; 19 | Math.Vector3 rotation; 20 | double zoom; 21 | Color color, lightColor; 22 | double gridTileSize; 23 | int animationLength; 24 | double animationSpeed; 25 | 26 | Object3DDetails(this.animationPath, this.animationTexturePath, this.rotation, this.zoom, this.animationLength, this.animationSpeed, 27 | {this.color = Colors.black, this.lightColor = Colors.white, this.gridTileSize = 0.5}); 28 | } 29 | 30 | List _objects = [ 31 | Object3DDetails( 32 | "assets/turtle.fbx", 33 | "assets/turtle.png", 34 | Math.Vector3(216, 10, 230), 35 | 3, 36 | 25, 37 | 0.4, 38 | color: Colors.black.withOpacity(0.2), 39 | lightColor: Colors.white.withOpacity(0.7), 40 | gridTileSize: 15.0, 41 | ), 42 | Object3DDetails( 43 | "assets/teddy_walk.fbx", 44 | "assets/teddy.png", 45 | Math.Vector3(86, 10, 40), 46 | 140, 47 | 32, 48 | 1.0, 49 | color: Colors.black.withOpacity(0.7), 50 | lightColor: Colors.white.withOpacity(0.3), 51 | ), 52 | Object3DDetails( 53 | "assets/teddy_idle.fbx", 54 | "assets/teddy.png", 55 | Math.Vector3(86, 10, 40), 56 | 140, 57 | 110, 58 | 1.5, 59 | color: Colors.black.withOpacity(0.7), 60 | lightColor: Colors.white.withOpacity(0.3), 61 | ), 62 | Object3DDetails( 63 | "assets/knight.fbx", 64 | "assets/knight.png", 65 | Math.Vector3(260, 10, 0), 66 | 30, 67 | 16, 68 | 0.7, 69 | color: Colors.black.withOpacity(0.8), 70 | lightColor: Colors.white.withOpacity(0.2), 71 | gridTileSize: 2, 72 | ), 73 | Object3DDetails( 74 | "assets/chipmunk.fbx", 75 | "assets/chipmunk.png", 76 | Math.Vector3(86, 10, 40), 77 | 140, 78 | 32, 79 | 1.0, 80 | color: Colors.black.withOpacity(0.7), 81 | lightColor: Colors.white.withOpacity(0.3), 82 | ), 83 | Object3DDetails( 84 | "assets/shark.fbx", 85 | "assets/shark.png", 86 | Math.Vector3(86, 10, 40), 87 | 140, 88 | 32, 89 | 1.0, 90 | color: Colors.black.withOpacity(0.7), 91 | lightColor: Colors.white.withOpacity(0.3), 92 | ), 93 | ]; 94 | 95 | class ChangeVariants with ChangeNotifier { 96 | bool _showWireframe = false; 97 | int _objIndex = 0; 98 | double _lightAngle = 0.0; 99 | Math.Vector3 _lightPosition = Math.Vector3(20.0, 20.0, 10.0); 100 | bool _rndColor = false; 101 | 102 | bool get rndColor => _rndColor; 103 | 104 | int get objIndex => _objIndex; 105 | 106 | double get lightAngle => _lightAngle; 107 | 108 | Math.Vector3 get lightPosition => _lightPosition; 109 | 110 | bool get showWireframe => _showWireframe; 111 | 112 | set rndColor(bool value) { 113 | _rndColor = value; 114 | } 115 | 116 | set objIndex(int value) { 117 | _objIndex = value; 118 | notifyListeners(); 119 | } 120 | 121 | set showWireframe(bool value) { 122 | _showWireframe = value; 123 | notifyListeners(); 124 | } 125 | 126 | set lightAngle(double value) { 127 | _lightAngle = value; 128 | notifyListeners(); 129 | } 130 | 131 | set lightPosition(Math.Vector3 value) { 132 | _lightPosition = value; 133 | notifyListeners(); 134 | } 135 | } 136 | 137 | class _ExampleState extends State { 138 | Fbx3DViewerController _fbx3DAnimationController; 139 | Timer _renderTimer; 140 | ChangeVariants _changeVariantsSet; 141 | 142 | _ExampleState() { 143 | _fbx3DAnimationController = Fbx3DViewerController(); 144 | } 145 | 146 | @override 147 | void initState() { 148 | super.initState(); 149 | _init(); 150 | } 151 | 152 | _init() async { 153 | _changeVariantsSet = Provider.of(context, listen: false); 154 | _fbx3DAnimationController = Fbx3DViewerController(); 155 | _startTimer(); 156 | } 157 | 158 | _startTimer() { 159 | _renderTimer = Timer.periodic(const Duration(milliseconds: 50), (t) { 160 | final d = 10.0; 161 | _changeVariantsSet.lightAngle += 0.8; 162 | if (_changeVariantsSet.lightAngle > 360) _changeVariantsSet.lightAngle = 0; 163 | double fx = sin(Math.radians(_changeVariantsSet.lightAngle)) * d; 164 | double fz = cos(Math.radians(_changeVariantsSet.lightAngle)) * d; 165 | _changeVariantsSet.lightPosition.setValues(-fx, -fz, 0); 166 | _fbx3DAnimationController.setLightPosition(_changeVariantsSet.lightPosition); 167 | }); 168 | } 169 | 170 | _endTimer() => _renderTimer.cancel(); 171 | 172 | @override 173 | void dispose() { 174 | super.dispose(); 175 | _endTimer(); 176 | } 177 | 178 | _nextObj() async { 179 | _changeVariantsSet.objIndex++; 180 | if (_changeVariantsSet.objIndex >= _objects.length) _changeVariantsSet.objIndex = 0; 181 | _fbx3DAnimationController.refresh(); 182 | 183 | Future.delayed(Duration(milliseconds: 50), () { 184 | _fbx3DAnimationController.reload().then((_) { 185 | Future.delayed(Duration(milliseconds: 100), () { 186 | _fbx3DAnimationController.refresh(); 187 | }); 188 | }); 189 | }); 190 | } 191 | 192 | @override 193 | Widget build(BuildContext context) { 194 | ScreenUtils.init(context); 195 | 196 | final changeVariantsGet = Provider.of(context); 197 | final object = _objects[changeVariantsGet.objIndex]; 198 | 199 | return Scaffold( 200 | backgroundColor: const Color(0xff353535), 201 | body: SafeArea( 202 | child: Stack( 203 | children: [ 204 | Fbx3DViewer( 205 | lightPosition: Math.Vector3(20, 10, 10), 206 | lightColor: Colors.black.withOpacity(0.2), 207 | color: changeVariantsGet._rndColor ? randomColor(opacity: 0.8) : Colors.black.withOpacity(0.8), 208 | refreshMilliseconds: 1, 209 | size: Size(ScreenUtils.width, ScreenUtils.height), 210 | initialZoom: object.zoom, 211 | endFrame: object.animationLength, 212 | initialAngles: object.rotation, 213 | fbxPath: object.animationPath, 214 | texturePath: object.animationTexturePath, 215 | animationSpeed: object.animationSpeed, 216 | fbx3DViewerController: _fbx3DAnimationController, 217 | showInfo: true, 218 | showWireframe: changeVariantsGet._showWireframe, 219 | wireframeColor: changeVariantsGet._rndColor ? randomColor(opacity: 0.5) : Colors.blue.withOpacity(0.5), 220 | onHorizontalDragUpdate: (d) { 221 | if (object.animationPath.contains("turtle") || object.animationPath.contains("knight")) 222 | _fbx3DAnimationController.rotateZ(d); 223 | else 224 | _fbx3DAnimationController.rotateZ(-d); 225 | }, 226 | onVerticalDragUpdate: (d) => _fbx3DAnimationController.rotateX(d), 227 | onZoomChangeListener: (zoom) => object.zoom = zoom, 228 | onRotationChangeListener: (Math.Vector3 rotation) => object.rotation.setFrom(rotation), 229 | panDistanceToActivate: 50, 230 | gridsTileSize: object.gridTileSize, 231 | ), 232 | FlatButton( 233 | color: Colors.white, 234 | child: Text("Change model"), 235 | onPressed: () => _nextObj(), 236 | ), 237 | Align( 238 | child: SizedBox( 239 | height: 120, 240 | width: ScreenUtils.width / 2, 241 | child: Column( 242 | children: [ 243 | CheckboxListTile( 244 | title: Text("Wireframe"), 245 | value: changeVariantsGet._showWireframe, 246 | onChanged: (v) { 247 | _changeVariantsSet.showWireframe = v; 248 | _fbx3DAnimationController.showWireframe(_changeVariantsSet.showWireframe); 249 | }, 250 | controlAffinity: ListTileControlAffinity.leading, 251 | ), 252 | CheckboxListTile( 253 | title: Text("Rnd colors"), 254 | value: changeVariantsGet._rndColor, 255 | onChanged: (v) { 256 | _changeVariantsSet.rndColor = v; 257 | _fbx3DAnimationController.setRandomColors(randomColor(opacity: 0.7), randomColor(opacity: 0.3)); 258 | }, 259 | controlAffinity: ListTileControlAffinity.leading, 260 | ) 261 | ], 262 | )), 263 | alignment: Alignment.topRight, 264 | ) 265 | ], 266 | ))); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | library fbx; 3 | 4 | export 'fbx/fbx_ascii_parser.dart'; 5 | export 'fbx/fbx_binary_parser.dart'; 6 | export 'fbx/fbx_element.dart'; 7 | export 'fbx/fbx_loader.dart'; 8 | export 'fbx/fbx_parser.dart'; 9 | 10 | export 'fbx/scene/fbx_anim_curve.dart'; 11 | export 'fbx/scene/fbx_anim_curve_node.dart'; 12 | export 'fbx/scene/fbx_anim_evaluator.dart'; 13 | export 'fbx/scene/fbx_anim_key.dart'; 14 | export 'fbx/scene/fbx_anim_layer.dart'; 15 | export 'fbx/scene/fbx_anim_stack.dart'; 16 | export 'fbx/scene/fbx_camera.dart'; 17 | export 'fbx/scene/fbx_camera_switcher.dart'; 18 | export 'fbx/scene/fbx_cluster.dart'; 19 | export 'fbx/scene/fbx_deformer.dart'; 20 | export 'fbx/scene/fbx_display_mesh.dart'; 21 | export 'fbx/scene/fbx_edge.dart'; 22 | export 'fbx/scene/fbx_frame_rate.dart'; 23 | export 'fbx/scene/fbx_geometry.dart'; 24 | export 'fbx/scene/fbx_global_settings.dart'; 25 | export 'fbx/scene/fbx_layer.dart'; 26 | export 'fbx/scene/fbx_layer_element.dart'; 27 | export 'fbx/scene/fbx_light.dart'; 28 | export 'fbx/scene/fbx_mapping_mode.dart'; 29 | export 'fbx/scene/fbx_material.dart'; 30 | export 'fbx/scene/fbx_mesh.dart'; 31 | export 'fbx/scene/fbx_node.dart'; 32 | export 'fbx/scene/fbx_node_attribute.dart'; 33 | export 'fbx/scene/fbx_null.dart'; 34 | export 'fbx/scene/fbx_object.dart'; 35 | export 'fbx/scene/fbx_polygon.dart'; 36 | export 'fbx/scene/fbx_pose.dart'; 37 | export 'fbx/scene/fbx_property.dart'; 38 | export 'fbx/scene/fbx_reference_mode.dart'; 39 | export 'fbx/scene/fbx_scene.dart'; 40 | export 'fbx/scene/fbx_skeleton.dart'; 41 | export 'fbx/scene/fbx_skin_deformer.dart'; 42 | export 'fbx/scene/fbx_texture.dart'; 43 | export 'fbx/scene/fbx_video.dart'; 44 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/bit_operators.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'dart:typed_data'; 3 | 4 | int shiftR(int v, int n) { 5 | // dart2js can't handle binary operations on negative numbers, so 6 | // until that issue is fixed (issues 16506, 1533), we'll have to do it 7 | // the slow way. 8 | return (v / _SHIFT_BITS[n]).floor(); 9 | } 10 | 11 | int shiftL(int v, int n) { 12 | // dart2js can't handle binary operations on negative numbers, so 13 | // until that issue is fixed (issues 16506, 1533), we'll have to do it 14 | // the slow way. 15 | return (v * _SHIFT_BITS[n]); 16 | } 17 | 18 | const List _SHIFT_BITS = [ 19 | 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 20 | 32768, 65536]; 21 | 22 | 23 | // Binary conversion of a uint8 to an int8. This is equivalent in C to 24 | // typecasting an unsigned char to a char. 25 | int uint8ToInt8(int d) { 26 | __uint8[0] = d; 27 | return __uint8ToInt8[0]; 28 | } 29 | 30 | // Binary conversion of a uint16 to an int16. This is equivalent in C to 31 | // typecasting an unsigned short to a short. 32 | int uint16ToInt16(int d) { 33 | __uint16[0] = d; 34 | return __uint16ToInt16[0]; 35 | } 36 | 37 | // Binary conversion of a uint32 to an int32. This is equivalent in C to 38 | // typecasting an unsigned int to signed int. 39 | int uint32ToInt32(int d) { 40 | __uint32[0] = d; 41 | return __uint32ToInt32[0]; 42 | } 43 | 44 | // Binary conversion of a uint32 to an float32. This is equivalent in C to 45 | // typecasting an unsigned int to float. 46 | double uint32ToFloat32(int d) { 47 | __uint32[0] = d; 48 | return __uint32ToFloat32[0]; 49 | } 50 | 51 | // Binary conversion of an int32 to a uint32. This is equivalent in C to 52 | // typecasting an int to an unsigned int. 53 | int int32ToUint32(int d) { 54 | __int32[0] = d; 55 | return __int32ToUint32[0]; 56 | } 57 | 58 | // Binary conversion of an uint64 to a int64. This is equivalent in C to 59 | // typecasting an unsigned long long to long long. 60 | int uint64ToInt64(int d) { 61 | __uint64[0] = d; 62 | return __uint64ToInt64[0]; 63 | } 64 | 65 | // Binary conversion of a float32 to an uint32. This is equivalent in C to 66 | // typecasting a float to unsigned int. 67 | int float32ToUint32(double d) { 68 | __float32[0] = d; 69 | return __float32ToUint32[0]; 70 | } 71 | 72 | // Binary conversion of an uint64 to a float64. This is equivalent in C to 73 | // typecasting an unsigned long long to double. 74 | double uint64ToFloat64(int d) { 75 | __uint64[0] = d; 76 | return __uint64ToFloat64[0]; 77 | } 78 | 79 | final __uint8 = Uint8List(1); 80 | final __uint8ToInt8 = Int8List.view(__uint8.buffer); 81 | 82 | final __uint16 = Uint16List(1); 83 | final __uint16ToInt16 = Int16List.view(__uint16.buffer); 84 | 85 | final __uint32 = Uint32List(1); 86 | final __uint32ToInt32 = Int32List.view(__uint32.buffer); 87 | final __uint32ToFloat32 = Float32List.view(__uint32.buffer); 88 | 89 | final __int32 = Int32List(1); 90 | final __int32ToUint32 = Uint32List.view(__int32.buffer); 91 | 92 | final __uint64 = Uint64List(1); 93 | final __uint64ToInt64 = Int64List.view(__uint64.buffer); 94 | 95 | final __float32 = Float32List(1); 96 | final __float32ToUint32 = Uint32List.view(__float32.buffer); 97 | 98 | final __uint64ToFloat64 = Float64List.view(__uint64.buffer); 99 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/fbx_ascii_parser.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_parser.dart'; 3 | import 'fbx_element.dart'; 4 | import 'input_buffer.dart'; 5 | 6 | /// Decodes an ASCII FBX file. 7 | class FbxAsciiParser extends FbxParser { 8 | static const FILE_HEADER = '; FBX'; 9 | 10 | InputBuffer _input; 11 | 12 | static bool isValidFile(InputBuffer input) { 13 | final fp = input.offset; 14 | final header = input.readString(FILE_HEADER.length); 15 | input.offset = fp; 16 | 17 | if (header != FILE_HEADER) { 18 | return false; 19 | } 20 | 21 | return true; 22 | } 23 | 24 | FbxAsciiParser(InputBuffer input) { 25 | final fp = input.offset; 26 | final header = input.readString(FILE_HEADER.length); 27 | input.offset = fp; 28 | 29 | if (header != FILE_HEADER) { 30 | return; 31 | } 32 | 33 | _input = input; 34 | } 35 | 36 | @override 37 | FbxElement nextElement() { 38 | if (_input == null) { 39 | return null; 40 | } 41 | 42 | var tk = _nextToken(_input); 43 | if (tk == '}') { 44 | return null; 45 | } 46 | 47 | if (_nextToken(_input) != ':') { 48 | return null; 49 | } 50 | 51 | final elem = FbxElement(tk); 52 | 53 | final sp = _input.offset; 54 | tk = _nextToken(_input); 55 | 56 | // If the next token is a node definition (nodeType:*), then back up 57 | // and save it for the next node. 58 | final tk2 = _nextToken(_input, peek: true); 59 | if (tk2 == ':') { 60 | _input.offset = sp; 61 | return elem; 62 | } 63 | 64 | if (tk != '{') { 65 | while (!_input.isEOS) { 66 | elem.properties.add(tk); 67 | tk = _nextToken(_input, peek: true); 68 | if (tk == ',' || tk == '{') { 69 | _nextToken(_input); // consume the ,{ token 70 | if (tk == '{') { 71 | break; 72 | } 73 | tk = _nextToken(_input); 74 | } else { 75 | break; 76 | } 77 | } 78 | } 79 | 80 | if (tk == '{') { 81 | var n = nextElement(); 82 | while (n != null) { 83 | elem.children.add(n); 84 | n = nextElement(); 85 | } 86 | } 87 | 88 | return elem; 89 | } 90 | 91 | @override 92 | String sceneName() => 'Model::Scene'; 93 | 94 | @override 95 | String getName(String rawName) => rawName.split('::').last; 96 | 97 | String _nextToken(InputBuffer input, {bool peek = false}) { 98 | _skipWhitespace(input); 99 | 100 | if (input.isEOS) { 101 | return null; 102 | } 103 | 104 | final sp = input.offset; 105 | var c = input.readByte(); 106 | 107 | if (c == TK_QUOTE) { 108 | final s = _readString(input); 109 | if (peek) { 110 | input.offset = sp; 111 | } 112 | return s; 113 | } 114 | 115 | if (c == TK_COMMA || c == TK_LBRACE || c == TK_RBRACE || c == TK_COLON) { 116 | final s = String.fromCharCode(c); 117 | if (peek) { 118 | input.offset = sp; 119 | } 120 | return s; 121 | } 122 | 123 | while (!input.isEOS) { 124 | c = input.peekBytes(1)[0]; 125 | if (!_isAlphaNumeric(c)) { 126 | break; 127 | } 128 | input.skip(1); 129 | } 130 | 131 | final ep = input.offset; 132 | input.offset = sp; 133 | 134 | final token = input.readString(ep - sp); 135 | if (peek) { 136 | input.offset = sp; 137 | } 138 | 139 | //if (token.length > 5 && token.toLowerCase().startsWith("c")) logger("------ token: " + token); 140 | 141 | return token; 142 | } 143 | 144 | String _readString(InputBuffer input) { 145 | final sp = input.offset; 146 | while (!input.isEOS) { 147 | final c = input.readByte(); 148 | if (c == TK_QUOTE) { 149 | break; 150 | } 151 | } 152 | final ep = input.offset; 153 | input.offset = sp; 154 | final string = input.readString(ep - sp - 1); // don't include ending " 155 | input.skip(1); // skip ending " 156 | return string; 157 | } 158 | 159 | bool _isAlphaNumeric(int c) { 160 | return (c >= TK_a && c <= TK_z) || 161 | (c >= TK_A && c <= TK_Z) || 162 | (c >= TK_0 && c <= TK_9) || 163 | (c == TK_DOT) || 164 | (c == TK_PLUS) || 165 | (c == TK_MINUS) || 166 | (c == TK_ASTERISK) || 167 | (c == TK_BAR) || 168 | (c == TK_UNDERSCORE); 169 | } 170 | 171 | void _skipWhitespace(InputBuffer input) { 172 | while (!input.isEOS) { 173 | final c = input.peekBytes(1)[0]; 174 | 175 | if (c == TK_SPACE || c == TK_TAB || c == TK_RL || c == TK_NL) { 176 | input.skip(1); 177 | continue; 178 | } 179 | 180 | // skip comments 181 | if (c == TK_SEMICOLON) { 182 | while (!input.isEOS) { 183 | final c2 = input.readByte(); 184 | if (c2 == TK_NL) { 185 | break; 186 | } 187 | } 188 | continue; 189 | } 190 | 191 | break; 192 | } 193 | } 194 | 195 | final int TK_LBRACE = '{'.codeUnits[0]; 196 | final int TK_RBRACE = '}'.codeUnits[0]; 197 | final int TK_SEMICOLON = ';'.codeUnits[0]; 198 | final int TK_COLON = ':'.codeUnits[0]; 199 | final int TK_RL = '\r'.codeUnits[0]; 200 | final int TK_NL = '\n'.codeUnits[0]; 201 | final int TK_QUOTE = '"'.codeUnits[0]; 202 | final int TK_SPACE = ' '.codeUnits[0]; 203 | final int TK_TAB = '\t'.codeUnits[0]; 204 | final int TK_COMMA = ','.codeUnits[0]; 205 | final int TK_DOT = '.'.codeUnits[0]; 206 | final int TK_ASTERISK = '*'.codeUnits[0]; 207 | final int TK_MINUS = '-'.codeUnits[0]; 208 | final int TK_PLUS = '+'.codeUnits[0]; 209 | final int TK_BAR = '|'.codeUnits[0]; 210 | final int TK_UNDERSCORE = '_'.codeUnits[0]; 211 | final int TK_a = 'a'.codeUnits[0]; 212 | final int TK_e = 'e'.codeUnits[0]; 213 | final int TK_z = 'z'.codeUnits[0]; 214 | final int TK_A = 'A'.codeUnits[0]; 215 | final int TK_E = 'E'.codeUnits[0]; 216 | final int TK_Z = 'Z'.codeUnits[0]; 217 | final int TK_0 = '0'.codeUnits[0]; 218 | final int TK_9 = '9'.codeUnits[0]; 219 | } 220 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/fbx_binary_parser.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_element.dart'; 3 | import 'fbx_parser.dart'; 4 | import 'input_buffer.dart'; 5 | import 'dart:typed_data'; 6 | import 'package:archive/archive.dart'; 7 | 8 | /// Decodes a binary FBX file. 9 | class FbxBinaryParser extends FbxParser { 10 | static const FILE_HEADER = 'Kaydara FBX Binary \x00'; 11 | 12 | static const int TYPE_BOOL = 67; // 'C' 13 | static const int TYPE_BYTE = 66; // 'B' 14 | static const int TYPE_INT16 = 89; // 'Y' 15 | static const int TYPE_INT32 = 73; // 'I' 16 | static const int TYPE_INT64 = 76; // 'L' 17 | static const int TYPE_FLOAT32 = 70; // 'F' 18 | static const int TYPE_FLOAT64 = 68; // 'D' 19 | static const int TYPE_ARRAY_BOOL = 99; // 'c' 20 | static const int TYPE_ARRAY_BYTE = 98; // 'b' 21 | static const int TYPE_ARRAY_INT16 = 121; // 'y' 22 | static const int TYPE_ARRAY_INT32 = 105; // 'i' 23 | static const int TYPE_ARRAY_INT64 = 108; // 'l' 24 | static const int TYPE_ARRAY_FLOAT32 = 102; // 'f' 25 | static const int TYPE_ARRAY_FLOAT64 = 100; // 'd' 26 | static const int TYPE_BYTES = 82; // 'R' 27 | static const int TYPE_STRING = 83; // 'S' 28 | static const String NAME_SEP = '\x00\x01'; 29 | 30 | InputBuffer _input; 31 | 32 | static bool isValidFile(InputBuffer input) { 33 | final fp = input.offset; 34 | final header = input.readString(FILE_HEADER.length); 35 | input.offset = fp; 36 | 37 | if (header != FILE_HEADER) { 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | FbxBinaryParser(InputBuffer input) { 45 | final fp = input.offset; 46 | final header = input.readString(FILE_HEADER.length); 47 | 48 | if (header != FILE_HEADER) { 49 | input.offset = fp; 50 | return; 51 | } 52 | 53 | _input = input; 54 | 55 | _input.skip(2); // \x1a\x00, not sure 56 | _input.skip(4); // file version 57 | } 58 | 59 | @override 60 | FbxElement nextElement() { 61 | if (_input == null) { 62 | return null; 63 | } 64 | 65 | final endOffset = _input.readUint32(); 66 | final propCount = _input.readUint32(); 67 | /*final propLength =*/ _input.readUint32(); 68 | 69 | if (endOffset == 0) { 70 | return null; 71 | } 72 | 73 | var elemId = _input.readString(_input.readByte()); 74 | 75 | final elem = FbxElement(elemId, propCount); 76 | 77 | for (var i = 0; i < propCount; ++i) { 78 | final s = _input.readByte(); 79 | elem.properties[i] = _readData(_input, s); 80 | } 81 | 82 | const _BLOCK_SENTINEL_LENGTH = 13; 83 | 84 | if (_input.position < endOffset) { 85 | while (_input.position < (endOffset - _BLOCK_SENTINEL_LENGTH)) { 86 | elem.children.add(nextElement()); 87 | } 88 | 89 | // Should be [0]*_BLOCK_SENTINEL_LENGTH 90 | _input.skip(_BLOCK_SENTINEL_LENGTH); 91 | } 92 | 93 | if (_input.position != endOffset) { 94 | throw Exception('scope length not reached, something is wrong'); 95 | } 96 | 97 | return elem; 98 | } 99 | 100 | @override 101 | String sceneName() => 'Scene${NAME_SEP}Model'; 102 | 103 | @override 104 | String getName(String rawName) => 105 | rawName.substring(0, rawName.codeUnits.indexOf(0)); 106 | 107 | dynamic _readData(InputBuffer input, int s) { 108 | switch (s) { 109 | case TYPE_BOOL: 110 | return input.readByte() != 0; 111 | case TYPE_BYTE: 112 | return input.readByte(); 113 | case TYPE_INT16: 114 | return input.readInt16(); 115 | case TYPE_INT32: 116 | return input.readInt32(); 117 | case TYPE_INT64: 118 | return input.readInt64(); 119 | case TYPE_FLOAT32: 120 | return input.readFloat32(); 121 | case TYPE_FLOAT64: 122 | return input.readFloat64(); 123 | case TYPE_BYTES: 124 | return input.readBytes(input.readUint32()).toUint8List(); 125 | case TYPE_STRING: 126 | var st = input.readString(input.readUint32()); 127 | return st; 128 | case TYPE_ARRAY_FLOAT32: 129 | return _readArray(input, s, 4); 130 | case TYPE_ARRAY_FLOAT64: 131 | return _readArray(input, s, 8); 132 | case TYPE_ARRAY_INT32: 133 | return _readArray(input, s, 4); 134 | case TYPE_ARRAY_INT64: 135 | return _readArray(input, s, 8); 136 | case TYPE_ARRAY_BYTE: 137 | return _readArray(input, s, 1); 138 | case TYPE_ARRAY_BOOL: 139 | return _readArray(input, s, 1); 140 | } 141 | return null; 142 | } 143 | 144 | dynamic _readArray(InputBuffer input, int s, int arrayStride) { 145 | const UNCOMPRESSED = 0; 146 | const ZLIB_COMPRESSED = 1; 147 | 148 | final length = input.readUint32(); 149 | final encoding = input.readUint32(); 150 | final compressedLength = input.readUint32(); 151 | 152 | var bytes = input.readBytes(compressedLength); 153 | 154 | Uint8List data; 155 | if (encoding == ZLIB_COMPRESSED) { 156 | data = ZLibDecoder().decodeBytes(bytes.toUint8List()) as Uint8List; 157 | } else if (encoding == UNCOMPRESSED) { 158 | data = bytes.toUint8List(); 159 | } else { 160 | throw Exception('Invalid Array Encoding $encoding'); 161 | } 162 | 163 | if (length * arrayStride != data.length) { 164 | throw Exception('Invalid Array Data'); 165 | } 166 | 167 | switch (s) { 168 | case TYPE_ARRAY_BYTE: 169 | return data; 170 | case TYPE_ARRAY_BOOL: 171 | return data; 172 | case TYPE_ARRAY_INT16: 173 | return data.buffer.asInt64List(0, length).toList(growable: false); 174 | case TYPE_ARRAY_INT32: 175 | return data.buffer.asInt32List(0, length).toList(growable: false); 176 | case TYPE_ARRAY_INT64: 177 | return data.buffer.asInt64List(0, length).toList(growable: false); 178 | case TYPE_ARRAY_FLOAT32: 179 | return data.buffer.asFloat32List(0, length).toList(growable: false); 180 | case TYPE_ARRAY_FLOAT64: 181 | var da = data.buffer.asFloat64List(0, length).toList(growable: false); 182 | return da; 183 | } 184 | 185 | return null; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/fbx_element.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Brendan Duncan. All rights reserved. 3 | */ 4 | 5 | class FbxElement { 6 | String id; 7 | List properties; 8 | List children = []; 9 | 10 | FbxElement(this.id, [int propertyCount]) { 11 | if (propertyCount != null) { 12 | properties = List(propertyCount); 13 | } else { 14 | properties = []; 15 | } 16 | } 17 | 18 | String getString(int index) => properties[index].toString(); 19 | 20 | int getInt(int index) => toInt(properties[index]); 21 | 22 | double getDouble(int index) => toDouble(properties[index]); 23 | 24 | double toDouble(dynamic x) => 25 | x is String ? double.parse(x) : 26 | x is bool ? (x ? 1.0 : 0.0) : 27 | (x as num).toDouble(); 28 | 29 | int toInt(dynamic x) => 30 | x is String ? int.parse(x) : 31 | x is bool ? (x ? 1 : 0) : 32 | (x as num).toInt(); 33 | } 34 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/fbx_loader.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'input_buffer.dart'; 3 | import 'fbx_element.dart'; 4 | import 'fbx_ascii_parser.dart'; 5 | import 'fbx_binary_parser.dart'; 6 | import 'fbx_parser.dart'; 7 | import 'scene/fbx_anim_curve.dart'; 8 | import 'scene/fbx_anim_curve_node.dart'; 9 | import 'scene/fbx_anim_layer.dart'; 10 | import 'scene/fbx_anim_stack.dart'; 11 | import 'scene/fbx_anim_key.dart'; 12 | import 'scene/fbx_camera.dart'; 13 | import 'scene/fbx_cluster.dart'; 14 | import 'scene/fbx_deformer.dart'; 15 | import 'scene/fbx_light.dart'; 16 | import 'scene/fbx_material.dart'; 17 | import 'scene/fbx_mesh.dart'; 18 | import 'scene/fbx_node.dart'; 19 | import 'scene/fbx_node_attribute.dart'; 20 | import 'scene/fbx_object.dart'; 21 | import 'scene/fbx_pose.dart'; 22 | import 'scene/fbx_scene.dart'; 23 | import 'scene/fbx_skeleton.dart'; 24 | import 'scene/fbx_skin_deformer.dart'; 25 | import 'scene/fbx_texture.dart'; 26 | import 'scene/fbx_video.dart'; 27 | import 'scene/fbx_global_settings.dart'; 28 | 29 | /// Decodes an FBX file into an [FbxScene] structure. 30 | class FbxLoader { 31 | FbxScene load(List bytes) { 32 | final input = InputBuffer(bytes); 33 | 34 | if (FbxBinaryParser.isValidFile(input)) { 35 | _parser = FbxBinaryParser(input); 36 | } else if (FbxAsciiParser.isValidFile(input)) { 37 | _parser = FbxAsciiParser(input); 38 | } else { 39 | return null; 40 | } 41 | 42 | final scene = FbxScene(); 43 | 44 | var elem = _parser.nextElement(); 45 | while (elem != null) { 46 | _loadRootElement(elem, scene); 47 | elem = _parser.nextElement(); 48 | } 49 | 50 | _parser = null; 51 | 52 | return scene; 53 | } 54 | 55 | void _loadRootElement(FbxElement e, FbxScene scene) { 56 | //logger("-----PARSER _loadRootElement " + e.id); 57 | 58 | if (e.id == 'FBXHeaderExtension') { 59 | _loadHeaderExtension(e, scene); 60 | } else if (e.id == 'GlobalSettings') { 61 | final node = FbxGlobalSettings(e, scene); 62 | scene.globalSettings = node; 63 | } else if (e.id == 'Objects') { 64 | _loadObjects(e, scene); 65 | } else if (e.id == 'Connections') { 66 | _loadConnections(e, scene); 67 | _fixConnections(scene); 68 | } else if (e.id == 'Takes') { 69 | _loadTakes(e, scene); 70 | } else { 71 | //print('Unhandled Element ${e.id}'); 72 | } 73 | } 74 | 75 | void _loadTakes(FbxElement e, FbxScene scene) { 76 | String currentTake; 77 | for (final c in e.children) { 78 | if (c.id == 'Current') { 79 | currentTake = c.properties[0] as String; 80 | } else if(c.id == 'Take') { 81 | // TODO store multiple takes 82 | if (c.properties[0] != currentTake) { 83 | continue; 84 | } 85 | 86 | _loadTake(c, scene); 87 | } 88 | } 89 | } 90 | 91 | // Older FBX versions store animation in 'Takes' 92 | void _loadTake(FbxElement e, FbxScene scene) { 93 | for (final c in e.children) { 94 | if (c.id == 'Model') { 95 | final name = c.properties[0] as String; 96 | 97 | final obj = scene.allObjects[name]; 98 | if (obj == null) { 99 | print('Could not find object $name'); 100 | continue; 101 | } 102 | 103 | for (final c2 in c.children) { 104 | if (c2.id == 'Channel') { 105 | _loadTakeChannel(c2, obj, scene); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | void _loadTakeChannel(FbxElement c, FbxObject obj, FbxScene scene) { 113 | if (c.properties[0] == 'Transform') { 114 | for (final c2 in c.children) { 115 | if (c2.properties[0] == 'T') { 116 | final animNode = FbxAnimCurveNode(0, 'T', null, scene); 117 | obj.connectToProperty('Lcl Translation', animNode); 118 | for (final c3 in c2.children) { 119 | if (c3.id == 'Channel' && c3.properties[0] == 'X') { 120 | final animCurve = FbxAnimCurve(0, 'X', null, scene); 121 | animNode.connectToProperty('X', animCurve); 122 | _loadTakeCurve(c3, animCurve); 123 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Y') { 124 | final animCurve = FbxAnimCurve(0, 'Y', null, scene); 125 | animNode.connectToProperty('Y', animCurve); 126 | _loadTakeCurve(c3, animCurve); 127 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Z') { 128 | final animCurve = FbxAnimCurve(0, 'Z', null, scene); 129 | animNode.connectToProperty('Z', animCurve); 130 | _loadTakeCurve(c3, animCurve); 131 | } 132 | } 133 | } else if (c2.properties[0] == 'R') { 134 | final animNode = FbxAnimCurveNode(0, 'R', null, scene); 135 | obj.connectToProperty('Lcl Rotation', animNode); 136 | for (final c3 in c2.children) { 137 | if (c3.id == 'Channel' && c3.properties[0] == 'X') { 138 | final animCurve = FbxAnimCurve(0, 'X', null, scene); 139 | animNode.connectToProperty('X', animCurve); 140 | _loadTakeCurve(c3, animCurve); 141 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Y') { 142 | final animCurve = FbxAnimCurve(0, 'Y', null, scene); 143 | animNode.connectToProperty('Y', animCurve); 144 | _loadTakeCurve(c3, animCurve); 145 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Z') { 146 | final animCurve = FbxAnimCurve(0, 'Z', null, scene); 147 | animNode.connectToProperty('Z', animCurve); 148 | _loadTakeCurve(c3, animCurve); 149 | } 150 | } 151 | } else if (c2.properties[0] == 'S') { 152 | final animNode = FbxAnimCurveNode(0, 'S', null, scene); 153 | obj.connectToProperty('Lcl Scaling', animNode); 154 | for (final c3 in c2.children) { 155 | if (c3.id == 'Channel' && c3.properties[0] == 'X') { 156 | final animCurve = FbxAnimCurve(0, 'X', null, scene); 157 | animNode.connectToProperty('X', animCurve); 158 | _loadTakeCurve(c3, animCurve); 159 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Y') { 160 | final animCurve = FbxAnimCurve(0, 'Y', null, scene); 161 | animNode.connectToProperty('Y', animCurve); 162 | _loadTakeCurve(c3, animCurve); 163 | } else if (c3.id == 'Channel' && c3.properties[0] == 'Z') { 164 | final animCurve = FbxAnimCurve(0, 'Z', null, scene); 165 | animNode.connectToProperty('Z', animCurve); 166 | _loadTakeCurve(c3, animCurve); 167 | } 168 | } 169 | } 170 | } 171 | } else if (c.properties[0] == 'Visibility') { 172 | final animNode = FbxAnimCurveNode(0, 'Visibility', null, scene); 173 | obj.connectToProperty('Visibility', animNode); 174 | 175 | final animCurve = FbxAnimCurve(0, 'Visibility', null, scene); 176 | _loadTakeCurve(c, animCurve); 177 | } 178 | } 179 | 180 | void _loadTakeCurve(FbxElement e, FbxAnimCurve animCurve) { 181 | for (final c in e.children) { 182 | if (c.id == 'Default') { 183 | animCurve.defaultValue = c.getDouble(0); 184 | } else if (c.id == 'Key') { 185 | for (var pi = 0; pi < c.properties.length;) { 186 | final time = c.getInt(pi); 187 | final value = c.getDouble(pi + 1); 188 | 189 | animCurve.keys.add(FbxAnimKey(time, value, 190 | FbxAnimKey.INTERPOLATION_LINEAR)); 191 | 192 | if ((pi + 2) >= c.properties.length) { 193 | break; 194 | } 195 | 196 | final type = c.properties[pi + 2].toString(); 197 | var keyType = FbxAnimKey.INTERPOLATION_LINEAR; 198 | 199 | if (type == 'C') { 200 | keyType = FbxAnimKey.INTERPOLATION_CONSTANT; 201 | pi += 4; 202 | } else if (type == 'L') { 203 | keyType = FbxAnimKey.INTERPOLATION_LINEAR; 204 | pi += 3; 205 | } else if (type == 'true') { 206 | keyType = FbxAnimKey.INTERPOLATION_CUBIC; 207 | pi += 5; 208 | } else { 209 | keyType = FbxAnimKey.INTERPOLATION_CUBIC; 210 | pi += 7; 211 | } 212 | 213 | animCurve.keys.add(FbxAnimKey(time, value, keyType)); 214 | } 215 | } 216 | } 217 | } 218 | 219 | /// Older versions of fbx connect deformers to the transform instead of 220 | /// the mesh. 221 | void _fixConnections(FbxScene scene) { 222 | for (final mesh in scene.meshes) { 223 | if (mesh.connectedFrom.isEmpty) { 224 | continue; 225 | } 226 | for (final cf in mesh.connectedFrom) { 227 | if (cf is FbxNode) { 228 | for (final df in cf.connectedTo) { 229 | if (df is FbxDeformer) { 230 | if (!mesh.connectedTo.contains(df)) { 231 | mesh.connectTo(df); 232 | } 233 | } 234 | } 235 | } 236 | } 237 | } 238 | } 239 | 240 | void _loadConnections(FbxElement e, FbxScene scene) { 241 | final SCENE = _parser.sceneName(); 242 | 243 | for (final c in e.children) { 244 | if (c.id == 'C' || c.id == 'Connect') { 245 | final type = c.properties[0] as String; 246 | 247 | if (type == 'OO') { 248 | final src = c.properties[1].toString(); 249 | final dst = c.properties[2].toString(); 250 | 251 | final srcModel = scene.allObjects[src]; 252 | if (srcModel == null) { 253 | print('COULD NOT FIND SRC NODE 1 : $src'); 254 | continue; 255 | } 256 | 257 | if (dst == '0' || dst == SCENE) { 258 | scene.rootNodes.add(srcModel as FbxNode); 259 | } else { 260 | final dstModel = scene.allObjects[dst]; 261 | if (dstModel != null) { 262 | dstModel.connectTo(srcModel); 263 | } else { 264 | print('COULD NOT FIND NODE 1 : $dst'); 265 | } 266 | } 267 | } else if (type == 'OP') { 268 | final src = c.properties[1].toString(); 269 | final dst = c.properties[2].toString(); 270 | var attr = c.properties[3] as String; 271 | 272 | final srcModel = scene.allObjects[src]; 273 | if (srcModel == null) { 274 | print('COULD NOT FIND SRC NODE 2 : $src $attr'); 275 | continue; 276 | } 277 | 278 | final dstModel = scene.allObjects[dst]; 279 | if (dstModel == null) { 280 | print('COULD NOT FIND NODE 2 : $dst '); 281 | continue; 282 | } 283 | else { 284 | attr = attr 285 | .split('|') 286 | .last; 287 | dstModel.connectToProperty(attr, srcModel); 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | void _loadObjects(FbxElement e, FbxScene scene) { 295 | //logger("-----PARSER _loadObjects " + e.id + " " + e.children.length.toString()); 296 | 297 | for (final c in e.children) { 298 | //logger("-----PARSER _loadObjects children " + c.id); 299 | 300 | if (c.id == 'Model') { 301 | int id; 302 | String rawName; 303 | String type; 304 | 305 | if (c.properties.length == 3) { 306 | id = c.getInt(0); 307 | rawName = c.properties[1] as String; 308 | type = c.properties[2] as String; 309 | } else { 310 | id = 0; 311 | rawName = c.properties[0] as String; 312 | type = c.properties[1] as String; 313 | } 314 | 315 | //logger("-----PARSER prop len " + c.properties.length.toString()); 316 | 317 | var name = _parser.getName(rawName); 318 | 319 | FbxObject node; 320 | 321 | if (type == 'Camera') { 322 | final camera = FbxCamera(id, name, c, scene); 323 | node = camera; 324 | scene.allObjects[rawName] = camera; 325 | scene.allObjects[name] = camera; 326 | scene.cameras.add(camera); 327 | } else if (type == 'Light') { 328 | final light = FbxLight(id, name, c, scene); 329 | node = light; 330 | scene.allObjects[rawName] = light; 331 | scene.allObjects[name] = light; 332 | scene.lights.add(light); 333 | } else if (type == 'Mesh') { 334 | // In older versions of Fbx, the mesh shape was combined with the 335 | // meshNode, rather than being a separate NodeAttribute; so we'll 336 | // split the nodes in that case. 337 | 338 | //logger("-----PARSER Mesh "); 339 | 340 | if (id == 0) { 341 | final meshNode = FbxNode(id, name, 'Transform', c, scene); 342 | scene.allObjects[rawName] = meshNode; 343 | scene.allObjects[name] = meshNode; 344 | 345 | final mesh = FbxMesh(id, c, scene); 346 | node = mesh; 347 | scene.meshes.add(mesh); 348 | meshNode.connectTo(mesh); 349 | } else { 350 | node = FbxNode(id, name, 'Transform', c, scene); 351 | scene.allObjects[rawName] = node; 352 | scene.allObjects[name] = node; 353 | } 354 | } else if (type == 'Limb' || type == 'LimbNode') { 355 | final limb = FbxSkeleton(id, name, type, c, scene); 356 | node = limb; 357 | scene.allObjects[rawName] = limb; 358 | scene.allObjects[name] = limb; 359 | scene.skeletonNodes.add(limb); 360 | } else { 361 | var tk = name.split(':'); 362 | if (tk.length > 1) { 363 | name = tk[1]; 364 | 365 | node = FbxNode(id, name, type, c, scene); 366 | scene.allObjects[rawName] = node; 367 | scene.allObjects[name] = node; 368 | 369 | node.reference = tk[0]; 370 | } else { 371 | node = FbxNode(id, name, type, c, scene); 372 | scene.allObjects[rawName] = node; 373 | scene.allObjects[name] = node; 374 | } 375 | } 376 | 377 | if (id != 0) { 378 | scene.allObjects[id.toString()] = node; 379 | } 380 | } else if (c.id == 'Geometry') { 381 | final id = c.getInt(0); 382 | final type = c.properties[2] as String; 383 | 384 | if (type == 'Mesh' || type == 'Shape') { 385 | final mesh = FbxMesh(id, c, scene); 386 | 387 | if (id != 0) { 388 | scene.allObjects[id.toString()] = mesh; 389 | } 390 | scene.meshes.add(mesh); 391 | } 392 | } else if (c.id == 'Material') { 393 | int id; 394 | String rawName; 395 | 396 | if (c.properties.length == 3) { 397 | id = c.getInt(0); 398 | rawName = c.properties[1] as String; 399 | } else { 400 | id = 0; 401 | rawName = c.properties[0] as String; 402 | } 403 | 404 | final name = _parser.getName(rawName); 405 | 406 | final material = FbxMaterial(id, name, c, scene); 407 | scene.allObjects[rawName] = material; 408 | scene.allObjects[name] = material; 409 | if (id != 0) { 410 | scene.allObjects[id.toString()] = material; 411 | } 412 | scene.materials.add(material); 413 | } else if (c.id == 'AnimationStack') { 414 | int id; 415 | String rawName; 416 | 417 | if (c.properties.length == 3) { 418 | id = c.getInt(0); 419 | rawName = c.properties[1] as String; 420 | } else { 421 | id = 0; 422 | rawName = c.properties[0] as String; 423 | } 424 | 425 | final name = _parser.getName(rawName); 426 | 427 | final stack = FbxAnimStack(id, name, c, scene); 428 | if (id != 0) { 429 | scene.allObjects[id.toString()] = stack; 430 | } 431 | scene.allObjects[rawName] = stack; 432 | scene.allObjects[name] = stack; 433 | scene.animationStack.add(stack); 434 | } else if (c.id == 'AnimationLayer') { 435 | int id; 436 | String rawName; 437 | 438 | if (c.properties.length == 3) { 439 | id = c.getInt(0); 440 | rawName = c.properties[1] as String; 441 | } else { 442 | id = 0; 443 | rawName = c.properties[0] as String; 444 | } 445 | 446 | final name = _parser.getName(rawName); 447 | 448 | final layer = FbxAnimLayer(id, name, c, scene); 449 | if (id != 0) { 450 | scene.allObjects[id.toString()] = layer; 451 | } 452 | scene.allObjects[rawName] = layer; 453 | scene.allObjects[name] = layer; 454 | } else if (c.id == 'AnimationCurveNode') { 455 | int id; 456 | String rawName; 457 | //String type; 458 | 459 | if (c.properties.length == 3) { 460 | id = c.getInt(0); 461 | rawName = c.properties[1] as String; 462 | //type = c.properties[2]; 463 | } else { 464 | id = 0; 465 | rawName = c.properties[0] as String; 466 | //type = c.properties[1]; 467 | } 468 | 469 | final name = _parser.getName(rawName); 470 | 471 | final curve = FbxAnimCurveNode(id, name, c, scene); 472 | if (id != 0) { 473 | scene.allObjects[id.toString()] = curve; 474 | } 475 | scene.allObjects[rawName] = curve; 476 | scene.allObjects[name] = curve; 477 | } else if (c.id == 'Deformer') { 478 | int id; 479 | String rawName; 480 | String type; 481 | 482 | if (c.properties.length == 3) { 483 | id = c.getInt(0); 484 | rawName = c.properties[1] as String; 485 | type = c.properties[2] as String; 486 | } else { 487 | id = 0; 488 | rawName = c.properties[0] as String; 489 | type = c.properties[1] as String; 490 | } 491 | 492 | final name = _parser.getName(rawName); 493 | 494 | if (type == 'Skin') { 495 | final skin = FbxSkinDeformer(id, name, c, scene); 496 | scene.deformers.add(skin); 497 | scene.allObjects[rawName] = skin; 498 | scene.allObjects[name] = skin; 499 | if (id != 0) { 500 | scene.allObjects[id.toString()] = skin; 501 | } 502 | } else if (type == 'Cluster') { 503 | final cluster = FbxCluster(id, name, c, scene); 504 | scene.deformers.add(cluster); 505 | scene.allObjects[rawName] = cluster; 506 | scene.allObjects[name] = cluster; 507 | 508 | if (id != 0) { 509 | scene.allObjects[id.toString()] = cluster; 510 | } 511 | } 512 | } else if (c.id == 'Texture') { 513 | int id; 514 | String rawName; 515 | 516 | if (c.properties.length == 3) { 517 | id = c.getInt(0); 518 | rawName = c.properties[1] as String; 519 | } else { 520 | id = 0; 521 | rawName = c.properties[0] as String; 522 | } 523 | 524 | final name = _parser.getName(rawName); 525 | 526 | final texture = FbxTexture(id, name, c, scene); 527 | 528 | scene.textures.add(texture); 529 | scene.allObjects[rawName] = texture; 530 | scene.allObjects[name] = texture; 531 | if (id != 0) { 532 | scene.allObjects[id.toString()] = texture; 533 | } 534 | } else if (c.id == 'Folder') { 535 | int id; 536 | String rawName; 537 | 538 | if (c.properties.length == 3) { 539 | id = c.getInt(0); 540 | rawName = c.properties[1] as String; 541 | } else { 542 | id = 0; 543 | rawName = c.properties[0] as String; 544 | } 545 | 546 | final name = _parser.getName(rawName); 547 | 548 | final folder = FbxObject(id, name, c.id, c, scene); 549 | scene.allObjects[rawName] = folder; 550 | scene.allObjects[name] = folder; 551 | if (id != 0) { 552 | scene.allObjects[id.toString()] = folder; 553 | } 554 | } else if (c.id == 'Constraint') { 555 | int id; 556 | String rawName; 557 | 558 | if (c.properties.length == 3) { 559 | id = c.getInt(0); 560 | rawName = c.properties[1] as String; 561 | } else { 562 | id = 0; 563 | rawName = c.properties[0] as String; 564 | } 565 | 566 | final name = _parser.getName(rawName); 567 | 568 | final constraint = FbxObject(id, name, c.id, c, scene); 569 | scene.allObjects[rawName] = constraint; 570 | scene.allObjects[name] = constraint; 571 | if (id != 0) { 572 | scene.allObjects[id.toString()] = constraint; 573 | } 574 | } else if (c.id == 'AnimationCurve') { 575 | int id; 576 | String rawName; 577 | 578 | if (c.properties.length == 3) { 579 | id = c.getInt(0); 580 | rawName = c.properties[1] as String; 581 | } else { 582 | id = 0; 583 | rawName = c.properties[0] as String; 584 | } 585 | 586 | final name = _parser.getName(rawName); 587 | 588 | final animCurve = FbxAnimCurve(id, name, c, scene); 589 | scene.allObjects[rawName] = animCurve; 590 | scene.allObjects[name] = animCurve; 591 | if (id != 0) { 592 | scene.allObjects[id.toString()] = animCurve; 593 | } 594 | } else if (c.id == 'NodeAttribute') { 595 | int id; 596 | String rawName; 597 | 598 | if (c.properties.length == 3) { 599 | id = c.getInt(0); 600 | rawName = c.properties[1] as String; 601 | } else { 602 | id = 0; 603 | rawName = c.properties[0] as String; 604 | } 605 | 606 | final name = _parser.getName(rawName); 607 | 608 | final node = FbxNodeAttribute(id, name, c.id, c, scene); 609 | scene.allObjects[rawName] = node; 610 | scene.allObjects[name] = node; 611 | if (id != 0) { 612 | scene.allObjects[id.toString()] = node; 613 | } 614 | } else if (c.id == 'GlobalSettings') { 615 | var node = FbxGlobalSettings(c, scene); 616 | scene.globalSettings = node; 617 | } else if (c.id == 'SceneInfo') { 618 | var node = FbxObject(0, 'SceneInfo', c.id, c, scene); 619 | scene.sceneInfo = node; 620 | } else if (c.id == 'Pose') { 621 | var pose = FbxPose(c.properties[0].toString(), 622 | c.properties[1] as String, c, scene); 623 | scene.poses.add(pose); 624 | } else if (c.id == 'Video') { 625 | int id; 626 | String rawName; 627 | 628 | if (c.properties.length == 3) { 629 | id = c.getInt(0); 630 | rawName = c.properties[1] as String; 631 | } else { 632 | id = 0; 633 | rawName = c.properties[0] as String; 634 | } 635 | 636 | final name = _parser.getName(rawName); 637 | 638 | final video = FbxVideo(id, name, c.id, c, scene); 639 | 640 | scene.videos.add(video); 641 | scene.allObjects[rawName] = video; 642 | scene.allObjects[name] = video; 643 | if (id != 0) { 644 | scene.allObjects[id.toString()] = video; 645 | } 646 | } else { 647 | //print('UNKNOWN OBJECT ${c.id}'); 648 | } 649 | } 650 | } 651 | 652 | 653 | void _loadHeaderExtension(FbxElement e, FbxScene data) { 654 | for (final c in e.children) { 655 | if (c.id == 'OtherFlags') { 656 | for (final c2 in c.children) { 657 | if (c2.properties.length == 1) { 658 | data.header[c2.id] = c2.properties[0]; 659 | } 660 | } 661 | } else { 662 | if (c.properties.length == 1) { 663 | if (c.id == 'FBXVersion') { 664 | /*_fileVersion =*/ c.getInt(0); 665 | } 666 | 667 | data.header[c.id] = c.properties[0]; 668 | } 669 | } 670 | } 671 | } 672 | 673 | //int _fileVersion = 0; 674 | FbxParser _parser; 675 | } 676 | 677 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/fbx_parser.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_element.dart'; 3 | 4 | /// Base class for [FbxAsciiParser] and [FbxBinaryParser]. 5 | abstract class FbxParser { 6 | FbxElement nextElement(); 7 | 8 | // Get the raw scene name, which is different depending on if it's an 9 | // ascii or binary file. 10 | String sceneName(); 11 | 12 | // Node names are encoded with the type and need to be extracted. 13 | // The format of this encoding is different for binary and ascii; 14 | String getName(String rawName); 15 | } 16 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/input_buffer.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'bit_operators.dart'; 3 | import 'dart:typed_data'; 4 | 5 | /// A buffer that can be read as a stream of bytes. 6 | class InputBuffer { 7 | List buffer; 8 | int start; 9 | int end; 10 | int offset; 11 | bool bigEndian; 12 | 13 | /// Create a InputStream for reading from a List 14 | InputBuffer(List buffer, 15 | {this.bigEndian = false, int offset = 0, int length}) 16 | : buffer = buffer 17 | , start = offset 18 | , offset = offset 19 | , end = (length == null ? buffer.length : offset + length); 20 | 21 | /// Create a copy of [other]. 22 | InputBuffer.from(InputBuffer other, 23 | {int offset = 0, int length}) 24 | : buffer = other.buffer 25 | , offset = other.offset + offset 26 | , start = other.start 27 | , end = (length == null) ? other.end : other.offset + offset + length 28 | , bigEndian = other.bigEndian; 29 | 30 | /// The current read position relative to the start of the buffer. 31 | int get position => offset - start; 32 | 33 | /// How many bytes are left in the stream. 34 | int get length => end - offset; 35 | 36 | /// Is the current position at the end of the stream? 37 | bool get isEOS => offset >= end; 38 | 39 | /// Reset to the beginning of the stream. 40 | void rewind() { 41 | offset = start; 42 | } 43 | 44 | /// Access the buffer relative from the current position. 45 | int operator[](int index) => buffer[offset + index]; 46 | 47 | /// Set a buffer element relative to the current position. 48 | operator[]=(int index, int value) => buffer[offset + index] = value; 49 | 50 | /// Copy data from [other] to this buffer, at [start] offset from the 51 | /// current read position, and [length] number of bytes. [offset] is 52 | /// the offset in [other] to start reading. 53 | void memcpy(int start, int length, dynamic other, [int offset = 0]) { 54 | if (other is InputBuffer) { 55 | buffer.setRange(this.offset + start, this.offset + start + length, 56 | other.buffer, other.offset + offset); 57 | } else { 58 | buffer.setRange(this.offset + start, this.offset + start + length, 59 | other as List, offset); 60 | } 61 | } 62 | 63 | /// Set a range of bytes in this buffer to [value], at [start] offset from the 64 | /// current read position, and [length] number of bytes. 65 | void memset(int start, int length, int value) { 66 | buffer.fillRange(offset + start, offset + start + length, value); 67 | } 68 | 69 | /// Return a InputStream to read a subset of this stream. It does not 70 | /// move the read position of this stream. [position] is specified relative 71 | /// to the start of the buffer. If [position] is not specified, the current 72 | /// read position is used. If [length] is not specified, the remainder of this 73 | /// stream is used. 74 | InputBuffer subset(int count, {int position, int offset = 0}) { 75 | var pos = position != null ? start + position : this.offset; 76 | pos += offset; 77 | 78 | return InputBuffer(buffer, bigEndian: bigEndian, offset: pos, 79 | length: count); 80 | } 81 | 82 | /// Returns the position of the given [value] within the buffer, starting 83 | /// from the current read position with the given [offset]. The position 84 | /// returned is relative to the start of the buffer, or -1 if the [value] 85 | /// was not found. 86 | int indexOf(int value, [int offset = 0]) { 87 | for (var i = this.offset + offset, end = this.offset + length; 88 | i < end; ++i) { 89 | if (buffer[i] == value) { 90 | return i - start; 91 | } 92 | } 93 | return -1; 94 | } 95 | 96 | /// Read [count] bytes from an [offset] of the current read position, without 97 | /// moving the read position. 98 | InputBuffer peekBytes(int count, [int offset = 0]) { 99 | return subset(count, offset: offset); 100 | } 101 | 102 | /// Move the read position by [count] bytes. 103 | void skip(int count) { 104 | offset += count; 105 | } 106 | 107 | /// Read a single byte. 108 | int readByte() { 109 | return buffer[offset++]; 110 | } 111 | 112 | int readInt8() { 113 | return uint8ToInt8(readByte()); 114 | } 115 | 116 | /// Read [count] bytes from the stream. 117 | InputBuffer readBytes(int count) { 118 | final bytes = subset(count); 119 | offset += bytes.length; 120 | return bytes; 121 | } 122 | 123 | /// Read a null-terminated string, or if [len] is provided, that number of 124 | /// bytes returned as a string. 125 | String readString([int len]) { 126 | if (len == null) { 127 | final codes = []; 128 | while (!isEOS) { 129 | final c = readByte(); 130 | if (c == 0) { 131 | return String.fromCharCodes(codes); 132 | } 133 | codes.add(c); 134 | } 135 | throw Exception('EOF reached without finding string terminator'); 136 | } 137 | 138 | final s = readBytes(len); 139 | final bytes = s.toUint8List(); 140 | final str = String.fromCharCodes(bytes); 141 | return str; 142 | } 143 | 144 | /// Read a 16-bit word from the stream. 145 | int readUint16() { 146 | final b1 = buffer[offset++] & 0xff; 147 | final b2 = buffer[offset++] & 0xff; 148 | if (bigEndian) { 149 | return (b1 << 8) | b2; 150 | } 151 | return (b2 << 8) | b1; 152 | } 153 | 154 | /// Read a 16-bit word from the stream. 155 | int readInt16() { 156 | return uint16ToInt16(readUint16()); 157 | } 158 | 159 | /// Read a 24-bit word from the stream. 160 | int readUint24() { 161 | final b1 = buffer[offset++] & 0xff; 162 | final b2 = buffer[offset++] & 0xff; 163 | final b3 = buffer[offset++] & 0xff; 164 | if (bigEndian) { 165 | return b3 | (b2 << 8) | (b1 << 16); 166 | } 167 | return b1 | (b2 << 8) | (b3 << 16); 168 | } 169 | 170 | /// Read a 32-bit word from the stream. 171 | int readUint32() { 172 | final b1 = buffer[offset++] & 0xff; 173 | final b2 = buffer[offset++] & 0xff; 174 | final b3 = buffer[offset++] & 0xff; 175 | final b4 = buffer[offset++] & 0xff; 176 | if (bigEndian) { 177 | return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; 178 | } 179 | return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; 180 | } 181 | 182 | /// Read a signed 32-bit integer from the stream. 183 | int readInt32() { 184 | return uint32ToInt32(readUint32()); 185 | } 186 | 187 | /// Read a 64-bit word form the stream. 188 | int readUint64() { 189 | final b1 = buffer[offset++] & 0xff; 190 | final b2 = buffer[offset++] & 0xff; 191 | final b3 = buffer[offset++] & 0xff; 192 | final b4 = buffer[offset++] & 0xff; 193 | final b5 = buffer[offset++] & 0xff; 194 | final b6 = buffer[offset++] & 0xff; 195 | final b7 = buffer[offset++] & 0xff; 196 | final b8 = buffer[offset++] & 0xff; 197 | if (bigEndian) { 198 | return (b1 << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32) | 199 | (b5 << 24) | (b6 << 16) | (b7 << 8) | b8; 200 | } 201 | return (b8 << 56) | (b7 << 48) | (b6 << 40) | (b5 << 32) | 202 | (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; 203 | } 204 | 205 | int readInt64() { 206 | return uint64ToInt64(readUint64()); 207 | } 208 | 209 | /// Read a 32-bit float. 210 | double readFloat32() { 211 | return uint32ToFloat32(readUint32()); 212 | } 213 | 214 | /// Read a 32-bit float. 215 | double readFloat64() { 216 | return uint64ToFloat64(readUint64()); 217 | } 218 | 219 | List toList([int offset = 0, int length = 0]) { 220 | if (buffer is Uint8List) { 221 | return toUint8List(offset, length); 222 | } 223 | final s = start + this.offset + offset; 224 | final e = (length <= 0) ? end : s + length; 225 | return buffer.sublist(s, e); 226 | } 227 | 228 | Uint8List toUint8List([int offset = 0, int length]) { 229 | final len = length ?? this.length - offset; 230 | if (buffer is Uint8List) { 231 | final b = buffer as Uint8List; 232 | return Uint8List.view(b.buffer, 233 | b.offsetInBytes + this.offset + offset, len); 234 | } 235 | return Uint8List.fromList(buffer.sublist(this.offset + offset, 236 | this.offset + offset + len)); 237 | } 238 | 239 | Uint32List toUint32List([int offset = 0]) { 240 | if (buffer is Uint8List) { 241 | final b = buffer as Uint8List; 242 | return Uint32List.view(b.buffer, b.offsetInBytes + this.offset + offset); 243 | } 244 | return Uint32List.view(toUint8List().buffer); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/matrix_utils.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'package:vector_math/vector_math.dart'; 3 | import 'dart:math'; 4 | 5 | Matrix4 inverseMat(Matrix4 m) { 6 | final i = Matrix4.copy(m); 7 | i.invert(); 8 | return i; 9 | } 10 | 11 | void scaleMat(Matrix4 mat, double s) { 12 | for (var i = 0; i < 16; ++i) { 13 | mat.storage[i] *= s; 14 | } 15 | } 16 | 17 | // TODO vector_math Matrix4.rotateY has a bug. Replace this version with the 18 | // vector_math version as soon as it gets fixed. 19 | void rotateY(Matrix4 mat, double angle) { 20 | final cosAngle = cos(angle); 21 | final sinAngle = sin(angle); 22 | var t1 = mat.storage[0] * cosAngle - mat.storage[8] * sinAngle; 23 | var t2 = mat.storage[1] * cosAngle - mat.storage[9] * sinAngle; 24 | var t3 = mat.storage[2] * cosAngle - mat.storage[10] * sinAngle; 25 | var t4 = mat.storage[3] * cosAngle - mat.storage[11] * sinAngle; 26 | var t5 = mat.storage[0] * sinAngle + mat.storage[8] * cosAngle; 27 | var t6 = mat.storage[1] * sinAngle + mat.storage[9] * cosAngle; 28 | var t7 = mat.storage[2] * sinAngle + mat.storage[10] * cosAngle; 29 | var t8 = mat.storage[3] * sinAngle + mat.storage[11] * cosAngle; 30 | mat.storage[0] = t1; 31 | mat.storage[1] = t2; 32 | mat.storage[2] = t3; 33 | mat.storage[3] = t4; 34 | mat.storage[8] = t5; 35 | mat.storage[9] = t6; 36 | mat.storage[10] = t7; 37 | mat.storage[11] = t8; 38 | } 39 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_curve.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_anim_key.dart'; 5 | import 'fbx_scene.dart'; 6 | 7 | class FbxAnimCurve extends FbxObject { 8 | double defaultValue; 9 | List keys = []; 10 | 11 | FbxAnimCurve(int id, String name, FbxElement element, FbxScene scene) 12 | : super(id, name, 'AnimCurve', element, scene) { 13 | 14 | if (element == null) { 15 | return; 16 | } 17 | 18 | //int version = 0; 19 | List keyTime; 20 | List keyValue; 21 | 22 | for (final c in element.children) { 23 | if (c.id == 'Default') { 24 | 25 | } else if (c.id == 'KeyVer') { 26 | //version = c.getInt(0); 27 | } else if (c.id == 'KeyTime') { 28 | if (c.children.isEmpty) { 29 | continue; 30 | } 31 | keyTime = c.children[0].properties; 32 | } else if (c.id == 'KeyValueFloat') { 33 | if (c.children.isEmpty) { 34 | continue; 35 | } 36 | keyValue = c.children[0].properties; 37 | } else if (c.id == 'KeyAttrFlags') { 38 | 39 | } else if (c.id == 'KeyAttrDataFloat') { 40 | 41 | } else if (c.id == 'KeyAttrRefCount') { 42 | 43 | } 44 | } 45 | 46 | if (keyTime != null && keyValue != null) { 47 | if (keyTime.length == keyValue.length) { 48 | for (var i = 0; i < keyTime.length; ++i) { 49 | keys.add(FbxAnimKey(toInt(keyTime[i]), 50 | toDouble(keyValue[i]), 51 | FbxAnimKey.INTERPOLATION_LINEAR)); 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | int get numKeys => keys.length; 59 | 60 | int keyTime(int index) => keys[index].time; 61 | 62 | double keyValue(int index) => keys[index].value; 63 | } 64 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_curve_node.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxAnimCurveNode extends FbxObject { 7 | FbxAnimCurveNode(int id, String name, FbxElement element, FbxScene scene) 8 | : super(id, name, 'AnimCurveNode', element, scene); 9 | } 10 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_evaluator.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../matrix_utils.dart'; 3 | import 'fbx_anim_curve.dart'; 4 | import 'fbx_anim_curve_node.dart'; 5 | import 'fbx_node.dart'; 6 | import 'fbx_object.dart'; 7 | import 'fbx_scene.dart'; 8 | import 'package:vector_math/vector_math.dart'; 9 | 10 | class FbxAnimEvaluator extends FbxObject { 11 | FbxAnimEvaluator(FbxScene scene) 12 | : super(0, '', 'AnimEvaluator', null, scene); 13 | 14 | Matrix4 getNodeGlobalTransform(FbxNode node, double time) { 15 | var t = getNodeLocalTransform(node, time); 16 | 17 | if (node.parent != null) { 18 | final pt = getNodeGlobalTransform(node.parent, time); 19 | t = (pt * t) as Matrix4; 20 | } 21 | 22 | return t; 23 | } 24 | 25 | Matrix4 getNodeLocalTransform(FbxNode node, double time) { 26 | // Cache the evaluated transform 27 | if (node.evalTime == time) { 28 | return node.transform; 29 | } 30 | node.evalTime = time; 31 | 32 | final t = node.translate.value as Vector3; 33 | final r = node.rotate.value as Vector3; 34 | final s = node.scale.value as Vector3; 35 | var tx = t.x; 36 | var ty = t.y; 37 | var tz = t.z; 38 | var rx = r.x; 39 | var ry = r.y; 40 | var rz = r.z; 41 | var sx = s.x; 42 | var sy = s.y; 43 | var sz = s.z; 44 | 45 | if (node.translate.connectedFrom != null && 46 | node.translate.connectedFrom is FbxAnimCurveNode) { 47 | final animNode = node.translate.connectedFrom as FbxAnimCurveNode; 48 | final ap = animNode.properties; 49 | 50 | if (ap.containsKey('X')) { 51 | tx = evalCurve(ap['X'].connectedFrom as FbxAnimCurve, time); 52 | } 53 | 54 | if (ap.containsKey('Y')) { 55 | ty = evalCurve(ap['Y'].connectedFrom as FbxAnimCurve, time); 56 | } 57 | 58 | if (ap.containsKey('Z')) { 59 | tz = evalCurve(ap['Z'].connectedFrom as FbxAnimCurve, time); 60 | } 61 | } 62 | 63 | 64 | if (node.rotate.connectedFrom != null 65 | && node.rotate.connectedFrom is FbxAnimCurveNode) { 66 | final animNode = node.rotate.connectedFrom as FbxAnimCurveNode; 67 | final ap = animNode.properties; 68 | 69 | if (ap.containsKey('X')) { 70 | rx = evalCurve(ap['X'].connectedFrom as FbxAnimCurve, time); 71 | } 72 | 73 | if (ap.containsKey('Y')) { 74 | ry = evalCurve(ap['Y'].connectedFrom as FbxAnimCurve, time); 75 | } 76 | 77 | if (ap.containsKey('Z')) { 78 | rz = evalCurve(ap['Z'].connectedFrom as FbxAnimCurve, time); 79 | } 80 | } 81 | 82 | 83 | if (node.scale.connectedFrom != null 84 | && node.scale.connectedFrom is FbxAnimCurveNode) { 85 | final animNode = node.scale.connectedFrom as FbxAnimCurveNode; 86 | final ap = animNode.properties; 87 | 88 | if (ap.containsKey('X')) { 89 | sx = evalCurve(ap['X'].connectedFrom as FbxAnimCurve, time); 90 | } 91 | 92 | if (ap.containsKey('Y')) { 93 | sy = evalCurve(ap['Y'].connectedFrom as FbxAnimCurve, time); 94 | } 95 | 96 | if (ap.containsKey('Z')) { 97 | sz = evalCurve(ap['Z'].connectedFrom as FbxAnimCurve, time); 98 | } 99 | } 100 | 101 | node.transform.setIdentity(); 102 | node.transform.translate(tx, ty, tz); 103 | node.transform.rotateZ(radians(rz)); 104 | rotateY(node.transform, radians(ry)); 105 | node.transform.rotateX(radians(rx)); 106 | node.transform.scale(sx, sy, sz); 107 | 108 | return node.transform; 109 | } 110 | 111 | 112 | double evalCurve(FbxAnimCurve curve, double frame) { 113 | if (curve.numKeys == 0) { 114 | if (curve.defaultValue != null) { 115 | return curve.defaultValue; 116 | } 117 | return 0.0; 118 | } 119 | 120 | if (frame < scene.timeToFrame(curve.keyTime(0))) { 121 | return curve.keyValue(0); 122 | } 123 | 124 | for (var i = 0, numKeys = curve.numKeys; i < numKeys; ++i) { 125 | final kf = scene.timeToFrame(curve.keyTime(i)); 126 | if (frame == kf) { 127 | return curve.keyValue(i); 128 | } 129 | 130 | if (frame < kf) { 131 | if (i == 0) { 132 | return curve.keyValue(i); 133 | } 134 | 135 | final kf2 = scene.timeToFrame(curve.keyTime(i - 1)); 136 | 137 | final u = (frame - kf2) / (kf - kf2); 138 | 139 | return ((1.0 - u) * curve.keyValue(i - 1)) + 140 | (u * curve.keyValue(i)); 141 | } 142 | } 143 | 144 | return curve.keys.last.value; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_key.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | class FbxAnimKey { 4 | static const int INTERPOLATION_CONSTANT = 0; 5 | static const int INTERPOLATION_LINEAR = 1; 6 | static const int INTERPOLATION_CUBIC = 2; 7 | 8 | static const int WEIGHTED_NONE = 0; 9 | static const int WEIGHTED_RIGHT = 1; 10 | static const int WEIGHTED_NEXT_LEFT = 2; 11 | 12 | static const int CONSTANT_STANDARD = 0; 13 | static const int CONSTANT_NEXT = 1; 14 | 15 | int time; 16 | double value; 17 | int interpolation; 18 | 19 | FbxAnimKey(this.time, this.value, this.interpolation); 20 | 21 | @override 22 | String toString() => '<$time : $value>'; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_layer.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | import 'package:vector_math/vector_math.dart'; 7 | 8 | class FbxAnimLayer extends FbxObject { 9 | static const int BLEND_ADDITIVE = 0; 10 | static const int BLEND_OVERRIDE = 1; 11 | static const int BLEND_OVERRIDE_PASSTHROUGH = 2; 12 | 13 | static const int ROTATION_BY_LAYER = 0; 14 | static const int ROTATION_BY_CHANNEL = 1; 15 | 16 | static const int SCALE_MULTIPLY = 0; 17 | static const int SCALE_ADDITIVE = 1; 18 | 19 | FbxProperty weight; 20 | FbxProperty mute; 21 | FbxProperty solo; 22 | FbxProperty lock; 23 | FbxProperty color; 24 | FbxProperty blendMode; 25 | FbxProperty rotationAccumulationMode; 26 | FbxProperty scaleAccumulationMode; 27 | 28 | FbxAnimLayer(int id, String name, FbxElement element, FbxScene scene) 29 | : super(id, name, 'AnimLayer', element, scene) { 30 | weight = addProperty('Weight', 100.0); 31 | mute = addProperty('Mute', false); 32 | solo = addProperty('Solo', false); 33 | lock = addProperty('Lock', false); 34 | color = addProperty('Color', Vector3(0.8, 0.8, 0.8)); 35 | blendMode = addProperty('BlendMode', BLEND_ADDITIVE); 36 | rotationAccumulationMode = addProperty('RotationAccumulationMode', ROTATION_BY_LAYER); 37 | scaleAccumulationMode = addProperty('ScaleAccumulationMode', SCALE_MULTIPLY); 38 | 39 | for (final c in element.children) { 40 | if (c.id == 'Weight') { 41 | weight.value = c.getDouble(0); 42 | } else if (c.id == 'Mute') { 43 | weight.value = c.getInt(0) != 0; 44 | } else if (c.id == 'Solo') { 45 | solo.value = c.getInt(0) != 0; 46 | } else if (c.id == 'Lock') { 47 | lock.value = c.getInt(0) != 0; 48 | } else if (c.id == 'Color') { 49 | color.value = Vector3(c.getDouble(0), c.getDouble(1), c.getDouble(2)); 50 | } else if (c.id == 'BlendMode') { 51 | blendMode.value = c.getInt(0); 52 | } else if (c.id == 'RotationAccumulationMode') { 53 | rotationAccumulationMode.value = c.getInt(0); 54 | } else if (c.id == 'ScaleAccumulationMode') { 55 | scaleAccumulationMode.value = c.getInt(0); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_anim_stack.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | 7 | class FbxAnimStack extends FbxObject { 8 | FbxProperty description; 9 | FbxProperty localStart; 10 | FbxProperty localStop; 11 | FbxProperty referenceStart; 12 | FbxProperty referenceStop; 13 | 14 | FbxAnimStack(int id, String name, FbxElement element, FbxScene scene) 15 | : super(id, name, 'AnimStack', element, scene) { 16 | description = addProperty('Description', ''); 17 | localStart = addProperty('LocalStart', 0); 18 | localStop = addProperty('LocalStop', 0); 19 | referenceStart = addProperty('ReferenceStart', 0); 20 | referenceStop = addProperty('ReferenceStop', 0); 21 | 22 | for (final c in element.children) { 23 | if (c.id == 'Properties70') { 24 | for (final c2 in c.children) { 25 | if (c2.properties[0] == 'Description') { 26 | description.value = c2.properties[4]; 27 | } else if (c2.properties[0] == 'LocalStart') { 28 | localStart.value = c2.getInt(4); 29 | } else if (c2.properties[0] == 'LocalStop') { 30 | localStop.value = c2.getInt(4); 31 | } else if (c2.properties[0] == 'ReferenceStart') { 32 | referenceStart.value = c2.getInt(4); 33 | } else if (c2.properties[0] == 'ReferenceStop') { 34 | referenceStop.value = c2.getInt(4); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_camera.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node_attribute.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | import 'package:vector_math/vector_math.dart'; 7 | 8 | class FbxCamera extends FbxNodeAttribute { 9 | FbxProperty position; 10 | FbxProperty lookAt; 11 | FbxProperty cameraOrthoZoom; 12 | FbxProperty roll; 13 | FbxProperty fieldOfView; 14 | FbxProperty frameColor; 15 | FbxProperty nearPlane; 16 | FbxProperty farPlane; 17 | 18 | FbxCamera(int id, String name, FbxElement element, FbxScene scene) 19 | : super(id, name, 'Camera', element, scene) { 20 | position = addProperty('Position', Vector3(0.0, 0.0, 0.0)); 21 | lookAt = addProperty('LookAt', Vector3(0.0, 0.0, 0.0)); 22 | cameraOrthoZoom = addProperty('CameraOrthoZoom', 1.0); 23 | roll = addProperty('Roll', 0.0); 24 | fieldOfView = addProperty('FieldOfView', 40.0); 25 | frameColor = addProperty('FrameColor', Vector3(0.0, 0.0, 0.0)); 26 | nearPlane = addProperty('NearPlane', 1.0); 27 | farPlane = addProperty('FarPlane', 10000.0); 28 | 29 | for (final c in element.children) { 30 | if (c.id == 'CameraOrthoZoom') { 31 | cameraOrthoZoom.value = c.getDouble(0); 32 | } else if (c.id == 'LookAt') { 33 | lookAt.value = Vector3(c.getDouble(0), c.getDouble(1), c.getDouble(2)); 34 | } else if (c.id == 'Position') { 35 | position.value = Vector3(c.getDouble(0), c.getDouble(1), c.getDouble(2)); 36 | } else if (c.id == 'Properties60') { 37 | for (final p in c.children) { 38 | if (p.id == 'Property') { 39 | if (p.properties[0] == 'Roll') { 40 | roll.value = p.getDouble(3); 41 | } else if (p.properties[0] == 'FieldOfView') { 42 | fieldOfView.value = p.getDouble(3); 43 | } else if (p.properties[0] == 'FrameColor') { 44 | frameColor.value = Vector3(p.getDouble(3), p.getDouble(4), p.getDouble(5)); 45 | } else if (p.properties[0] == 'NearPlane') { 46 | nearPlane.value = p.getDouble(3); 47 | } else if (p.properties[0] == 'FarPlane') { 48 | farPlane.value = p.getDouble(3); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_camera_switcher.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node_attribute.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxCameraSwitcher extends FbxNodeAttribute { 7 | FbxCameraSwitcher(int id, String name, FbxElement element, FbxScene scene) 8 | : super(id, name, 'CameraSwitcher', element, scene); 9 | } 10 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_cluster.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_deformer.dart'; 4 | import 'fbx_node.dart'; 5 | import 'fbx_scene.dart'; 6 | import 'dart:typed_data'; 7 | import 'package:vector_math/vector_math.dart'; 8 | 9 | class FbxCluster extends FbxDeformer { 10 | static const int NORMALIZE = 0; 11 | static const int ADDITIVE = 1; 12 | static const int TOTAL_ONE = 2; 13 | 14 | Uint32List indexes; 15 | Float32List weights; 16 | Matrix4 transform; 17 | Matrix4 transformLink; 18 | int linkMode = NORMALIZE; 19 | 20 | FbxCluster(int id, String name, FbxElement element, FbxScene scene) 21 | : super(id, name, 'Cluster', element, scene) { 22 | 23 | for (final c in element.children) { 24 | var p = ((c.properties.length == 1 && c.properties[0] is List) 25 | ? c.properties[0] 26 | : (c.children.length == 1) 27 | ? c.children[0].properties 28 | : c.properties) as List; 29 | 30 | if (c.id == 'Indexes') { 31 | indexes = Uint32List(p.length); 32 | for (var i = 0, len = p.length; i < len; ++i) { 33 | indexes[i] = toInt(p[i]); 34 | } 35 | } else if (c.id == 'Weights') { 36 | weights = Float32List(p.length); 37 | for (var i = 0, len = p.length; i < len; ++i) { 38 | weights[i] = toDouble(p[i]); 39 | } 40 | } else if (c.id == 'Transform') { 41 | transform = Matrix4.identity(); 42 | for (var i = 0, len = p.length; i < len; ++i) { 43 | transform.storage[i] = toDouble(p[i]); 44 | } 45 | } else if (c.id == 'TransformLink') { 46 | transformLink = Matrix4.identity(); 47 | for (var i = 0, len = p.length; i < len; ++i) { 48 | transformLink.storage[i] = toDouble(p[i]); 49 | } 50 | } 51 | } 52 | } 53 | 54 | FbxNode getLink() => 55 | connectedTo.isNotEmpty ? connectedTo[0] as FbxNode : null; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_deformer.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxDeformer extends FbxObject { 7 | FbxDeformer(int id, String name, String type, FbxElement element, 8 | FbxScene scene) 9 | : super(id, name, type, element, scene); 10 | } 11 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_display_mesh.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'dart:math'; 3 | import 'dart:typed_data'; 4 | import 'package:vector_math/vector_math.dart'; 5 | 6 | class FbxDisplayMesh { 7 | int numPoints; 8 | Float32List points; 9 | Float32List normals; 10 | Float32List uvs; 11 | Float32List colors; 12 | Uint16List indices; 13 | Float32List skinWeights; 14 | Float32List skinIndices; 15 | List> pointMap; 16 | 17 | void generateSmoothNormals() { 18 | if (indices == null || points == null) { 19 | return; 20 | } 21 | 22 | // Compute normals 23 | normals = Float32List(points.length); 24 | for (var ti = 0; ti < indices.length; ti += 3) { 25 | final p1 = Vector3(points[indices[ti] * 3], 26 | points[indices[ti] * 3 + 1], 27 | points[indices[ti] * 3 + 2]); 28 | 29 | final p2 = Vector3(points[indices[ti + 1] * 3], 30 | points[indices[ti + 1] * 3 + 1], 31 | points[indices[ti + 1] * 3 + 2]); 32 | 33 | final p3 = Vector3(points[indices[ti + 2] * 3], 34 | points[indices[ti + 2] * 3 + 1], 35 | points[indices[ti + 2] * 3 + 2]); 36 | 37 | final N = (p2 - p1).cross(p3 - p1); 38 | 39 | normals[indices[ti] * 3] += N.x; 40 | normals[indices[ti] * 3 + 1] += N.y; 41 | normals[indices[ti] * 3 + 2] += N.z; 42 | 43 | normals[indices[ti + 1] * 3] += N.x; 44 | normals[indices[ti + 1] * 3 + 1] += N.y; 45 | normals[indices[ti + 1] * 3 + 2] += N.z; 46 | 47 | normals[indices[ti + 2] * 3] += N.x; 48 | normals[indices[ti + 2] * 3 + 1] += N.y; 49 | normals[indices[ti + 2] * 3 + 2] += N.z; 50 | } 51 | 52 | for (var ni = 0; ni < normals.length; ni += 3) { 53 | var l = normals[ni] * normals[ni] + 54 | normals[ni + 1] * normals[ni + 1] + 55 | normals[ni + 2] * normals[ni + 2]; 56 | if (l == 0.0) { 57 | continue; 58 | } 59 | 60 | l = 1.0 / sqrt(l); 61 | 62 | normals[ni] *= l; 63 | normals[ni + 1] *= l; 64 | normals[ni + 2] *= l; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_edge.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | class FbxEdge { 4 | List vertices = [0, 0]; 5 | 6 | FbxEdge(int x, int y) { 7 | vertices[0] = x; 8 | vertices[1] = y; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_frame_rate.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | class FbxFrameRate { 4 | static const int DEFAULT = 0; 5 | static const int FPS_120 = 1; 6 | static const int FPS_100 = 2; 7 | static const int FPS_60 = 3; 8 | static const int FPS_50 = 4; 9 | static const int FPS_48 = 5; 10 | static const int FPS_30 = 6; 11 | static const int FPS_30_DROP = 7; 12 | static const int NTSC_DROP_FRAME = 8; 13 | static const int NTSC_FULL_FRAME = 9; 14 | static const int PAL = 10; 15 | static const int FPS_24 = 11; 16 | static const int FPS_1000 = 12; 17 | static const int FILM_FULL_FRAME = 13; 18 | static const int CUSTOM = 14; 19 | static const int FPS_96 = 15; 20 | static const int FPS_72 = 16; 21 | static const int FPS_59_DOT_94 = 17; 22 | 23 | static double timeToFrame(int timeValue, int frameRate) { 24 | return (timeValue / 1924423250.0); 25 | } 26 | 27 | static double timeToSeconds(int timeValue, int frameRate) { 28 | return frameToSeconds(timeToFrame(timeValue, frameRate), frameRate); 29 | } 30 | 31 | static double frameToSeconds(double frame, int frameRate) { 32 | switch (frameRate) { 33 | case FPS_24: 34 | return frame / 24.0; 35 | } 36 | return frame; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_geometry.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node_attribute.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxGeometry extends FbxNodeAttribute { 7 | FbxGeometry(int id, String name, String type, FbxElement element, 8 | FbxScene scene) 9 | : super(id, name, type, element, scene); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_global_settings.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_frame_rate.dart'; 4 | import 'fbx_object.dart'; 5 | import 'fbx_property.dart'; 6 | import 'fbx_scene.dart'; 7 | import 'package:vector_math/vector_math.dart'; 8 | 9 | class FbxGlobalSettings extends FbxObject { 10 | FbxProperty upAxis; 11 | FbxProperty upAxisSign; 12 | FbxProperty frontAxis; 13 | FbxProperty frontAxisSign; 14 | FbxProperty coordAxis; 15 | FbxProperty coordAxisSign; 16 | FbxProperty originalUpAxis; 17 | FbxProperty originalUpAxisSign; 18 | FbxProperty unitScaleFactor; 19 | FbxProperty originalUnitScaleFactor; 20 | FbxProperty ambientColor; 21 | FbxProperty defaultCamera; 22 | FbxProperty timeMode; 23 | FbxProperty timeProtocol; 24 | FbxProperty snapOnFrameMode; 25 | FbxProperty timeSpanStart; 26 | FbxProperty timeSpanStop; 27 | FbxProperty customFrameRate; 28 | 29 | FbxGlobalSettings(FbxElement element, FbxScene scene) 30 | : super(0, '', 'GlobalSettings', element, scene) { 31 | 32 | upAxis = addProperty('UpAxis', 1); 33 | upAxisSign = addProperty('UpAxisSign', 1); 34 | frontAxis = addProperty('FrontAxis', 2); 35 | frontAxisSign = addProperty('FrontAxisSign', 1); 36 | coordAxis = addProperty('CoordAxis', 0); 37 | coordAxisSign = addProperty('CoordAxisSign', 1); 38 | originalUpAxis = addProperty('OriginalUpAxis', 0); 39 | originalUpAxisSign = addProperty('OriginalUpAxisSign', 1); 40 | unitScaleFactor = addProperty('UnitScaleFactor', 1.0); 41 | originalUnitScaleFactor = addProperty('OriginalUnitScaleFactor', 1.0); 42 | ambientColor = addProperty('AmbientColor', Vector3(0.0, 0.0, 0.0)); 43 | defaultCamera = addProperty('DefaultCamera', ''); 44 | timeMode = addProperty('TimeMode', FbxFrameRate.DEFAULT); 45 | timeProtocol = addProperty('TimeProtocol', 0); 46 | snapOnFrameMode = addProperty('SnapOnFrameMode', 0); 47 | timeSpanStart = addProperty('TimeSpanStart', 0); 48 | timeSpanStop = addProperty('TimeSpanEnd', 0); 49 | customFrameRate = addProperty('CustomFrameRate', -1.0); 50 | 51 | for (final c in element.children) { 52 | if (c.id == 'Properties60' || c.id == 'Properties70') { 53 | final vi = c.id == 'Properties60' ? 3 : 4; 54 | for (final p in c.children) { 55 | if (p.properties[0] == 'UpAxis') { 56 | upAxis.value = p.getInt(vi); 57 | } else if (p.properties[0] == 'UpAxisSign') { 58 | upAxisSign.value = p.getInt(vi); 59 | } else if (p.properties[0] == 'FrontAxis') { 60 | frontAxis.value = p.getInt(vi); 61 | } else if (p.properties[0] == 'FrontAxisSign') { 62 | frontAxisSign.value = p.getInt(vi); 63 | } else if (p.properties[0] == 'CoordAxis') { 64 | coordAxis.value = p.getInt(vi); 65 | } else if (p.properties[0] == 'CoordAxisSign') { 66 | coordAxisSign.value = p.getInt(vi); 67 | } else if (p.properties[0] == 'OriginalUpAxis') { 68 | originalUpAxis.value = p.getInt(vi); 69 | } else if (p.properties[0] == 'OriginalUpAxisSign') { 70 | originalUpAxisSign.value = p.getInt(vi); 71 | } else if (p.properties[0] == 'UnitScaleFactor') { 72 | unitScaleFactor.value = p.getDouble(vi); 73 | } else if (p.properties[0] == 'OriginalUnitScaleFactor') { 74 | originalUnitScaleFactor.value = p.getDouble(vi); 75 | } else if (p.properties[0] == 'AmbientColor') { 76 | ambientColor.value = Vector3(p.getDouble(vi), p.getDouble(vi), 77 | p.getDouble(vi)); 78 | } else if (p.properties[0] == 'DefaultCamera') { 79 | defaultCamera.value = p.getString(vi); 80 | } else if (p.properties[0] == 'TimeMode') { 81 | timeMode.value = p.getInt(vi); 82 | } else if (p.properties[0] == 'TimeProtocol') { 83 | timeProtocol.value = p.getInt(vi); 84 | } else if (p.properties[0] == 'SnapOnFrameMode') { 85 | snapOnFrameMode.value = p.getInt(vi); 86 | } else if (p.properties[0] == 'TimeSpanStart') { 87 | timeSpanStart.value = p.getInt(vi); 88 | } else if (p.properties[0] == 'TimeSpanStop') { 89 | timeSpanStop.value = p.getInt(vi); 90 | } else if (p.properties[0] == 'CustomFrameRate') { 91 | customFrameRate.value = p.getDouble(vi); 92 | } 93 | } 94 | } 95 | } 96 | 97 | scene.startFrame = FbxFrameRate.timeToFrame(timeSpanStart.value as int, 98 | timeMode.value as int); 99 | 100 | scene.endFrame = FbxFrameRate.timeToFrame(timeSpanStop.value as int, 101 | timeMode.value as int); 102 | 103 | scene.currentFrame = scene.startFrame; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_layer.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_layer_element.dart'; 3 | import 'package:vector_math/vector_math.dart'; 4 | 5 | class FbxLayer { 6 | bool get hasNormals => _normals != null; 7 | 8 | FbxLayerElement get normals { 9 | _normals ??= FbxLayerElement(); 10 | return _normals; 11 | } 12 | 13 | 14 | bool get hasBinormals => _binormals != null; 15 | 16 | FbxLayerElement get binormals { 17 | _binormals ??= FbxLayerElement(); 18 | return _binormals; 19 | } 20 | 21 | 22 | bool get hasTangents => _tangents != null; 23 | 24 | FbxLayerElement get tangents { 25 | _tangents ??= FbxLayerElement(); 26 | return _tangents; 27 | } 28 | 29 | 30 | bool get hasUvs => _uvs != null; 31 | 32 | FbxLayerElement get uvs { 33 | _uvs ??= FbxLayerElement(); 34 | return _uvs; 35 | } 36 | 37 | 38 | bool get hasColors => _colors != null; 39 | 40 | FbxLayerElement get colors { 41 | _colors ??= FbxLayerElement(); 42 | return _colors; 43 | } 44 | 45 | FbxLayerElement _normals; 46 | FbxLayerElement _binormals; 47 | FbxLayerElement _tangents; 48 | FbxLayerElement _uvs; 49 | FbxLayerElement _colors; 50 | } 51 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_layer_element.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_mapping_mode.dart'; 3 | import 'fbx_reference_mode.dart'; 4 | 5 | class FbxLayerElement { 6 | FbxMappingMode mappingMode = FbxMappingMode.None; 7 | FbxReferenceMode referenceMode = FbxReferenceMode.Direct; 8 | List indexArray; 9 | List data; 10 | 11 | int get length => data.length; 12 | 13 | T operator[](int index) => data[index]; 14 | 15 | operator[]=(int index, T v) => data[index] = v; 16 | } 17 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_light.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node_attribute.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | import 'package:vector_math/vector_math.dart'; 7 | 8 | class FbxLight extends FbxNodeAttribute { 9 | static const int SPOT = 0; 10 | static const int POINT = 1; 11 | static const int DIRECTIONAL = 2; 12 | 13 | static const int NO_DECAY = 0; 14 | static const int LINEAR_DECAY = 1; 15 | static const int QUADRATIC_DECAY = 2; 16 | static const int CUBIC_DECAY = 3; 17 | 18 | FbxProperty color; 19 | FbxProperty intensity; 20 | FbxProperty coneAngle; 21 | FbxProperty decay; 22 | FbxProperty lightType; 23 | 24 | FbxLight(int id, String name, FbxElement element, FbxScene scene) 25 | : super(id, name, 'Light', element, scene) { 26 | color = addProperty('Color', Vector3(1.0, 1.0, 1.0)); 27 | intensity = addProperty('Intensity', 1.0); 28 | coneAngle = addProperty('Cone angle', 1.0); 29 | decay = addProperty('Decay', NO_DECAY); 30 | lightType = addProperty('LightType', DIRECTIONAL); 31 | 32 | for (final c in element.children) { 33 | if (c.id == 'Properties60') { 34 | for (final p in c.children) { 35 | if (p.id == 'Property') { 36 | if (p.properties[0] == 'Color') { 37 | color.value = Vector3(p.getDouble(3), p.getDouble(4), 38 | p.getDouble(5)); 39 | } else if (p.properties[0] == 'Intensity') { 40 | intensity.value = p.getDouble(3); 41 | } else if (p.properties[0] == 'Cone angle') { 42 | coneAngle.value = p.getDouble(3); 43 | } else if (p.properties[0] == 'LightType') { 44 | lightType.value = p.getInt(3); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_mapping_mode.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | enum FbxMappingMode { 4 | /// The mapping is undetermined. 5 | None, 6 | /// There will be one mapping coordinate for each surface control point/vertex. 7 | ByControlPoint, 8 | /// There will be one mapping coordinate for each vertex, for every polygon 9 | /// of which it is a part. This means that a vertex will have as many mapping 10 | /// coordinates as polygons of which it is a part. 11 | ByPolygonVertex, 12 | /// There can be only one mapping coordinate for the whole polygon. 13 | ByPolygon, 14 | /// There will be one mapping coordinate for each unique edge in the mesh. 15 | /// This is meant to be used with smoothing layer elements. 16 | ByEdge, 17 | /// There can be only one mapping coordinate for the whole surface. 18 | AllSame 19 | } 20 | 21 | FbxMappingMode stringToMappingMode(String id) { 22 | id = id.toLowerCase(); 23 | if (id == 'bycontrolpoint' || id == 'byvertex' || id == 'byvertice') { 24 | return FbxMappingMode.ByControlPoint; 25 | } else if (id == 'bypolygonvertex') { 26 | return FbxMappingMode.ByPolygonVertex; 27 | } else if (id == 'bypolygon') { 28 | return FbxMappingMode.ByPolygon; 29 | } else if (id == 'byedge') { 30 | return FbxMappingMode.ByEdge; 31 | } else if (id == 'allsame') { 32 | return FbxMappingMode.AllSame; 33 | } else { 34 | print('Unhandled Mapping Mode: ${id}'); 35 | } 36 | 37 | return FbxMappingMode.None; 38 | } 39 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_material.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | import 'package:vector_math/vector_math.dart'; 7 | 8 | class FbxMaterial extends FbxObject { 9 | FbxProperty shadingModel; 10 | // lambert 11 | FbxProperty ambientColor; 12 | FbxProperty diffuseColor; 13 | FbxProperty transparencyFactor; 14 | FbxProperty emissive; 15 | FbxProperty ambient; 16 | FbxProperty diffuse; 17 | FbxProperty opacity; 18 | // phong 19 | FbxProperty specular; 20 | FbxProperty specularFactor; 21 | FbxProperty shininess; 22 | FbxProperty reflection; 23 | FbxProperty reflectionFactor; 24 | 25 | FbxMaterial(int id, String name, FbxElement element, FbxScene scene) 26 | : super(id, name, 'Material', element, scene) { 27 | 28 | shadingModel = addProperty('ShadingModel', 'lambert'); 29 | ambientColor = addProperty('AmbientColor', Vector3(0.0, 0.0, 0.0)); 30 | diffuseColor = addProperty('DiffuseColor', Vector3(1.0, 1.0, 1.0)); 31 | transparencyFactor = addProperty('TransparencyFactor', 1.0); 32 | emissive = addProperty('Emissive', Vector3(0.0, 0.0, 0.0)); 33 | ambient = addProperty('Ambient', Vector3(0.0, 0.0, 0.0)); 34 | diffuse = addProperty('Diffuse', Vector3(1.0, 1.0, 1.0)); 35 | opacity = addProperty('Opacity', 1.0); 36 | specular = addProperty('Specular', Vector3(1.0, 1.0, 1.0)); 37 | specularFactor = addProperty('SpecularFactor', 0.0); 38 | shininess = addProperty('Shininess', 1.0); 39 | reflection = addProperty('Reflection', Vector3(1.0, 1.0, 1.0)); 40 | reflectionFactor = addProperty('ReflectionFactor', 0.0); 41 | 42 | for (final c in element.children) { 43 | if (c.id == 'ShadingModel') { 44 | shadingModel.value = c.getString(0); 45 | } else if (c.id == 'Properties70') { 46 | for (final p in c.children) { 47 | if (p.properties[0] == 'AmbientColor') { 48 | ambientColor.value = Vector3(p.getDouble(4), p.getDouble(5), 49 | p.getDouble(6)); 50 | } else if (p.properties[0] == 'DiffuseColor') { 51 | diffuseColor.value = Vector3(p.getDouble(4), p.getDouble(5), 52 | p.getDouble(6)); 53 | } else if (p.properties[0] == 'TransparencyFactor') { 54 | transparencyFactor.value = p.getDouble(4); 55 | } else if (p.properties[0] == 'Emissive') { 56 | emissive.value = Vector3(p.getDouble(4), p.getDouble(5), 57 | p.getDouble(6)); 58 | } else if (p.properties[0] == 'Ambient') { 59 | ambient.value = Vector3(p.getDouble(4), p.getDouble(5), 60 | p.getDouble(6)); 61 | } else if (p.properties[0] == 'Diffuse') { 62 | diffuse.value = Vector3(p.getDouble(4), p.getDouble(5), 63 | p.getDouble(6)); 64 | } else if (p.properties[0] == 'Opacity') { 65 | opacity.value = p.getDouble(4); 66 | } else if (p.properties[0] == 'Specular') { 67 | specular.value = Vector3(p.getDouble(4), p.getDouble(5), 68 | p.getDouble(6)); 69 | } else if (p.properties[0] == 'SpecularFactor') { 70 | specularFactor.value = p.getDouble(4); 71 | } else if (p.properties[0] == 'Shininess') { 72 | shininess.value = p.getDouble(4); 73 | } else if (p.properties[0] == 'Reflection') { 74 | reflection.value = Vector3(p.getDouble(4), p.getDouble(5), 75 | p.getDouble(6)); 76 | } else if (p.properties[0] == 'ReflectionFactor') { 77 | reflectionFactor.value = p.getDouble(4); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_mesh.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import '../matrix_utils.dart'; 4 | import 'fbx_cluster.dart'; 5 | import 'fbx_display_mesh.dart'; 6 | import 'fbx_edge.dart'; 7 | import 'fbx_geometry.dart'; 8 | import 'fbx_layer.dart'; 9 | import 'fbx_layer_element.dart'; 10 | import 'fbx_mapping_mode.dart'; 11 | import 'fbx_node.dart'; 12 | import 'fbx_polygon.dart'; 13 | import 'fbx_pose.dart'; 14 | import 'fbx_reference_mode.dart'; 15 | import 'fbx_scene.dart'; 16 | import 'fbx_skin_deformer.dart'; 17 | import 'package:vector_math/vector_math.dart'; 18 | import 'dart:typed_data'; 19 | 20 | class FbxMesh extends FbxGeometry { 21 | List points; 22 | int polygonVertexCount = 0; 23 | List polygons = []; 24 | List edges = []; 25 | List layers = []; 26 | List display = []; 27 | List clusterMap = []; 28 | 29 | FbxMesh(int id, FbxElement element, FbxScene scene) : super(id, '', 'Mesh', element, scene) { 30 | for (final c in element.children) { 31 | if (c.id == 'Vertices') { 32 | _loadPoints(c); 33 | } else if (c.id == 'PolygonVertexIndex') { 34 | _loadPolygons(c); 35 | } else if (c.id == 'Edges') { 36 | _loadEdges(c); 37 | } else if (c.id == 'LayerElementNormal') { 38 | _loadNormals(c); 39 | } else if (c.id == 'LayerElementUV') { 40 | _loadUvs(c); 41 | } else if (c.id == 'LayerElementTexture') { 42 | _loadTexture(c); 43 | } 44 | } 45 | } 46 | 47 | FbxLayer getLayer(int index) { 48 | while (layers.length <= index) { 49 | layers.add(null); 50 | } 51 | 52 | if (layers[index] == null) { 53 | layers[index] = FbxLayer(); 54 | } 55 | 56 | return layers[index]; 57 | } 58 | 59 | List get skinDeformer { 60 | return findConnectionsByType('Skin') as List; 61 | } 62 | 63 | List _getClusters() { 64 | final clusters = []; 65 | final skins = findConnectionsByType('Skin'); 66 | for (final skin in skins) { 67 | final l = []; 68 | skin.findConnectionsByType('Cluster', l); 69 | for (var c in l) { 70 | if (c.indexes != null && c.weights != null) { 71 | clusters.add(c); 72 | } 73 | } 74 | } 75 | return clusters; 76 | } 77 | 78 | bool hasDeformedPoints() => _deformedPoints != null; 79 | 80 | List get deformedPoints { 81 | _deformedPoints ??= List(points.length); 82 | return _deformedPoints; 83 | } 84 | 85 | void computeDeformations() { 86 | final meshNode = getConnectedFrom(0) as FbxNode; 87 | if (meshNode == null) { 88 | return; 89 | } 90 | computeLinearBlendSkinning(meshNode); 91 | updateDisplayMesh(); 92 | } 93 | 94 | void updateDisplayMesh() { 95 | var pts = _deformedPoints ?? points; 96 | if (_deformedPoints[0] == null) { 97 | pts = points; 98 | } 99 | 100 | final disp = display[0]; 101 | for (var pi = 0, len = pts.length; pi < len; ++pi) { 102 | for (var vi = 0; vi < disp.pointMap[pi].length; ++vi) { 103 | final dpi = disp.pointMap[pi][vi]; 104 | disp.points[dpi] = pts[pi].x; 105 | disp.points[dpi + 1] = pts[pi].y; 106 | disp.points[dpi + 2] = pts[pi].z; 107 | } 108 | } 109 | } 110 | 111 | Float32List computeSkinPalette([Float32List data]) { 112 | final meshNode = getConnectedFrom(0) as FbxNode; 113 | if (meshNode == null) { 114 | return null; 115 | } 116 | 117 | data ??= Float32List(_clusters.length * 16); 118 | 119 | final pose = scene.getPose(0); 120 | 121 | for (var i = 0, j = 0, len = _clusters.length; i < len; ++i) { 122 | final cluster = _clusters[i]; 123 | final w = _getClusterMatrix(meshNode, cluster, pose); 124 | for (var k = 0; k < 16; ++k) { 125 | data[j++] = w.storage[k]; 126 | } 127 | } 128 | 129 | return data; 130 | } 131 | 132 | void computeLinearBlendSkinning(FbxNode meshNode) { 133 | final outPoints = deformedPoints; 134 | final pose = scene.getPose(0); 135 | 136 | for (var pi = 0, len = points.length; pi < len; ++pi) { 137 | if (clusterMap[pi] == null || (clusterMap[pi] as List).isEmpty) { 138 | continue; 139 | } 140 | 141 | var sp = Vector3.zero(); 142 | var p = points[pi]; 143 | var weightSum = 0.0; 144 | 145 | var clusterMode = FbxCluster.NORMALIZE; 146 | 147 | for (var clusterWeight in clusterMap[pi]) { 148 | final cluster = clusterWeight[0] as FbxCluster; 149 | final weight = clusterWeight[1] as double; 150 | 151 | clusterMode = cluster.linkMode; 152 | 153 | final w = _getClusterMatrix(meshNode, cluster, pose); 154 | 155 | sp += ((w * p) as Vector3) * weight; 156 | 157 | weightSum += weight; 158 | } 159 | 160 | if (clusterMode == FbxCluster.NORMALIZE) { 161 | if (weightSum != 0.0) { 162 | sp /= weightSum; 163 | } 164 | } else if (clusterMode == FbxCluster.TOTAL_ONE) { 165 | sp += p * (1.0 - weightSum); 166 | } 167 | 168 | outPoints[pi] = sp; 169 | } 170 | } 171 | 172 | Matrix4 _getClusterMatrix(FbxNode meshNode, FbxCluster cluster, FbxPose pose) { 173 | final joint = cluster.getLink(); 174 | 175 | var refGlobalInitPos; 176 | 177 | //TODO BUG1 was 178 | if (pose == null) 179 | refGlobalInitPos = new Matrix4.identity(); 180 | else 181 | refGlobalInitPos = pose.getMatrix(meshNode); 182 | 183 | if (refGlobalInitPos == null) refGlobalInitPos = new Matrix4.identity(); 184 | 185 | final refGlobalCurrentPos = meshNode.evalGlobalTransform(); 186 | 187 | final clusterGlobalInitPos = inverseMat(cluster.transformLink); 188 | 189 | final clusterGlobalCurrentPos = joint.evalGlobalTransform(); 190 | 191 | final clusterRelativeInitPos = (clusterGlobalInitPos * refGlobalInitPos) as Matrix4; 192 | 193 | final clusterRelativeCurrentPosInverse = (inverseMat(refGlobalCurrentPos) * clusterGlobalCurrentPos) as Matrix4; 194 | 195 | final vertexTransform = (clusterRelativeCurrentPosInverse * clusterRelativeInitPos) as Matrix4; 196 | 197 | return vertexTransform; 198 | } 199 | 200 | void generateClusterMap() { 201 | clusterMap = List(points.length); 202 | 203 | for (final cluster in _clusters) { 204 | if (cluster.indexes == null || cluster.weights == null) { 205 | continue; 206 | } 207 | 208 | for (var i = 0; i < cluster.indexes.length; ++i) { 209 | var pi = cluster.indexes[i]; 210 | if (clusterMap[pi] == null) { 211 | clusterMap[pi] = []; 212 | } 213 | clusterMap[pi].add([cluster, cluster.weights[i]]); 214 | } 215 | } 216 | } 217 | 218 | void generateDisplayMeshes() { 219 | display = []; 220 | 221 | if (points == null) { 222 | return; 223 | } 224 | 225 | _clusters = _getClusters(); 226 | generateClusterMap(); 227 | 228 | final disp = FbxDisplayMesh(); 229 | display.add(disp); 230 | 231 | var splitPolygonVerts = false; 232 | 233 | FbxLayer layer; 234 | FbxLayerElement normals; 235 | FbxLayerElement uvs; 236 | 237 | if (layers.isNotEmpty) { 238 | layer = layers[0]; 239 | } 240 | 241 | if (layer != null && layer.hasNormals) { 242 | normals = layer.normals; 243 | if (normals.mappingMode != FbxMappingMode.ByControlPoint) { 244 | splitPolygonVerts = true; 245 | } 246 | } 247 | 248 | if (layer != null && layer.hasUvs) { 249 | uvs = layer.uvs; 250 | if (uvs.mappingMode != FbxMappingMode.ByControlPoint) { 251 | splitPolygonVerts = true; 252 | } 253 | } 254 | 255 | disp.pointMap = List>(points.length); 256 | 257 | if (splitPolygonVerts) { 258 | var triCount = 0; 259 | var numPoints = 0; 260 | for (final poly in polygons) { 261 | triCount += poly.vertices.length - 2; 262 | numPoints += poly.vertices.length; 263 | } 264 | 265 | disp.numPoints = numPoints; 266 | disp.points = Float32List(numPoints * 3); 267 | disp.indices = Uint16List(triCount * 3); 268 | 269 | if (normals != null) { 270 | disp.normals = Float32List(disp.points.length); 271 | } 272 | 273 | if (uvs != null) { 274 | disp.uvs = Float32List(disp.points.length); 275 | } 276 | 277 | var pi = 0; 278 | var ni = 0; 279 | var ni2 = 0; 280 | var ti = 0; 281 | var ti2 = 0; 282 | 283 | for (final poly in polygons) { 284 | for (var vi = 0, len = poly.vertices.length; vi < len; ++vi, ++ni2, ++ti2) { 285 | var p1 = poly.vertices[vi]; 286 | 287 | if (disp.pointMap[p1] == null) { 288 | disp.pointMap[p1] = []; 289 | } 290 | disp.pointMap[p1].add(pi); 291 | 292 | disp.points[pi++] = points[p1].x; 293 | disp.points[pi++] = points[p1].y; 294 | disp.points[pi++] = points[p1].z; 295 | 296 | if (normals != null) { 297 | if (normals.mappingMode == FbxMappingMode.ByControlPoint) { 298 | ni2 = p1; 299 | } 300 | disp.normals[ni++] = normals[ni2].x; 301 | disp.normals[ni++] = normals[ni2].y; 302 | disp.normals[ni++] = normals[ni2].z; 303 | } 304 | 305 | if (uvs != null) { 306 | if (uvs.mappingMode == FbxMappingMode.ByControlPoint) { 307 | ti2 = p1; 308 | } 309 | if (ti2 < uvs.data.length) { 310 | disp.uvs[ti++] = uvs.data[ti2].x; 311 | disp.uvs[ti++] = uvs.data[ti2].y; 312 | } 313 | } 314 | } 315 | } 316 | 317 | pi = 0; 318 | var xi = 0; 319 | for (final poly in polygons) { 320 | for (var vi = 2, len = poly.vertices.length; vi < len; ++vi) { 321 | disp.indices[xi++] = pi; 322 | disp.indices[xi++] = pi + (vi - 1); 323 | disp.indices[xi++] = pi + vi; 324 | } 325 | pi += poly.vertices.length; 326 | } 327 | } else { 328 | disp.numPoints = points.length; 329 | disp.points = Float32List(points.length * 3); 330 | 331 | for (var xi = 0, pi = 0, len = points.length; xi < len; ++xi) { 332 | disp.pointMap[xi] = [pi]; 333 | 334 | disp.points[pi++] = points[xi].x; 335 | disp.points[pi++] = points[xi].y; 336 | disp.points[pi++] = points[xi].z; 337 | } 338 | 339 | if (normals != null) { 340 | disp.normals = Float32List(disp.points.length); 341 | 342 | for (var vi = 0, ni = 0, len = normals.data.length; ni < len; ++ni) { 343 | disp.normals[vi++] = normals[ni].x; 344 | disp.normals[vi++] = normals[ni].y; 345 | disp.normals[vi++] = normals[ni].z; 346 | } 347 | } 348 | 349 | if (uvs != null) { 350 | disp.uvs = Float32List(points.length * 2); 351 | 352 | for (var vi = 0, ni = 0, len = uvs.data.length; ni < len; ++ni) { 353 | disp.uvs[vi++] = uvs[ni].x; 354 | disp.uvs[vi++] = uvs[ni].y; 355 | } 356 | } 357 | 358 | final verts = []; 359 | 360 | for (final poly in polygons) { 361 | for (var vi = 2, len = poly.vertices.length; vi < len; ++vi) { 362 | verts.add(poly.vertices[0]); 363 | verts.add(poly.vertices[vi - 1]); 364 | verts.add(poly.vertices[vi]); 365 | } 366 | } 367 | 368 | disp.indices = Uint16List.fromList(verts); 369 | } 370 | 371 | if (disp.normals == null) { 372 | disp.generateSmoothNormals(); 373 | } 374 | 375 | if (_clusters.isNotEmpty) { 376 | disp.skinWeights = Float32List(disp.numPoints * 4); 377 | disp.skinIndices = Float32List(disp.numPoints * 4); 378 | 379 | final count = Int32List(points.length); 380 | 381 | for (var ci = 0, len = _clusters.length; ci < len; ++ci) { 382 | final index = ci.toDouble(); 383 | 384 | final cluster = _clusters[ci]; 385 | 386 | for (var xi = 0, numPts = cluster.indexes.length; xi < numPts; ++xi) { 387 | final weight = cluster.weights[xi]; 388 | final pi = cluster.indexes[xi]; 389 | 390 | if (disp.pointMap[pi] != null) { 391 | for (var vi = 0, nv = disp.pointMap[pi].length; vi < nv; ++vi) { 392 | final pv = (disp.pointMap[pi][vi] ~/ 3) * 4; 393 | 394 | if (count[pi] > 3) { 395 | for (var cc = 0; cc < 4; ++cc) { 396 | if (disp.skinWeights[pv + cc] < weight) { 397 | disp.skinIndices[pv + cc] = index; 398 | disp.skinWeights[pv + cc] = weight; 399 | break; 400 | } 401 | } 402 | } else { 403 | final wi = pv + count[pi]; 404 | disp.skinIndices[wi] = index; 405 | disp.skinWeights[wi] = weight; 406 | } 407 | } 408 | } 409 | 410 | count[pi]++; 411 | } 412 | } 413 | 414 | //TODO disp uvs-en atmegyunk , rendezzuk index szerint 415 | Float32List newUvs = Float32List(disp.uvs.length); 416 | int j = 0; 417 | for (int index = 0; index < uvs.indexArray.length; index++) { 418 | final uvIndex = uvs.indexArray[index]; 419 | final temp = uvs.data[uvIndex]; 420 | newUvs[j++] = temp.x; 421 | newUvs[j++] = temp.y; 422 | } 423 | disp.uvs = newUvs; 424 | } 425 | } 426 | 427 | void _loadPoints(FbxElement e) { 428 | var p = ((e.properties.length == 1 && e.properties[0] is List) ? e.properties[0] : (e.children.length == 1) ? e.children[0].properties : e.properties) as List; 429 | 430 | points = List(p.length ~/ 3); 431 | 432 | for (var i = 0, j = 0, len = p.length; i < len; i += 3) { 433 | points[j++] = Vector3(toDouble(p[i]), toDouble(p[i + 1]), toDouble(p[i + 2])); 434 | } 435 | } 436 | 437 | void _loadPolygons(FbxElement e) { 438 | var p = ((e.properties.length == 1 && e.properties[0] is List) ? e.properties[0] : (e.children.length == 1) ? e.children[0].properties : e.properties) as List; 439 | 440 | polygonVertexCount = p.length; 441 | 442 | var polygonStart = 0; 443 | 444 | // Triangulate the mesh while we're parsing it. 445 | for (var i = 0, len = p.length; i < len; ++i) { 446 | var vi = toInt(p[i]); 447 | 448 | // negative index indicates the end of a polygon 449 | if (vi < 0) { 450 | vi = ~vi; 451 | 452 | final poly = FbxPolygon(); 453 | polygons.add(poly); 454 | 455 | for (var xi = polygonStart; xi < i; ++xi) { 456 | poly.vertices.add(toInt(p[xi])); 457 | } 458 | poly.vertices.add(vi); 459 | 460 | polygonStart = i + 1; 461 | } 462 | } 463 | } 464 | 465 | void _loadEdges(FbxElement e) { 466 | /*var p = (e.properties.length == 1 && e.properties[0] is List) ? e.properties[0] 467 | : (e.children.length == 1) ? e.children[0].properties 468 | : e.properties; 469 | 470 | for (int ei = 0, len = p.length; ei < len; ei += 2) { 471 | int v1 = toInt(p[ei]); 472 | int v2 = toInt(p[ei + 1]); 473 | edges.add(FbxEdge(v1, v2)); 474 | }*/ 475 | } 476 | 477 | void _loadNormals(FbxElement e) { 478 | final layerIndex = toInt(e.properties[0]); 479 | final layer = getLayer(layerIndex); 480 | 481 | final normals = layer.normals; 482 | 483 | for (final c in e.children) { 484 | if (c.properties.isEmpty) { 485 | continue; 486 | } 487 | 488 | if (c.id == 'MappingInformationType') { 489 | normals.mappingMode = stringToMappingMode(c.properties[0] as String); 490 | } else if (c.id == 'ReferenceInformationType') { 491 | normals.referenceMode = stringToReferenceMode(c.properties[0] as String); 492 | } else if (c.id == 'Normals') { 493 | var p = ((c.properties.length == 1 && c.properties[0] is List) ? c.properties[0] : (c.children.length == 1) ? c.children[0].properties : c.properties) as List; 494 | 495 | normals.data = List(p.length ~/ 3); 496 | for (var i = 0, j = 0, len = p.length; i < len; i += 3) { 497 | normals.data[j++] = Vector3(toDouble(p[i]), toDouble(p[i + 1]), toDouble(p[i + 2])); 498 | } 499 | } 500 | } 501 | } 502 | 503 | void _loadUvs(FbxElement e) { 504 | final layerIndex = toInt(e.properties[0]); 505 | final layer = getLayer(layerIndex); 506 | 507 | final uvs = layer.uvs; 508 | 509 | for (final c in e.children) { 510 | var p = ((c.properties.length == 1 && c.properties[0] is List) ? c.properties[0] : (c.children.length == 1) ? c.children[0].properties : c.properties) as List; 511 | 512 | if (c.id == 'MappingInformationType') { 513 | uvs.mappingMode = stringToMappingMode(p[0] as String); 514 | } else if (c.id == 'ReferenceInformationType') { 515 | uvs.referenceMode = stringToReferenceMode(p[0] as String); 516 | } else if (c.id == 'UV' && p.isNotEmpty) { 517 | uvs.data = List(p.length ~/ 2); 518 | for (var i = 0, j = 0, len = p.length; i < len; i += 2) { 519 | uvs.data[j++] = Vector2(toDouble(p[i]), toDouble(p[i + 1])); 520 | } 521 | } 522 | //TODO UVIndex 523 | else if (c.id == 'UVIndex' && p.isNotEmpty) { 524 | uvs.indexArray = List(p.length); 525 | for (var i = 0, j = 0, len = p.length; i < len; i++) { 526 | uvs.indexArray[j++] = toInt(p[i]); 527 | } 528 | } 529 | } 530 | } 531 | 532 | void _loadTexture(FbxElement e) {} 533 | 534 | List _deformedPoints; 535 | List _clusters = []; 536 | } 537 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_node.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import '../matrix_utils.dart'; 4 | import 'fbx_object.dart'; 5 | import 'fbx_property.dart'; 6 | import 'fbx_scene.dart'; 7 | import 'package:vector_math/vector_math.dart'; 8 | 9 | 10 | class FbxNode extends FbxObject { 11 | FbxProperty translate; 12 | FbxProperty rotate; 13 | FbxProperty scale; 14 | FbxProperty visibility; 15 | Matrix4 transform; 16 | double evalTime; 17 | FbxNode parent; 18 | List children = []; 19 | 20 | FbxNode(int id, String name, String type, FbxElement element, FbxScene scene) 21 | : super(id, name, type, element, scene) { 22 | translate = addProperty('Lcl Translation', Vector3(0.0, 0.0, 0.0)); 23 | rotate = addProperty('Lcl Rotation', Vector3(0.0, 0.0, 0.0)); 24 | scale = addProperty('Lcl Scaling', Vector3(1.0, 1.0, 1.0)); 25 | visibility = addProperty('Visibility', 1); 26 | 27 | for (final c in element.children) { 28 | if (c.id == 'Properties70') { 29 | for (final p in c.children) { 30 | if (p.id == 'P') { 31 | if (p.properties[0] == 'Lcl Translation') { 32 | translate.value = Vector3(p.getDouble(4), p.getDouble(5), 33 | p.getDouble(6)); 34 | } else if (p.properties[0] == 'Lcl Rotation') { 35 | rotate.value = Vector3(p.getDouble(4), p.getDouble(5), 36 | p.getDouble(6)); 37 | } else if (p.properties[0] == 'Lcl Scaling') { 38 | scale.value = Vector3(p.getDouble(4), p.getDouble(5), 39 | p.getDouble(6)); 40 | } else if (p.properties[0] == 'Visibility') { 41 | visibility.value = p.getInt(4); 42 | } 43 | } 44 | } 45 | } else if (c.id == 'Properties60') { 46 | for (final p in c.children) { 47 | if (p.id == 'Property') { 48 | if (p.properties[0] == 'Lcl Translation') { 49 | translate.value = Vector3(p.getDouble(3), p.getDouble(4), 50 | p.getDouble(5)); 51 | } else if (p.properties[0] == 'Lcl Rotation') { 52 | rotate.value = Vector3(p.getDouble(3), p.getDouble(4), 53 | p.getDouble(5)); 54 | } else if (p.properties[0] == 'Lcl Scaling') { 55 | scale.value = Vector3(p.getDouble(3), p.getDouble(4), 56 | p.getDouble(5)); 57 | } else if (p.properties[0] == 'Visibility') { 58 | visibility.value = p.getInt(3); 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | resetLocalTransform(); 66 | } 67 | 68 | @override 69 | void connectTo(FbxObject other) { 70 | if (other is FbxNode) { 71 | final node = other; 72 | children.add(node); 73 | node.parent = this; 74 | } else { 75 | super.connectTo(other); 76 | } 77 | } 78 | 79 | Matrix4 localTransform() => transform; 80 | 81 | Matrix4 globalTransform() { 82 | if (parent != null) { 83 | return (parent.globalTransform() * localTransform()) as Matrix4; 84 | } 85 | return localTransform(); 86 | } 87 | 88 | Matrix4 evalLocalTransform() => scene.getNodeLocalTransform(this); 89 | 90 | Matrix4 evalGlobalTransform() => scene.getNodeGlobalTransform(this); 91 | 92 | void resetLocalTransform() { 93 | transform = Matrix4.identity(); 94 | 95 | final t = translate.value as Vector3; 96 | final r = rotate.value as Vector3; 97 | final s = scale.value as Vector3; 98 | transform.translate(t.x, t.y, t.z); 99 | transform.rotateZ(radians(r.z)); 100 | rotateY(transform, radians(r.y)); 101 | transform.rotateX(radians(r.x)); 102 | transform.scale(s.x, s.y, s.z); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_node_attribute.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxNodeAttribute extends FbxObject { 7 | FbxNodeAttribute(int id, String name, String type, FbxElement element, 8 | FbxScene scene) 9 | : super(id, name, type, element, scene); 10 | } 11 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_null.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node_attribute.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxNull extends FbxNodeAttribute { 7 | FbxNull(int id, String name, FbxElement element, FbxScene scene) 8 | : super(id, name, 'Null', element, scene); 9 | } 10 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_object.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_scene.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_node.dart'; 6 | import 'fbx_node_attribute.dart'; 7 | 8 | class FbxObject { 9 | int id; 10 | String name; 11 | String type; 12 | FbxElement element; 13 | FbxScene scene; 14 | Map properties = {}; 15 | List connectedFrom = []; 16 | List connectedTo = []; 17 | List nodeAttributes = []; 18 | String reference; 19 | 20 | FbxObject(this.id, this.name, this.type, this.element, this.scene); 21 | 22 | FbxNode getParentNode() { 23 | if (this is FbxNode) { 24 | return this as FbxNode; 25 | } 26 | 27 | for (var n in connectedFrom) { 28 | if (n is FbxNode) { 29 | return n; 30 | } 31 | } 32 | 33 | for (var n in connectedFrom) { 34 | final node = n.getParentNode(); 35 | if (node != null) { 36 | return node; 37 | } 38 | } 39 | 40 | return null; 41 | } 42 | 43 | int get numConnectedFrom => connectedFrom.length; 44 | 45 | FbxObject getConnectedFrom(int index) => 46 | (index >= 0 && index < connectedFrom.length) 47 | ? connectedFrom[index] : null; 48 | 49 | int get numConnectedTo => connectedTo.length; 50 | 51 | FbxObject getConnectedTo(int index) => 52 | (index >= 0 && index < connectedTo.length) 53 | ? connectedTo[index] : null; 54 | 55 | List findConnectionsByType(String type, 56 | [List connections]) { 57 | connections ??= []; 58 | 59 | for (final obj in connectedTo) { 60 | if (obj.type == type) { 61 | connections.add(obj); 62 | } 63 | } 64 | 65 | return connections; 66 | } 67 | 68 | void connectTo(FbxObject object) { 69 | connectedTo.add(object); 70 | object.connectedFrom.add(this); 71 | } 72 | 73 | void connectToProperty(String propertyName, FbxObject object) { 74 | final property = addProperty(propertyName); 75 | property.connectedFrom = object; 76 | } 77 | 78 | Iterable propertyNames() => properties.keys; 79 | 80 | bool hasProperty(String name) => properties.containsKey(name); 81 | 82 | FbxProperty addProperty(String name, [dynamic defaultValue]) { 83 | if (!properties.containsKey(name)) { 84 | properties[name] = FbxProperty(defaultValue); 85 | } 86 | return properties[name]; 87 | } 88 | 89 | FbxProperty setProperty(String name, dynamic value) { 90 | if (!properties.containsKey(name)) { 91 | properties[name] = FbxProperty(value); 92 | } else { 93 | properties[name].value = value; 94 | } 95 | return properties[name]; 96 | } 97 | 98 | dynamic getProperty(String name) => 99 | properties.containsKey(name) ? properties[name].value : null; 100 | 101 | @override 102 | String toString() => '$name ($type)'; 103 | 104 | double toDouble(dynamic x) => 105 | x is double ? x 106 | : x is bool ? (x ? 1.0 : 0.0) 107 | : x is String ? double.parse(x) 108 | : (x as num).toDouble(); 109 | 110 | int toInt(dynamic x) => 111 | x is int ? x 112 | : x is bool ? (x ? 1 : 0) 113 | : x is String ? int.parse(x) 114 | : (x as num).toInt(); 115 | } 116 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_polygon.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | 4 | class FbxPolygon { 5 | List vertices = []; 6 | } 7 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_pose.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:typed_data'; 2 | 3 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 4 | import '../fbx_element.dart'; 5 | import 'fbx_node.dart'; 6 | import 'fbx_object.dart'; 7 | import 'fbx_scene.dart'; 8 | import 'package:vector_math/vector_math.dart'; 9 | 10 | class FbxPose extends FbxObject { 11 | Map data = {}; 12 | String poseType = 'BindPose'; 13 | 14 | FbxPose(String name, String type, FbxElement element, FbxScene scene) 15 | : super(0, name, type, element, scene) { 16 | for (final c in element.children) { 17 | if (c.id == 'Type') { 18 | poseType = c.getString(0); 19 | } else if (c.id == 'PoseNode') { 20 | Matrix4 matrix; 21 | String nodeName; 22 | 23 | for (final c2 in c.children) { 24 | if (c2.id == 'Node') { 25 | nodeName = c2.properties[0].toString(); 26 | } else if (c2.id == 'Matrix') { 27 | var p = (c2.properties.length == 16) ? c2.properties 28 | : (c2.children.length == 1 && 29 | c2.children[0].properties.length == 16) ? c2.children[0].properties 30 | : (c2.properties.length == 1 && c2.properties[0] is List) ? c2.properties[0] as List 31 | : null; 32 | if (p != null) { 33 | matrix = Matrix4.zero(); 34 | for (var i = 0; i < 16; ++i) { 35 | matrix.storage[i] = toDouble(p[i]); 36 | } 37 | } 38 | } 39 | } 40 | 41 | if (matrix != null && nodeName != null) { 42 | final node = scene.allObjects[nodeName] as FbxNode; 43 | if (node != null) { 44 | data[node] = matrix; 45 | } else { 46 | print('Could not find pose node: $nodeName'); 47 | } 48 | } else { 49 | print('Invalid PoseNode: $nodeName'); 50 | } 51 | } 52 | } 53 | } 54 | 55 | Matrix4 getMatrix(FbxNode node) { 56 | if (!data.containsKey(node)) { 57 | return null; 58 | } 59 | return data[node]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_property.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_object.dart'; 3 | 4 | class FbxProperty { 5 | dynamic value; 6 | FbxObject connectedFrom; 7 | 8 | FbxProperty(this.value); 9 | 10 | @override 11 | String toString() { 12 | if (connectedFrom != null) { 13 | return '${value} <--- ${connectedFrom.name}<${connectedFrom.type}>'; 14 | } 15 | return value.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_reference_mode.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | 3 | enum FbxReferenceMode { 4 | /// This indicates that the mapping information for the n'th element is found 5 | /// in the n'th place of FbxLayerElementTemplate.directArray. 6 | Direct, 7 | /// This symbol is kept for backward compatibility with FBX v5.0 files. 8 | /// In FBX v6.0 and higher, this symbol is replaced with eIndexToDirect. 9 | Index, 10 | /// This indicates that the FbxLayerElementTemplate.indexArray contains, for 11 | /// the n'th element, an index in the FbxLayerElementTemplate.directArray 12 | /// array of mapping elements. IndexToDirect is usually useful for storing 13 | /// ByPolygonVertex mapping mode elements coordinates. Since the same 14 | /// coordinates are usually repeated many times, this saves spaces by storing 15 | /// the coordinate only one time and then referring to them with an index. 16 | /// Materials and Textures are also referenced with this mode and the actual 17 | /// Material/Texture can be accessed via the FbxLayerElementTemplate.directArray 18 | IndexToDirect 19 | } 20 | 21 | 22 | FbxReferenceMode stringToReferenceMode(String id) { 23 | if (id == 'Direct') { 24 | return FbxReferenceMode.Direct; 25 | } else if (id == 'Index') { 26 | return FbxReferenceMode.Index; 27 | } else if (id == 'IndexToDirect') { 28 | return FbxReferenceMode.IndexToDirect; 29 | } 30 | 31 | return FbxReferenceMode.Direct; 32 | } 33 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_scene.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import 'fbx_frame_rate.dart'; 3 | import 'fbx_node.dart'; 4 | import 'fbx_object.dart'; 5 | import 'fbx_global_settings.dart'; 6 | import 'fbx_camera.dart'; 7 | import 'fbx_light.dart'; 8 | import 'fbx_deformer.dart'; 9 | import 'fbx_material.dart'; 10 | import 'fbx_anim_evaluator.dart'; 11 | import 'fbx_mesh.dart'; 12 | import 'fbx_anim_stack.dart'; 13 | import 'fbx_skeleton.dart'; 14 | import 'fbx_pose.dart'; 15 | import 'fbx_video.dart'; 16 | import 'fbx_texture.dart'; 17 | import 'package:vector_math/vector_math.dart'; 18 | 19 | /// Contains the description of a complete 3D scene. 20 | class FbxScene extends FbxObject { 21 | final header = {}; 22 | 23 | FbxGlobalSettings globalSettings; 24 | FbxObject sceneInfo; 25 | FbxAnimEvaluator evaluator; 26 | List cameras = []; 27 | List lights = []; 28 | List meshes = []; 29 | List deformers = []; 30 | List materials = []; 31 | List animationStack = []; 32 | List skeletonNodes = []; 33 | List poses = []; 34 | List videos = []; 35 | List textures = []; 36 | 37 | List rootNodes = []; 38 | Map allObjects = {}; 39 | 40 | double startFrame = 1.0; 41 | double endFrame = 100.0; 42 | double currentFrame = 1.0; 43 | 44 | FbxScene() 45 | : super(0, '', 'Scene', null, null) { 46 | evaluator = FbxAnimEvaluator(this); 47 | } 48 | 49 | FbxPose getPose(int index) => index < poses.length ? poses[index] : null; 50 | 51 | Matrix4 getNodeLocalTransform(FbxNode node) => 52 | evaluator.getNodeLocalTransform(node, currentFrame); 53 | 54 | Matrix4 getNodeGlobalTransform(FbxNode node) => 55 | evaluator.getNodeGlobalTransform(node, currentFrame); 56 | 57 | int get timeMode { 58 | if (globalSettings != null) { 59 | return globalSettings.timeMode.value as int; 60 | } 61 | return FbxFrameRate.DEFAULT; 62 | } 63 | 64 | double get startTime => FbxFrameRate.frameToSeconds(startFrame, timeMode); 65 | 66 | double get endTime => FbxFrameRate.frameToSeconds(endFrame, timeMode); 67 | 68 | double timeToFrame(int timeValue) { 69 | return FbxFrameRate.timeToFrame(timeValue, timeMode); 70 | } 71 | 72 | double timeToSeconds(int timeValue) { 73 | return FbxFrameRate.timeToSeconds(timeValue, timeMode); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_skeleton.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node.dart'; 4 | import 'fbx_property.dart'; 5 | import 'fbx_scene.dart'; 6 | 7 | class FbxSkeleton extends FbxNode { 8 | static const ROOT = 0; 9 | static const LIMB = 1; 10 | static const LIMB_NODE = 2; 11 | static const EFFECTOR = 3; 12 | 13 | FbxProperty skeletonType; 14 | 15 | FbxSkeleton(int id, String name, String type, FbxElement element, 16 | FbxScene scene) 17 | : super(id, name, type, element, scene) { 18 | skeletonType = setProperty('SkeletonType', LIMB); 19 | 20 | switch (type) { 21 | case 'Root': 22 | skeletonType.value = ROOT; 23 | break; 24 | case 'Limb': 25 | skeletonType.value = LIMB; 26 | break; 27 | case 'LimbNode': 28 | skeletonType.value = LIMB_NODE; 29 | break; 30 | case 'Effector': 31 | skeletonType.value = EFFECTOR; 32 | break; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_skin_deformer.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_deformer.dart'; 4 | import 'fbx_node.dart'; 5 | import 'fbx_property.dart'; 6 | import 'fbx_scene.dart'; 7 | 8 | 9 | class FbxSkinDeformer extends FbxDeformer { 10 | static const int RIGID = 0; 11 | static const int LINEAR = 1; 12 | static const int DUAL_QUATERNION = 2; 13 | static const int BLEND = 3; 14 | 15 | FbxProperty linkDeformAcuracy; 16 | FbxProperty skinningType; 17 | 18 | FbxSkinDeformer(int id, String name, FbxElement element, FbxScene scene) 19 | : super(id, name, 'Skin', element, scene) { 20 | linkDeformAcuracy = addProperty('Link_DeformAcuracy', 50); 21 | skinningType = addProperty('SkinningType', RIGID); 22 | 23 | for (final c in element.children) { 24 | if (c.id == 'Link_DeformAcuracy') { 25 | linkDeformAcuracy.value = toInt(c.properties[0]); 26 | } else if (c.id == 'SkinningType') { 27 | if (c.properties[0] == 'Rigid') { 28 | skinningType.value = RIGID; 29 | } else if (c.properties[0] == 'Linear') { 30 | skinningType.value = LINEAR; 31 | } else if (c.properties[0] == 'DualQuaternion') { 32 | skinningType.value = DUAL_QUATERNION; 33 | } else if (c.properties[0] == 'Blend') { 34 | skinningType.value = BLEND; 35 | } 36 | } 37 | } 38 | } 39 | 40 | List get clusters => connectedTo as List; 41 | } 42 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_texture.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_node.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxTexture extends FbxNode { 7 | String filename; 8 | 9 | FbxTexture(int id, String name, FbxElement element, FbxScene scene) 10 | : super(id, name, 'Texture', element, scene) { 11 | for (final c in element.children) { 12 | if (c.id == 'FileName') { 13 | filename = c.getString(0); 14 | } 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /lib/fbx_parser/fbx/scene/fbx_video.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2015 Brendan Duncan. All rights reserved. 2 | import '../fbx_element.dart'; 3 | import 'fbx_object.dart'; 4 | import 'fbx_scene.dart'; 5 | 6 | class FbxVideo extends FbxObject { 7 | String filename; 8 | int useMipMap; 9 | 10 | FbxVideo(int id, String name, String type, FbxElement element, FbxScene scene) 11 | : super(id, name, type, element, scene) { 12 | for (final c in element.children) { 13 | if (c.id == 'UseMipMap') { 14 | useMipMap = c.getInt(0); 15 | } else if (c.id == 'Filename') { 16 | filename = c.getString(0); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/fbx_viewer/fbx3d_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:core'; 3 | import 'dart:typed_data'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_fbx3d_viewer/fbx_parser/fbx.dart'; 6 | import 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_object.dart'; 7 | 8 | import 'utils/logger.dart'; 9 | 10 | class Fbx3DModel { 11 | FbxScene scene; 12 | List objects = List(); 13 | 14 | Fbx3DModel(); 15 | 16 | _parseFbx(String cont) { 17 | logger("-----_parseFbx START"); 18 | 19 | scene = FbxLoader().load(Uint8List.fromList(utf8.encode(cont))); 20 | 21 | logger("---------scene.meshes ${scene.meshes.length}"); 22 | 23 | for (FbxMesh mesh in scene.meshes) { 24 | FbxNode meshNode = mesh.getParentNode(); 25 | 26 | logger("-----------meshNode $meshNode"); 27 | 28 | if (meshNode == null) { 29 | continue; 30 | } 31 | 32 | mesh.generateDisplayMeshes(); 33 | if (mesh.display.isEmpty) { 34 | continue; 35 | } 36 | 37 | Fbx3DObject object = Fbx3DObject(meshNode, mesh); 38 | object.setPoints(mesh.display[0].points); 39 | object.setNormals(mesh.display[0].normals); 40 | object.setIndices(mesh.display[0].indices); 41 | object.setUvs(mesh.display[0].uvs); 42 | object.setSkinning(mesh.display[0].skinWeights, mesh.display[0].skinIndices); 43 | 44 | object.transform = meshNode.evalGlobalTransform(); 45 | 46 | logger("-----objects added $object"); 47 | 48 | objects.add(object); 49 | } 50 | 51 | logger("-----_parseFbx END"); 52 | } 53 | 54 | parseFrom(BuildContext context, String contFbx) => _parseFbx(contFbx); 55 | } 56 | -------------------------------------------------------------------------------- /lib/fbx_viewer/fbx3d_object.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:flutter_fbx3d_viewer/fbx_parser/fbx.dart'; 3 | import 'package:vector_math/vector_math.dart'; 4 | 5 | class Fbx3DObject { 6 | FbxNode node; 7 | FbxMesh mesh; 8 | Matrix4 transform; 9 | 10 | Float32List points; 11 | Float32List normals; 12 | Float32List uvs; 13 | Uint16List indices; 14 | 15 | Float32List skinPalette; 16 | Float32List skinWeights; 17 | Float32List skinIndices; 18 | 19 | Fbx3DObject(this.node, this.mesh); 20 | 21 | void update() { 22 | if (node != null) { 23 | transform = node.evalGlobalTransform(); 24 | skinPalette = mesh.computeSkinPalette(skinPalette); 25 | setPoints(mesh.display[0].points); 26 | } 27 | } 28 | 29 | void setPoints(Float32List p) { 30 | if (points == null) { 31 | points = (p); 32 | } 33 | } 34 | 35 | void setNormals(Float32List n) { 36 | if (normals == null) { 37 | normals = (n); 38 | } 39 | } 40 | 41 | void setUvs(Float32List uv) { 42 | if (uv == null) { 43 | return; 44 | } 45 | 46 | if (uvs == null) { 47 | uvs = (uv); 48 | } 49 | } 50 | 51 | void setIndices(Uint16List i) { 52 | if (indices == null) { 53 | indices = (i); 54 | } 55 | } 56 | 57 | void setSkinning(Float32List weights, Float32List indices) { 58 | if (skinWeights == null) { 59 | skinWeights = (weights); 60 | skinIndices = (indices); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/fbx_viewer/fbx3d_viewer.dart: -------------------------------------------------------------------------------- 1 | export 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_object.dart'; 2 | export 'package:flutter_fbx3d_viewer/fbx_viewer/utils/math_utils.dart'; 3 | export 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_model.dart'; 4 | export 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_viewer.dart'; 5 | export 'package:flutter_fbx3d_viewer/fbx_viewer/utils/logger.dart'; 6 | export 'package:flutter_fbx3d_viewer/fbx_viewer/utils/screen_utils.dart'; 7 | export 'package:flutter_fbx3d_viewer/fbx_viewer/widgets/zoom_gesture_detector.dart'; 8 | -------------------------------------------------------------------------------- /lib/fbx_viewer/flutter_fbx3d_viewer.dart: -------------------------------------------------------------------------------- 1 | library flutter_fbx3d_viewer; 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'dart:math'; 6 | import 'dart:typed_data'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_model.dart'; 10 | import 'package:flutter_fbx3d_viewer/fbx_viewer/fbx3d_object.dart'; 11 | import 'package:flutter_fbx3d_viewer/fbx_viewer/painter/globals.dart'; 12 | import 'package:flutter_fbx3d_viewer/fbx_viewer/painter/texture_data.dart'; 13 | import 'package:flutter_fbx3d_viewer/fbx_viewer/painter/vertices_painter.dart'; 14 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/converter.dart'; 15 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/logger.dart'; 16 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/math_utils.dart'; 17 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/screen_utils.dart'; 18 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/utils.dart'; 19 | import 'package:flutter_fbx3d_viewer/fbx_viewer/widgets/zoom_gesture_detector.dart'; 20 | import 'package:vector_math/vector_math.dart' as Math; 21 | 22 | import 'fbx3d_viewer.dart'; 23 | 24 | /// Created by Kozári László 2020.01.16 25 | /// This is a simple non textured Fbx animated 3d file viewer. 26 | /// 27 | 28 | class Fbx3DViewer extends StatefulWidget { 29 | final Size size; 30 | final String fbxPath; 31 | final bool showInfo; 32 | bool showWireframe; 33 | final Color wireframeColor; 34 | Math.Vector3 initialAngles; 35 | double initialZoom; 36 | final double animationSpeed; 37 | final Fbx3DViewerController fbx3DViewerController; 38 | int panDistanceToActivate = 10; 39 | final Function(double) onZoomChangeListener; 40 | final Function(Math.Vector3) onRotationChangeListener; 41 | final void Function(double dx) onHorizontalDragUpdate; 42 | final void Function(double dy) onVerticalDragUpdate; 43 | final int refreshMilliseconds; 44 | final int endFrame; 45 | Color color; 46 | final String texturePath; 47 | Math.Vector3 lightPosition; 48 | Color lightColor; 49 | final bool showWireFrame; 50 | final Color backgroundColor; 51 | final bool showGrids; 52 | final Color gridsColor; 53 | final int gridsMaxTile; 54 | final double gridsTileSize; 55 | 56 | currentState() => fbx3DViewerController.state; 57 | 58 | Fbx3DViewer({ 59 | @required this.size, 60 | @required this.fbxPath, 61 | @required this.lightPosition, 62 | @required this.initialZoom, 63 | @required this.animationSpeed, 64 | @required this.fbx3DViewerController, 65 | @required this.refreshMilliseconds, 66 | @required this.endFrame, 67 | this.texturePath, 68 | this.backgroundColor = const Color(0xff353535), 69 | this.showInfo = false, 70 | this.showWireframe = false, 71 | this.wireframeColor = Colors.black, 72 | this.initialAngles, 73 | this.panDistanceToActivate = 10, 74 | this.onZoomChangeListener, 75 | this.onRotationChangeListener, 76 | this.onHorizontalDragUpdate, 77 | this.onVerticalDragUpdate, 78 | this.color = Colors.white, 79 | this.lightColor = Colors.white, 80 | this.showWireFrame = true, 81 | this.showGrids = true, 82 | this.gridsColor = const Color(0xff4b4b4b), 83 | this.gridsMaxTile = 10, 84 | this.gridsTileSize = 1.0, 85 | }); 86 | 87 | @override 88 | _Fbx3DViewerState createState() => fbx3DViewerController.state; 89 | } 90 | 91 | class Fbx3DViewerController extends StatefulWidget { 92 | final _Fbx3DViewerState state = _Fbx3DViewerState(); 93 | 94 | reload() async => await state.reload(); 95 | 96 | rotateX(v) => state.rotateX(v); 97 | 98 | rotateY(v) => state.rotateY(v); 99 | 100 | rotateZ(v) => state.rotateZ(v); 101 | 102 | reset() => state.reset(); 103 | 104 | refresh() { 105 | if (state.fbx3DRenderer != null) state.fbx3DRenderer.refresh(); 106 | } 107 | 108 | @override 109 | State createState() => state; 110 | 111 | setLightPosition(Math.Vector3 lightPosition) => state.setLightPosition(lightPosition); 112 | 113 | showWireframe(bool showWireframe) => state.showWireframe(showWireframe); 114 | 115 | getWidget() => state.widget; 116 | 117 | setRandomColors(Color color, Color lightColor) => state.setRandomColors(color, lightColor); 118 | 119 | setColor(Color color) => state.setColor(color); 120 | 121 | setLightColor(Color color) => state.setLightColor(color); 122 | } 123 | 124 | class _Fbx3DViewerState extends State { 125 | double angleX = 0.0; 126 | double angleY = 0.0; 127 | double angleZ = 0.0; 128 | double previousZoom; 129 | Offset startingFocalPoint; 130 | Offset previousOffset; 131 | Offset offset = Offset.zero; 132 | Fbx3DModel model; 133 | bool isLoading = false; 134 | TextureData textureData; 135 | var rotation = Math.Vector3(0, 0, 0); 136 | double zoom; 137 | Fbx3DRenderer fbx3DRenderer; 138 | 139 | initState() { 140 | super.initState(); 141 | _init(); 142 | } 143 | 144 | reload() async => await _parse(); 145 | 146 | reset() async => fbx3DRenderer = null; 147 | 148 | _init() async => _parse(); 149 | 150 | _parse() async { 151 | logger("___PARSE ${widget.fbxPath}"); 152 | 153 | setState(() => isLoading = true); 154 | 155 | fbx3DRenderer = null; 156 | zoom = widget.initialZoom; 157 | 158 | if (!widget.fbxPath.startsWith("assets/")) { 159 | var pathFbx = widget.fbxPath; 160 | var fileFbx = File(pathFbx); 161 | 162 | if (await fileFbx.exists()) { 163 | var contFbx = await fileFbx.readAsString(); 164 | _newModel(contFbx); 165 | } 166 | } else { 167 | final cont = await rootBundle.loadString(widget.fbxPath); 168 | _newModel(cont); 169 | } 170 | 171 | if (widget.texturePath != null) { 172 | textureData = TextureData(); 173 | await textureData.load(context, widget.texturePath, resizeWidth: 200); 174 | } 175 | logger("load ${widget.texturePath}"); 176 | 177 | paintRasterizer.shader = null; 178 | 179 | setState(() => isLoading = false); 180 | } 181 | 182 | _newModel(String contFbx) { 183 | model = Fbx3DModel(); 184 | model.parseFrom(context, contFbx); 185 | 186 | if (widget.initialAngles != null) { 187 | setRotation(widget.initialAngles); 188 | } 189 | } 190 | 191 | setRotation(Math.Vector3 r) { 192 | angleX = r.x; 193 | angleY = r.y; 194 | angleZ = r.z; 195 | _rotationChanged(); 196 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 197 | } 198 | 199 | @override 200 | void dispose() { 201 | super.dispose(); 202 | } 203 | 204 | rotateX(double v) { 205 | angleX += v; 206 | if (angleX > 360) 207 | angleX = angleX - 360; 208 | else if (angleX < 0) angleX = 360 - angleX; 209 | _rotationChanged(); 210 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 211 | } 212 | 213 | rotateY(double v) { 214 | angleY += v; 215 | if (angleY > 360) 216 | angleY = angleY - 360; 217 | else if (angleY < 0) angleY = 360 - angleY; 218 | _rotationChanged(); 219 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 220 | } 221 | 222 | rotateZ(double v) { 223 | angleZ += v; 224 | if (angleZ > 360) 225 | angleZ = angleZ - 360; 226 | else if (angleZ < 0) angleZ = 360 - angleZ; 227 | _rotationChanged(); 228 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 229 | } 230 | 231 | _rotationChanged() { 232 | rotation.setValues(angleX, angleY, angleZ); 233 | if (widget.onRotationChangeListener != null) widget.onRotationChangeListener(rotation); 234 | } 235 | 236 | _handleScaleStart(initialFocusPoint) { 237 | startingFocalPoint = initialFocusPoint; 238 | previousOffset = offset; 239 | previousZoom = zoom; 240 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 241 | } 242 | 243 | _handleScaleUpdate(changedFocusPoint, scale) { 244 | zoom = previousZoom * scale; 245 | final Offset normalizedOffset = (startingFocalPoint - previousOffset) / previousZoom; 246 | offset = changedFocusPoint - normalizedOffset * zoom; 247 | if (widget.onZoomChangeListener != null) widget.onZoomChangeListener(zoom); 248 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 249 | } 250 | 251 | @override 252 | Widget build(BuildContext context) { 253 | if (isLoading) { 254 | return Center(child: CircularProgressIndicator(backgroundColor: Colors.black)); 255 | } else { 256 | if (fbx3DRenderer == null) { 257 | fbx3DRenderer = Fbx3DRenderer(widget); 258 | fbx3DRenderer.refresh(); 259 | } 260 | 261 | return ZoomGestureDetector( 262 | child: CustomPaint( 263 | painter: fbx3DRenderer, 264 | size: widget.size, 265 | ), 266 | onScaleStart: (initialFocusPoint) => _handleScaleStart(initialFocusPoint), 267 | onScaleUpdate: (changedFocusPoint, scale) => _handleScaleUpdate(changedFocusPoint, scale), 268 | onHorizontalDragUpdate: (double dx) => widget.onHorizontalDragUpdate(dx), 269 | onVerticalDragUpdate: (double dy) => widget.onVerticalDragUpdate(dy), 270 | panDistanceToActivate: widget.panDistanceToActivate, 271 | ); 272 | } 273 | } 274 | 275 | setLightPosition(Math.Vector3 lightPosition) { 276 | widget.lightPosition = lightPosition; 277 | if (fbx3DRenderer != null) fbx3DRenderer.refresh(); 278 | } 279 | 280 | showWireframe(bool showWireframe) { 281 | widget.showWireframe = showWireframe; 282 | 283 | if (fbx3DRenderer != null) { 284 | fbx3DRenderer.reset(); 285 | Future.delayed(Duration(milliseconds: 100), () { 286 | fbx3DRenderer.refresh(); 287 | }); 288 | } 289 | } 290 | 291 | setRandomColors(Color color, Color lightColor) { 292 | widget.color = color; 293 | widget.lightColor = lightColor; 294 | 295 | if (fbx3DRenderer != null) { 296 | fbx3DRenderer.reset(); 297 | Future.delayed(Duration(milliseconds: 100), () => fbx3DRenderer.refresh()); 298 | } 299 | } 300 | 301 | setColor(Color color) { 302 | widget.color = color; 303 | 304 | if (fbx3DRenderer != null) { 305 | fbx3DRenderer.reset(); 306 | Future.delayed(Duration(milliseconds: 100), () => fbx3DRenderer.refresh()); 307 | } 308 | } 309 | 310 | setLightColor(Color color) { 311 | widget.lightColor = color; 312 | 313 | if (fbx3DRenderer != null) { 314 | fbx3DRenderer.reset(); 315 | Future.delayed(Duration(milliseconds: 100), () => fbx3DRenderer.refresh()); 316 | } 317 | } 318 | } 319 | 320 | class Fbx3DRenderer extends ChangeNotifier implements CustomPainter { 321 | Paint paintFill = Paint(); 322 | Paint paintWireframe = Paint(); 323 | Paint paintGrids = Paint(); 324 | Paint paintGridsMain = Paint(); 325 | Paint paintBackground = Paint(); 326 | Fbx3DViewer widget; 327 | 328 | Fbx3DRenderer(this.widget) { 329 | paintFill.style = PaintingStyle.fill; 330 | paintWireframe.style = PaintingStyle.stroke; 331 | paintWireframe.color = widget.wireframeColor; 332 | paintGrids.style = PaintingStyle.stroke; 333 | paintGrids.color = widget.gridsColor; 334 | paintGridsMain.style = PaintingStyle.stroke; 335 | paintGridsMain.color = Colors.black; 336 | paintGridsMain.strokeWidth = 1; 337 | paintBackground.color = widget.backgroundColor; 338 | } 339 | 340 | _transformVertex(Math.Vector3 vertex) { 341 | final _viewPortX = (widget.size.width / 2).toDouble(); 342 | final _viewPortY = (widget.size.height / 2).toDouble(); 343 | 344 | final trans = Math.Matrix4.translationValues(_viewPortX, _viewPortY, 1); 345 | trans.scale(widget.currentState().zoom, -widget.currentState().zoom); 346 | trans.rotateX(MathUtils.degreeToRadian(widget.currentState().angleX)); 347 | trans.rotateY(MathUtils.degreeToRadian(widget.currentState().angleY)); 348 | trans.rotateZ(MathUtils.degreeToRadian(widget.currentState().angleZ)); 349 | return trans.transform3(vertex); 350 | } 351 | 352 | _drawTriangle( 353 | Canvas canvas, Math.Vector3 v1, Math.Vector3 v2, Math.Vector3 v3, Math.Vector2 uv1, Math.Vector2 uv2, Math.Vector2 uv3, Math.Vector3 n1, Math.Vector3 n2, Math.Vector3 n3) { 354 | final path = Path(); 355 | path.moveTo(v1.x, v1.y); 356 | path.lineTo(v2.x, v2.y); 357 | path.lineTo(v3.x, v3.y); 358 | path.lineTo(v1.x, v1.y); 359 | path.close(); 360 | 361 | final color = widget.color; 362 | final lightPosition = widget.lightPosition; 363 | final lightColor = widget.lightColor; 364 | 365 | /* 366 | final normalVector = MathUtils.normalVector3(v1, v2, v3); 367 | Math.Vector3 normalizedLight = Math.Vector3.copy(lightPosition).normalized(); 368 | final jnv = Math.Vector3.copy(normalVector).normalized(); 369 | final normal = MathUtils.scalarMultiplication(jnv, normalizedLight); 370 | final brightness = normal.clamp(0.1, 1.0); 371 | 372 | if (widget.drawMode == DrawMode.WIREFRAME) { 373 | canvas.drawPath(path, paintWireframeBlue); 374 | } else if (widget.drawMode == DrawMode.SHADED) { 375 | final shade = Color.lerp(color, lightColor, brightness); 376 | paintFill.color = shade; 377 | canvas.drawPath(path, paintFill); 378 | } else if (widget.drawMode == DrawMode.TEXTURED) { 379 | if (widget.rasterizerMethod == RasterizerMethod.OldMethod) 380 | drawTexturedTrianglePoints(canvas, depthBuffer, v1, v2, v3, uv1, uv2, uv3, n1, n2, n3, color, brightness, 381 | widget.currentState().textureData, lightPosition); 382 | else if (widget.rasterizerMethod == RasterizerMethod.NewMethod)*/ 383 | drawTexturedTriangleVertices(canvas, v1, v2, v3, uv1, uv2, uv3, n1, n2, n3, color, widget.currentState().textureData, lightPosition, lightColor); 384 | //} 385 | 386 | if (widget.showWireframe ?? false == true) { 387 | canvas.drawPath(path, paintWireframe); 388 | } 389 | } 390 | 391 | _drawGrids(Canvas canvas) { 392 | final steps = widget.gridsTileSize; 393 | final distance = (widget.gridsMaxTile * steps).toInt(); 394 | 395 | for (int i = -distance ~/ steps; i <= distance ~/ steps; i++) { 396 | final p1 = gen2DPointFrom3D(_transformVertex(Math.Vector3(-distance.toDouble(), 0, -i * steps.toDouble()))); 397 | final p2 = gen2DPointFrom3D(_transformVertex(Math.Vector3(distance.toDouble(), 0, -i * steps.toDouble()))); 398 | canvas.drawLine(p1, p2, paintGrids); 399 | 400 | final p3 = gen2DPointFrom3D(_transformVertex(Math.Vector3(-distance.toDouble(), 0, i * steps.toDouble()))); 401 | final p4 = gen2DPointFrom3D(_transformVertex(Math.Vector3(distance.toDouble(), 0, i * steps.toDouble()))); 402 | canvas.drawLine(p3, p4, paintGrids); 403 | 404 | if (i == 0) { 405 | canvas.drawLine(p1, p2, paintGridsMain); 406 | } 407 | } 408 | 409 | for (int i = -distance ~/ steps; i <= distance ~/ steps; i++) { 410 | final p1 = gen2DPointFrom3D(_transformVertex(Math.Vector3(-i * steps.toDouble(), 0, -distance.toDouble()))); 411 | final p2 = gen2DPointFrom3D(_transformVertex(Math.Vector3(-i * steps.toDouble(), 0, distance.toDouble()))); 412 | canvas.drawLine(p1, p2, paintGrids); 413 | 414 | final p3 = gen2DPointFrom3D(_transformVertex(Math.Vector3(i * steps.toDouble(), 0, -distance.toDouble()))); 415 | final p4 = gen2DPointFrom3D(_transformVertex(Math.Vector3(i * steps.toDouble(), 0, distance.toDouble()))); 416 | canvas.drawLine(p3, p4, paintGrids); 417 | 418 | if (i == 0) { 419 | canvas.drawLine(p1, p2, paintGridsMain); 420 | } 421 | } 422 | } 423 | 424 | @override 425 | void paint(Canvas canvas, Size size) { 426 | canvas.drawPaint(paintBackground); 427 | 428 | if (widget.showGrids) { 429 | _drawGrids(canvas); 430 | } 431 | 432 | final model = widget.currentState().model; 433 | 434 | if (model == null) return; 435 | 436 | if (model.scene != null) { 437 | model.scene.currentFrame += widget.animationSpeed; 438 | if (model.scene.currentFrame >= widget.endFrame) { 439 | model.scene.currentFrame = model.scene.startFrame; 440 | } 441 | } 442 | 443 | if (model.objects.length == 0) { 444 | final sHead = "${widget.fbxPath}"; 445 | final sDesc = "Null objects found!"; 446 | drawErrorText(canvas, sHead, sDesc); 447 | return; 448 | } 449 | 450 | int vCount = 0; 451 | int verticesCount = 0; 452 | int triangleCount = 0; 453 | 454 | for (int i = 0; i < model.objects.length; i++) { 455 | Fbx3DObject obj = model.objects[i]; 456 | obj.update(); 457 | vCount += obj.points.length ~/ 3; 458 | } 459 | if (vCount > MAX_SUPPORTED_VERTICES) { 460 | final sHead = "${widget.fbxPath}"; 461 | final sDesc = "Too much points: $vCount! Max supported points: $MAX_SUPPORTED_VERTICES!"; 462 | drawErrorText(canvas, sHead, sDesc); 463 | return; 464 | } 465 | 466 | for (int i = 0; i < model.objects.length; i++) { 467 | final obj = model.objects[i]; 468 | 469 | if (obj.skinIndices == null) { 470 | final sHead = "${widget.fbxPath}"; 471 | final sDesc = "SkinIndices not set!"; 472 | drawErrorText(canvas, sHead, sDesc); 473 | return; 474 | } 475 | if (obj.skinWeights == null) { 476 | final sHead = "${widget.fbxPath}"; 477 | final sDesc = "SkinWeights not set!"; 478 | drawErrorText(canvas, sHead, sDesc); 479 | return; 480 | } 481 | 482 | final oPoints = obj.points; 483 | final oIndices = obj.indices; 484 | final oNormals = obj.normals; 485 | final oUVs = obj.uvs; 486 | final sortedItems = List>(); 487 | 488 | final oJointMatrix = listMatrixFromFloat32List(obj.skinPalette); 489 | final oSkinIndices = listVector4FromFloat32List(obj.skinIndices); 490 | final oSkinWeights = listVector4FromFloat32List(obj.skinWeights); 491 | 492 | //1 a pontokbol keszitek egy Vec3 listet 493 | final List tempVertices = List(); 494 | final List tempNormals = List(); 495 | final List tempUVs = List(); 496 | 497 | for (int index = 0; index < oPoints.length; index += 3) { 498 | final p1 = oPoints[index]; 499 | final p2 = oPoints[index + 1]; 500 | final p3 = oPoints[index + 2]; 501 | final v = Math.Vector3(p1, p2, p3); 502 | tempVertices.add(v); 503 | 504 | final n1 = oNormals[index]; 505 | final n2 = oNormals[index + 1]; 506 | final n3 = oNormals[index + 2]; 507 | final n = Math.Vector3(n1, n2, n3); 508 | tempNormals.add(n); 509 | 510 | final uv1 = oUVs[index]; 511 | final uv2 = oUVs[index + 1]; 512 | final uv3 = oUVs[index + 2]; 513 | final uv = Math.Vector3(uv1, uv2, uv3); 514 | tempUVs.add(uv); 515 | } 516 | 517 | //get the _getMaxWeightsPerVertex() 518 | _getMaxWeightsPerVertex(tempVertices, oSkinWeights); 519 | 520 | final List bonedVertices = List(); 521 | final List tempNormals2 = List(); 522 | 523 | for (int index = 0; index < tempVertices.length; index++) { 524 | final skinIndexX = oSkinIndices[index].x; 525 | final skinIndexY = oSkinIndices[index].y; 526 | final skinIndexZ = oSkinIndices[index].z; 527 | final skinIndexW = oSkinIndices[index].w; 528 | final skinWeightX = oSkinWeights[index].x; 529 | final skinWeightY = oSkinWeights[index].y; 530 | final skinWeightZ = oSkinWeights[index].z; 531 | final skinWeightW = oSkinWeights[index].w; 532 | 533 | final bv = 534 | _Fbx3DBones.calculateBoneVertex(tempVertices[index], skinIndexX, skinIndexY, skinIndexZ, skinIndexW, skinWeightX, skinWeightY, skinWeightZ, skinWeightW, oJointMatrix); 535 | bonedVertices.add(bv); 536 | 537 | final bn = 538 | _Fbx3DBones.calculateBoneNormal(tempNormals[index], skinIndexX, skinIndexY, skinIndexZ, skinIndexW, skinWeightX, skinWeightY, skinWeightZ, skinWeightW, oJointMatrix); 539 | tempNormals2.add(bn); 540 | } 541 | 542 | verticesCount += bonedVertices.length; 543 | 544 | final List newPoints = List(); 545 | final List newNormals = List(); 546 | final List newUVs = List(); 547 | 548 | for (int index = 0; index < bonedVertices.length; index++) { 549 | newPoints.add(bonedVertices[index].x); 550 | newPoints.add(bonedVertices[index].y); 551 | newPoints.add(bonedVertices[index].z); 552 | 553 | newNormals.add(tempNormals2[index].x); 554 | newNormals.add(tempNormals2[index].y); 555 | newNormals.add(tempNormals2[index].z); 556 | 557 | newUVs.add(tempUVs[index].x); 558 | newUVs.add(tempUVs[index].y); 559 | newUVs.add(tempUVs[index].z); 560 | } 561 | 562 | final Float32List nPoints = Float32List.fromList(newPoints); 563 | final Float32List nNormals = Float32List.fromList(newNormals); 564 | final Float32List nUVs = Float32List.fromList(newUVs); 565 | 566 | final List vertices = List(); 567 | final List normals = List(); 568 | final List uvs = List(); 569 | 570 | for (int index = 0; index < oIndices.length; index += 3) { 571 | Math.Vector3 v1 = _transformVertex(Math.Vector3(nPoints[oIndices[index] * 3], nPoints[oIndices[index] * 3 + 1], nPoints[oIndices[index] * 3 + 2])); 572 | Math.Vector3 v2 = _transformVertex(Math.Vector3(nPoints[oIndices[index + 1] * 3], nPoints[oIndices[index + 1] * 3 + 1], nPoints[oIndices[index + 1] * 3 + 2])); 573 | Math.Vector3 v3 = _transformVertex(Math.Vector3(nPoints[oIndices[index + 2] * 3], nPoints[oIndices[index + 2] * 3 + 1], nPoints[oIndices[index + 2] * 3 + 2])); 574 | vertices.add(v1); 575 | vertices.add(v2); 576 | vertices.add(v3); 577 | 578 | Math.Vector3 n1 = Math.Vector3(nNormals[oIndices[index] * 3], nNormals[oIndices[index] * 3 + 1], nNormals[oIndices[index] * 3 + 2]); 579 | Math.Vector3 n2 = Math.Vector3(nNormals[oIndices[index + 1] * 3], nNormals[oIndices[index + 1] * 3 + 1], nNormals[oIndices[index + 1] * 3 + 2]); 580 | Math.Vector3 n3 = Math.Vector3(nNormals[oIndices[index + 2] * 3], nNormals[oIndices[index + 2] * 3 + 1], nNormals[oIndices[index + 2] * 3 + 2]); 581 | normals.add(n1); 582 | normals.add(n2); 583 | normals.add(n3); 584 | 585 | Math.Vector2 uv1 = Math.Vector2(nUVs[oIndices[index] * 2], nUVs[oIndices[index] * 2 + 1]); 586 | Math.Vector2 uv2 = Math.Vector2(nUVs[oIndices[index + 1] * 2], nUVs[oIndices[index + 1] * 2 + 1]); 587 | Math.Vector2 uv3 = Math.Vector2(nUVs[oIndices[index + 2] * 2], nUVs[oIndices[index + 2] * 2 + 1]); 588 | uvs.add(uv1); 589 | uvs.add(uv2); 590 | uvs.add(uv3); 591 | } 592 | 593 | // painter's algorithm 594 | for (int index = 0; index < vertices.length; index += 3) { 595 | final Math.Vector3 v1 = vertices[index]; 596 | final Math.Vector3 v2 = vertices[index + 1]; 597 | final Math.Vector3 v3 = vertices[index + 2]; 598 | 599 | final Math.Vector3 n1 = normals[index]; 600 | final Math.Vector3 n2 = normals[index + 1]; 601 | final Math.Vector3 n3 = normals[index + 2]; 602 | 603 | final Math.Vector2 uv1 = uvs[index]; 604 | final Math.Vector2 uv2 = uvs[index + 1]; 605 | final Math.Vector2 uv3 = uvs[index + 2]; 606 | 607 | sortedItems.add({ 608 | "order": MathUtils.zIndex(v1, v2, v3), 609 | "v1": v1, 610 | "v2": v2, 611 | "v3": v3, 612 | "uv1": uv1, 613 | "uv2": uv2, 614 | "uv3": uv3, 615 | "n1": n1, 616 | "n2": n2, 617 | "n3": n3, 618 | }); 619 | } 620 | sortedItems.sort((Map a, Map b) => a["order"].compareTo(b["order"])); 621 | 622 | //logger("???? " + sorted.length.toString() + " " + vertices.length.toString()); 623 | 624 | //7 vegul a rendezett vertexeket kirajzolom 3-asaval, triangle-nkent 625 | for (int index = 0; index < sortedItems.length; index++) { 626 | final sorted = sortedItems[index]; 627 | final v1 = Math.Vector3.copy(sorted['v1']); 628 | final v2 = Math.Vector3.copy(sorted['v2']); 629 | final v3 = Math.Vector3.copy(sorted['v3']); 630 | final uv1 = sorted['uv1']; 631 | final uv2 = sorted['uv2']; 632 | final uv3 = sorted['uv3']; 633 | final n1 = sorted['n1']; 634 | final n2 = sorted['n2']; 635 | final n3 = sorted['n3']; 636 | 637 | _drawTriangle(canvas, v1, v2, v3, uv1, uv2, uv3, n1, n2, n3); 638 | 639 | triangleCount++; 640 | } 641 | } 642 | 643 | _drawInfo(canvas, verticesCount, triangleCount); 644 | } 645 | 646 | _drawInfo(Canvas canvas, int verticesCount, int triangleCount) { 647 | if (widget.showInfo) { 648 | final rot = widget.currentState().rotation; 649 | final zoom = widget.currentState().zoom.toStringAsFixed(1); 650 | 651 | drawText(canvas, "vertices: " + verticesCount.toString(), Offset(20, ScreenUtils.height - 80)); 652 | drawText(canvas, "triangles: " + triangleCount.toString(), Offset(20, ScreenUtils.height - 100)); 653 | drawText(canvas, "endFrame: " + widget.endFrame.toString() + " speed: " + widget.animationSpeed.toString(), Offset(20, ScreenUtils.height - 120)); 654 | 655 | drawText(canvas, "zoom: " + zoom + " rot: (" + rot.x.toStringAsFixed(0) + ", " + rot.y.toStringAsFixed(0) + ", " + rot.z.toStringAsFixed(0) + ")", 656 | Offset(20, ScreenUtils.height - 150), 657 | fontSize: 14); 658 | 659 | drawText(canvas, "path: " + widget.fbxPath, Offset(20, ScreenUtils.height - 185), fontSize: 12); 660 | } 661 | } 662 | 663 | @override 664 | bool shouldRepaint(Fbx3DRenderer old) => true; 665 | 666 | refresh() => notifyListeners(); 667 | 668 | reset() { 669 | if (widget.fbx3DViewerController != null) widget.fbx3DViewerController.reset(); 670 | } 671 | 672 | @override 673 | bool hitTest(Offset position) => true; 674 | 675 | @override 676 | bool shouldRebuildSemantics(CustomPainter previous) => false; 677 | 678 | @override 679 | get semanticsBuilder => null; 680 | } 681 | 682 | //from jMonkeyEngine, normalize the weights 683 | _getMaxWeightsPerVertex(List tempVertices, List oSkinWeights) { 684 | int maxWeightsPerVertex = 0; 685 | 686 | for (int index = 0; index < tempVertices.length; index++) { 687 | final w0 = oSkinWeights[index].x; 688 | final w1 = oSkinWeights[index].y; 689 | final w2 = oSkinWeights[index].z; 690 | final w3 = oSkinWeights[index].w; 691 | 692 | if (w3 != 0) { 693 | maxWeightsPerVertex = max(maxWeightsPerVertex, 4); 694 | } else if (w2 != 0) { 695 | maxWeightsPerVertex = max(maxWeightsPerVertex, 3); 696 | } else if (w1 != 0) { 697 | maxWeightsPerVertex = max(maxWeightsPerVertex, 2); 698 | } else if (w0 != 0) { 699 | maxWeightsPerVertex = max(maxWeightsPerVertex, 1); 700 | } 701 | 702 | double sum = w0 + w1 + w2 + w3; 703 | if (sum != 1.0) { 704 | double normalized = (sum != 0) ? (1.0 / sum) : 0.0; 705 | oSkinWeights[index].x = (w0 * normalized); 706 | oSkinWeights[index].y = (w1 * normalized); 707 | oSkinWeights[index].z = (w2 * normalized); 708 | oSkinWeights[index].w = (w3 * normalized); 709 | } 710 | } 711 | return maxWeightsPerVertex; 712 | } 713 | 714 | class _Fbx3DBones { 715 | _Fbx3DBones._(); 716 | 717 | static Math.Vector3 calculateBoneVertex(vertexPosition, skinIndexX, skinIndexY, skinIndexZ, skinIndexW, skinWeightX, skinWeightY, skinWeightZ, skinWeightW, joints) { 718 | Math.Vector4 p = Math.Vector4(vertexPosition.x, vertexPosition.y, vertexPosition.z, 1.0); 719 | 720 | Math.Vector4 sp = Math.Vector4(0.0, 0.0, 0.0, 0.0); 721 | int index = 0; 722 | 723 | index = skinIndexX.toInt(); 724 | sp = (joints[index] * p) * skinWeightX; 725 | 726 | index = skinIndexY.toInt(); 727 | sp += (joints[index] * p) * skinWeightY; 728 | 729 | index = skinIndexZ.toInt(); 730 | sp += (joints[index] * p) * skinWeightZ; 731 | 732 | index = skinIndexW.toInt(); 733 | sp += (joints[index] * p) * skinWeightW; 734 | 735 | return sp.xyz; 736 | } 737 | 738 | static Math.Vector3 calculateBoneNormal(vertexNormal, skinIndexX, skinIndexY, skinIndexZ, skinIndexW, skinWeightX, skinWeightY, skinWeightZ, skinWeightW, joints) { 739 | Math.Vector3 n = Math.Vector3(vertexNormal.x, vertexNormal.y, vertexNormal.z); 740 | 741 | Math.Vector3 sn = Math.Vector3(0.0, 0.0, 0.0); 742 | int index = 0; 743 | 744 | index = skinIndexX.toInt(); 745 | sn = (joints[index] * n) * skinWeightX; 746 | 747 | index = skinIndexY.toInt(); 748 | sn += (joints[index] * n) * skinWeightY; 749 | 750 | index = skinIndexZ.toInt(); 751 | sn += (joints[index] * n) * skinWeightZ; 752 | 753 | index = skinIndexW.toInt(); 754 | sn += (joints[index] * n) * skinWeightW; 755 | 756 | return sn.xyz; 757 | } 758 | } 759 | -------------------------------------------------------------------------------- /lib/fbx_viewer/painter/globals.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | const MAX_SUPPORTED_VERTICES = 16000; 4 | 5 | final Paint paintRasterizer = Paint(); 6 | 7 | -------------------------------------------------------------------------------- /lib/fbx_viewer/painter/texture_data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui' as UI; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/utils.dart'; 8 | 9 | import 'package:image/image.dart' as IMG; 10 | 11 | /// 12 | /// Created by Kozári László in 2020.01.06 13 | /// lostinwar22@gmail.com 14 | /// 15 | 16 | class TextureData { 17 | IMG.Image imageIMG; 18 | UI.Image imageUI; 19 | int width; 20 | int height; 21 | 22 | load(BuildContext context,String path, {int resizeWidth}) async { 23 | ByteData imageData; 24 | 25 | if (path.startsWith("assets/")) 26 | imageData = await rootBundle.load(path); 27 | else { 28 | final fileImg = File(path); 29 | if (await fileImg.exists()) { 30 | imageData = ByteData.view((await fileImg.readAsBytes()).buffer); 31 | } 32 | } 33 | 34 | final buffer = imageData.buffer; 35 | final imageInBytes = buffer.asUint8List(imageData.offsetInBytes, imageData.lengthInBytes); 36 | IMG.Image resized = IMG.copyResize(IMG.decodeImage(imageInBytes), width: resizeWidth); 37 | 38 | imageIMG = resized; 39 | width = imageIMG.width; 40 | height = imageIMG.height; 41 | 42 | imageUI = await ImageLoader.loadImage(context, path); 43 | } 44 | 45 | Color map(double tu, double tv) { 46 | if (imageIMG == null) { 47 | return Colors.white; 48 | } 49 | int u = ((tu * width).toInt() % width).abs(); 50 | int v = ((tv * height).toInt() % height).abs(); 51 | 52 | return Color(convertABGRtoARGB(imageIMG.getPixel(u, v))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/fbx_viewer/painter/vertices_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_fbx3d_viewer/fbx_viewer/painter/texture_data.dart'; 6 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/math_utils.dart'; 7 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/utils.dart'; 8 | import 'package:vector_math/vector_math.dart' as Math; 9 | import 'package:flutter/rendering.dart'; 10 | 11 | import 'globals.dart'; 12 | 13 | //TODO draw in one 14 | drawTexturedTriangleVertices(Canvas canvas, Math.Vector3 v1, Math.Vector3 v2, Math.Vector3 v3, Math.Vector2 uv1, Math.Vector2 uv2, Math.Vector2 uv3, Math.Vector3 n1, Math.Vector3 n2, Math.Vector3 n3, Color color, TextureData textureData, Math.Vector3 lightPosition, Color lightColor) { 15 | VertexMode vertexMode = VertexMode.triangles; 16 | 17 | if (textureData.imageUI == null) return; 18 | 19 | if (paintRasterizer.shader == null) { 20 | final TileMode tmx = TileMode.clamp; 21 | final TileMode tmy = TileMode.clamp; 22 | final Float64List matrix4 = Matrix4.identity().storage; 23 | final ImageShader shader = ImageShader(textureData.imageUI, tmx, tmy, matrix4); 24 | paintRasterizer.shader = shader; 25 | } 26 | 27 | final List vertices = [ 28 | gen2DPointFrom3D(v1), 29 | gen2DPointFrom3D(v2), 30 | gen2DPointFrom3D(v3), 31 | ]; 32 | 33 | final List textureCoordinates = [ 34 | Offset(uv1.x * textureData.imageUI.width, (1 - uv1.y) * textureData.imageUI.height), 35 | Offset(uv2.x * textureData.imageUI.width, (1 - uv2.y) * textureData.imageUI.height), 36 | Offset(uv3.x * textureData.imageUI.width, (1 - uv3.y) * textureData.imageUI.height), 37 | ]; 38 | 39 | double nl1 = _calculateNormal(n1, lightPosition); 40 | double nl2 = _calculateNormal(n2, lightPosition); 41 | double nl3 = _calculateNormal(n3, lightPosition); 42 | 43 | final shade1 = Color.lerp(color, lightColor, nl1); 44 | final shade2 = Color.lerp(color, lightColor, nl2); 45 | final shade3 = Color.lerp(color, lightColor, nl3); 46 | 47 | final List colors = [shade1, shade2, shade3]; 48 | final Vertices _vertices = Vertices(vertexMode, vertices, textureCoordinates: textureCoordinates, colors: colors); 49 | canvas.drawVertices(_vertices, BlendMode.colorBurn, paintRasterizer); 50 | 51 | /* 52 | final shade = Colors.black.withOpacity(brightness); 53 | final List colors = [shade, shade, shade]; 54 | final Vertices _vertices = Vertices(vertexMode, vertices, textureCoordinates: textureCoordinates, colors: colors); 55 | canvas.drawVertices(_vertices, BlendMode.colorBurn, paintRasterizer); 56 | */ 57 | } 58 | 59 | _calculateNormal(Math.Vector3 n, Math.Vector3 lightPosition) { 60 | Math.Vector3 normalizedLight = Math.Vector3.copy(lightPosition).normalized(); 61 | final jnv = Math.Vector3.copy(n).normalized(); 62 | final normal = MathUtils.scalarMultiplication(jnv, normalizedLight); 63 | final brightness = normal.clamp(0.1, 1.0); 64 | return brightness; 65 | } 66 | -------------------------------------------------------------------------------- /lib/fbx_viewer/utils/converter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:vector_math/vector_math.dart'; 3 | 4 | Float32List scalarValues(Float32List list, double v) { 5 | List out = List(); 6 | for (int index = 0; index < list.length; index++) { 7 | out.add(list[index] * v); 8 | } 9 | return Float32List.fromList(out); 10 | } 11 | 12 | List listVector2FromFloat32List(Float32List list) { 13 | List vectors = List(); 14 | for (int index = 0; index < list.length; index += 2) { 15 | var v = Vector2( 16 | list[index], 17 | list[index + 1], 18 | ); 19 | vectors.add(v); 20 | } 21 | return vectors; 22 | } 23 | 24 | List listVector3FromUint16List(Uint16List list) { 25 | List vectors = List(); 26 | for (int index = 0; index < list.length; index += 3) { 27 | var v = Vector3( 28 | list[index].toDouble(), 29 | list[index + 1].toDouble(), 30 | list[index + 2].toDouble(), 31 | ); 32 | vectors.add(v); 33 | } 34 | return vectors; 35 | } 36 | 37 | List listVector3FromFloat32List(Float32List list) { 38 | List vectors = List(); 39 | for (int index = 0; index < list.length; index += 3) { 40 | var v = Vector3( 41 | list[index], 42 | list[index + 1], 43 | list[index + 2], 44 | ); 45 | vectors.add(v); 46 | } 47 | return vectors; 48 | } 49 | 50 | List listVector4FromFloat32List(Float32List list) { 51 | List vectors = List(); 52 | for (int index = 0; index < list.length; index += 4) { 53 | var v = Vector4( 54 | list[index], 55 | list[index + 1], 56 | list[index + 2], 57 | list[index + 3], 58 | ); 59 | vectors.add(v); 60 | } 61 | return vectors; 62 | } 63 | 64 | List listMatrixFromFloat32List(Float32List skinPalette) { 65 | List matrices = List(); 66 | for (int index = 0; index < skinPalette.length; index += 16) { 67 | var m = Matrix4( 68 | skinPalette[index], 69 | skinPalette[index + 1], 70 | skinPalette[index + 2], 71 | skinPalette[index + 3], 72 | skinPalette[index + 4], 73 | skinPalette[index + 5], 74 | skinPalette[index + 6], 75 | skinPalette[index + 7], 76 | skinPalette[index + 8], 77 | skinPalette[index + 9], 78 | skinPalette[index + 10], 79 | skinPalette[index + 11], 80 | skinPalette[index + 12], 81 | skinPalette[index + 13], 82 | skinPalette[index + 14], 83 | skinPalette[index + 15], 84 | ); 85 | matrices.add(m); 86 | } 87 | return matrices; 88 | } 89 | 90 | double getMultiplicationValue(double d) { 91 | String text = d.abs().toString(); 92 | 93 | int integerPlaces = int.parse(text.split(".")[0]); 94 | String decimalPlaces = text.split(".")[1]; 95 | 96 | if (integerPlaces > 1) 97 | return 1.0 / integerPlaces.toDouble(); 98 | else { 99 | double count = 1; 100 | for (int i = 0; i < decimalPlaces.length; i++) { 101 | var char = decimalPlaces[i]; 102 | if (char == '0') 103 | count *= 10; 104 | else 105 | break; 106 | } 107 | return count; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/fbx_viewer/utils/logger.dart: -------------------------------------------------------------------------------- 1 | logger(String s) { 2 | print("-> $s"); 3 | } -------------------------------------------------------------------------------- /lib/fbx_viewer/utils/math_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | import 'dart:math' as Math; 3 | 4 | import 'package:vector_math/vector_math.dart'; 5 | 6 | class MathUtils { 7 | MathUtils._(); 8 | 9 | static Vector3 normalVector3(Vector3 v1, Vector3 v2, Vector3 v3) { 10 | Vector3 s1 = Vector3.copy(v2); 11 | s1.sub(v1); 12 | Vector3 s3 = Vector3.copy(v2); 13 | s3.sub(v3); 14 | 15 | return Vector3( 16 | (s1.y * s3.z) - (s1.z * s3.y), 17 | (s1.z * s3.x) - (s1.x * s3.z), 18 | (s1.x * s3.y) - (s1.y * s3.x), 19 | ); 20 | } 21 | 22 | static double scalarMultiplication(Vector3 v1, Vector3 v2) => (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); 23 | 24 | static double degreeToRadian(double degree) => degree * (Math.pi / 180.0); 25 | 26 | static double zIndex(Vector3 p1, Vector3 p2, Vector3 p3) => (p1.z + p2.z + p3.z) / 3; 27 | } 28 | -------------------------------------------------------------------------------- /lib/fbx_viewer/utils/screen_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ScreenUtils { 5 | ScreenUtils._(); 6 | 7 | static double width = 1; 8 | static double height = 1; 9 | 10 | static init(BuildContext context) { 11 | var size = MediaQuery.of(context).size; 12 | width = size.width; 13 | height = size.height; 14 | } 15 | } -------------------------------------------------------------------------------- /lib/fbx_viewer/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:ui' as ui; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:flutter_fbx3d_viewer/fbx_viewer/utils/screen_utils.dart'; 10 | 11 | import 'package:vector_math/vector_math.dart' as Math; 12 | 13 | drawText(Canvas canvas, String s, Offset offset, {double fontSize = 18}) { 14 | final textStyle = TextStyle( 15 | color: Colors.white, 16 | fontSize: fontSize, 17 | shadows: [Shadow(blurRadius: 5, color: Colors.black, offset: const Offset(1, 1))], 18 | ); 19 | final textSpan = TextSpan(text: s, style: textStyle); 20 | final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr); 21 | textPainter.layout(minWidth: 0); 22 | textPainter.paint(canvas, offset); 23 | } 24 | 25 | drawErrorText(Canvas canvas, String sHead, String sDesc) { 26 | drawText(canvas, sDesc, Offset(10, ScreenUtils.height / 2), fontSize: 12); 27 | drawText(canvas, sHead, Offset(10, ScreenUtils.height / 2 - 30), fontSize: 16); 28 | } 29 | 30 | Offset gen2DPointFrom3D(Math.Vector3 v) { 31 | final vn = Math.Vector3.copy(v); 32 | return Offset(vn.x, vn.y); 33 | } 34 | 35 | Color randomColor({double opacity}) => Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0).withOpacity(opacity ?? 1.0); 36 | 37 | int convertABGRtoARGB(int color) { 38 | int newColor = color; 39 | newColor = newColor & 0xFF00FF00; 40 | newColor = ((color & 0xFF) << 16) | newColor; 41 | newColor = ((color & 0x00FF0000) >> 16) | newColor; 42 | return newColor; 43 | } 44 | 45 | class ImageLoader { 46 | ImageLoader._(); 47 | 48 | static Future loadImage(BuildContext context, String path) async { 49 | final Completer completer = new Completer(); 50 | 51 | var fileImageUint8List; 52 | 53 | if (path.startsWith("assets/")) 54 | fileImageUint8List = (await rootBundle.load(path)).buffer.asUint8List(); 55 | else 56 | fileImageUint8List = await File(path).readAsBytes(); 57 | 58 | ui.decodeImageFromList(fileImageUint8List, (ui.Image img) { 59 | return completer.complete(img); 60 | }); 61 | return completer.future; 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /lib/fbx_viewer/widgets/zoom_gesture_detector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | 4 | /// 5 | /// Created by Kozári László in 2020.01.01 6 | /// lostinwar22@gmail.com 7 | /// 8 | 9 | class ZoomGestureDetector extends StatefulWidget { 10 | final Widget child; 11 | final void Function(Offset initialPoint) onPanStart; 12 | final void Function(Offset initialPoint, Offset delta) onPanUpdate; 13 | final void Function() onPanEnd; 14 | final void Function(Offset initialFocusPoint) onScaleStart; 15 | final void Function(Offset changedFocusPoint, double scale) onScaleUpdate; 16 | final void Function() onScaleEnd; 17 | final void Function(double dx) onHorizontalDragUpdate; 18 | final void Function(double dy) onVerticalDragUpdate; 19 | final int panDistanceToActivate; 20 | 21 | const ZoomGestureDetector({ 22 | this.child, 23 | this.onPanStart, 24 | this.onPanUpdate, 25 | this.onPanEnd, 26 | this.onScaleStart, 27 | this.onScaleUpdate, 28 | this.onScaleEnd, 29 | this.onHorizontalDragUpdate, 30 | this.onVerticalDragUpdate, 31 | this.panDistanceToActivate 32 | }); 33 | 34 | @override 35 | _ZoomGestureDetectorState createState() => _ZoomGestureDetectorState(); 36 | } 37 | 38 | class _ZoomGestureDetectorState extends State { 39 | final List _touches = []; 40 | double _initialScalingDistance; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return RawGestureDetector( 45 | child: widget.child, 46 | gestures: { 47 | ImmediateMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( 48 | () => ImmediateMultiDragGestureRecognizer(), 49 | (ImmediateMultiDragGestureRecognizer instance) { 50 | instance.onStart = (Offset offset) { 51 | final touch = Touch( 52 | offset, 53 | (drag, details) => _onTouchUpdate(drag, details), 54 | (drag, details) => _onTouchEnd(drag, details), 55 | ); 56 | _onTouchStart(touch); 57 | return touch; 58 | }; 59 | }, 60 | ), 61 | }, 62 | ); 63 | } 64 | 65 | void _onTouchStart(Touch touch) { 66 | _touches.add(touch); 67 | if (_touches.length == 1) { 68 | if (widget.onPanStart != null) widget.onPanStart(touch._startOffset); 69 | } else if (_touches.length == 2) { 70 | _initialScalingDistance = (_touches[0]._currentOffset - _touches[1]._currentOffset).distance; 71 | if (widget.onScaleStart != null) widget.onScaleStart((_touches[0]._currentOffset + _touches[1]._currentOffset) / 2); 72 | } 73 | } 74 | 75 | void _onTouchUpdate(Touch touch, DragUpdateDetails details) { 76 | assert(_touches.isNotEmpty); 77 | touch._currentOffset = details.localPosition; 78 | 79 | if (_touches.length == 1) { 80 | if (widget.onPanUpdate != null) widget.onPanUpdate(touch._startOffset, details.localPosition - touch._startOffset); 81 | 82 | if (widget.onHorizontalDragUpdate != null) { 83 | final dx = (details.localPosition.dx - touch._startOffset.dx).abs(); 84 | if (dx > widget.panDistanceToActivate ?? 50) widget.onHorizontalDragUpdate((details.localPosition.dx - touch._startOffset.dx).clamp(-2.0, 2.0)); 85 | } 86 | 87 | if (widget.onVerticalDragUpdate != null) { 88 | final dy = (details.localPosition.dy - touch._startOffset.dy).abs(); 89 | if (dy > widget.panDistanceToActivate ?? 50) widget.onVerticalDragUpdate((details.localPosition.dy - touch._startOffset.dy).clamp(-2.0, 2.0)); 90 | } 91 | 92 | } else { 93 | var newDistance = (_touches[0]._currentOffset - _touches[1]._currentOffset).distance; 94 | 95 | if (widget.onScaleUpdate != null) widget.onScaleUpdate((_touches[0]._currentOffset + _touches[1]._currentOffset) / 2, newDistance / _initialScalingDistance); 96 | } 97 | } 98 | 99 | void _onTouchEnd(Touch touch, DragEndDetails details) { 100 | _touches.remove(touch); 101 | if (_touches.length == 0) { 102 | if (widget.onPanEnd != null) widget.onPanEnd(); 103 | } else if (_touches.length == 1) { 104 | if (widget.onScaleEnd != null) widget.onScaleEnd(); 105 | 106 | _touches[0]._startOffset = _touches[0]._currentOffset; 107 | if (widget.onPanStart != null) widget.onPanStart(_touches[0]._startOffset); 108 | } 109 | } 110 | } 111 | 112 | class Touch extends Drag { 113 | Offset _startOffset; 114 | Offset _currentOffset; 115 | 116 | final void Function(Drag drag, DragUpdateDetails details) onUpdate; 117 | final void Function(Drag drag, DragEndDetails details) onEnd; 118 | 119 | Touch(this._startOffset, this.onUpdate, this.onEnd) { 120 | _currentOffset = _startOffset; 121 | } 122 | 123 | @override 124 | void update(DragUpdateDetails details) { 125 | super.update(details); 126 | onUpdate(this, details); 127 | } 128 | 129 | @override 130 | void end(DragEndDetails details) { 131 | super.end(details); 132 | onEnd(this, details); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /pix/pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic1.jpg -------------------------------------------------------------------------------- /pix/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic2.jpg -------------------------------------------------------------------------------- /pix/pic3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic3.jpg -------------------------------------------------------------------------------- /pix/pic4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic4.jpg -------------------------------------------------------------------------------- /pix/pic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic5.jpg -------------------------------------------------------------------------------- /pix/pic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaszlo8207/Flutter-FBX-3D-Viewer/bb4ca2aebab62b7fb52a6271a8e4f351bc9a3eb1/pix/pic6.jpg -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: "direct main" 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.3" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | image: 71 | dependency: "direct main" 72 | description: 73 | name: image 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "2.1.4" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.12.6" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.1.8" 91 | nested: 92 | dependency: transitive 93 | description: 94 | name: nested 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.0.4" 98 | path: 99 | dependency: transitive 100 | description: 101 | name: path 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.6.4" 105 | pedantic: 106 | dependency: transitive 107 | description: 108 | name: pedantic 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.8.0+1" 112 | petitparser: 113 | dependency: transitive 114 | description: 115 | name: petitparser 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "2.4.0" 119 | provider: 120 | dependency: "direct main" 121 | description: 122 | name: provider 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "4.0.2" 126 | quiver: 127 | dependency: transitive 128 | description: 129 | name: quiver 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.0.5" 133 | sky_engine: 134 | dependency: transitive 135 | description: flutter 136 | source: sdk 137 | version: "0.0.99" 138 | source_span: 139 | dependency: transitive 140 | description: 141 | name: source_span 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.5.5" 145 | stack_trace: 146 | dependency: transitive 147 | description: 148 | name: stack_trace 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.9.3" 152 | stream_channel: 153 | dependency: transitive 154 | description: 155 | name: stream_channel 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.0.0" 159 | string_scanner: 160 | dependency: transitive 161 | description: 162 | name: string_scanner 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.0.5" 166 | term_glyph: 167 | dependency: transitive 168 | description: 169 | name: term_glyph 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.1.0" 173 | test_api: 174 | dependency: transitive 175 | description: 176 | name: test_api 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.2.11" 180 | typed_data: 181 | dependency: transitive 182 | description: 183 | name: typed_data 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.1.6" 187 | vector_math: 188 | dependency: "direct main" 189 | description: 190 | name: vector_math 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "2.0.8" 194 | xml: 195 | dependency: transitive 196 | description: 197 | name: xml 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "3.5.0" 201 | sdks: 202 | dart: ">=2.4.0 <3.0.0" 203 | flutter: ">=1.12.1" 204 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_fbx3d_viewer 2 | description: Flutter package for viewing Fbx 3d animated files with textures, basic lights, colors. 3 | version: 1.0.0 4 | homepage: https://github.com/klaszlo8207/Flutter-FBX-3D-Viewer 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | vector_math: ^2.0.8 11 | provider: ^4.0.2 12 | archive: '>= 2.0.0 <3.0.0' 13 | image: '>= 2.0.0 <3.0.0' 14 | 15 | flutter: 16 | sdk: flutter 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter: 23 | #assets: 24 | #- assets/knight.fbx 25 | 26 | --------------------------------------------------------------------------------