Skip to content

Commit

Permalink
feat: cleaned skeleton code + tree view
Browse files Browse the repository at this point in the history
  • Loading branch information
jvenin committed Apr 29, 2024
1 parent 342511f commit 5a15aac
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 108 deletions.
12 changes: 12 additions & 0 deletions lib/classes/gsf/header2/chunks/bone.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:typed_data';

import 'package:paraworld_gsf_viewer/classes/gsf/header2/affine_matrix.dart';
import 'package:paraworld_gsf_viewer/classes/gsf_data.dart';
import 'package:vector_math/vector_math.dart';

class Bone extends GsfPart {
late final Standard4BytesData<int> guid;
Expand All @@ -22,6 +23,17 @@ class Bone extends GsfPart {

AffineTransformation? bindPose;

Vector3 get translation => Vector3(posX.value, posY.value, posZ.value);
Quaternion get rotation => Quaternion(
quaternionX.value,
quaternionY.value,
quaternionZ.value,
quaternionW.value,
);
Vector3 get scale => Vector3(scaleX.value, scaleY.value, scaleZ.value);

Matrix4 get localTransform => Matrix4.compose(translation, rotation, scale);

@override
String get label => 'Bone 0x${guid.value.toRadixString(16)}';

Expand Down
2 changes: 1 addition & 1 deletion lib/classes/gsf/header2/chunks/mesh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ mixin MeshToModelInterface on Chunk {
],
cloth: [],
boundingBox: globalBB,
skeleton: [],
skeletons: [],
);
}
}
Expand Down
75 changes: 21 additions & 54 deletions lib/classes/gsf/header2/chunks/skeleton.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ class SkeletonChunk extends Chunk {
}
_getChildOfBone(boneTree, bones, nextBone);
}
print(boneTree);
bindPoses = [];
for (var i = 0; i < allBonesCount.value; i++) {
final bindPose = AffineTransformation.fromBytes(
Expand Down Expand Up @@ -179,33 +178,17 @@ class SkeletonChunk extends Chunk {
}

_createJointsBranch(
List<ModelVertex> jointsBranch,
Map<int, bool> treatedBones,
List<ModelVertex> jointVertices,
List<int> boneIds,
Matrix4 parentWorldTransform,
) {
for (final boneId in boneIds) {
final data = boneTree[boneId]!;
final bone = data.bone;
treatedBones[bone.guid.value] = true;
final translation = Vector3(
bone.posX.value,
bone.posY.value,
bone.posZ.value,
);

final Quaternion rotation = Quaternion(
bone.quaternionX.value,
bone.quaternionY.value,
bone.quaternionZ.value,
bone.quaternionW.value,
);
final scale =
Vector3(bone.scaleX.value, bone.scaleY.value, bone.scaleZ.value);
Matrix4 localTransform = Matrix4.compose(translation, rotation, scale);
Matrix4 localTransform = bone.localTransform;
localTransform *= bone.bindPose!.matrix;
final Matrix4 worldTranform = parentWorldTransform * localTransform;
jointsBranch.add(
jointVertices.add(
ModelVertex(
worldTranform.getTranslation(),
box: BoundingBoxModel.zero(),
Expand All @@ -215,47 +198,31 @@ class SkeletonChunk extends Chunk {
);

_createJointsBranch(
jointsBranch,
treatedBones,
jointVertices,
data.children,
worldTranform,
);
}
}

List<List<ModelVertex>> toModelVertices() {
final List<List<ModelVertex>> vertices = [];
final Map<int, bool> treatedBones = {};
for (final bone in bones) {
final branch = <ModelVertex>[];
if (treatedBones.containsKey(bone.guid.value)) {
continue;
}
treatedBones[bone.guid.value] = true;
var translation =
Vector3(bone.posX.value, bone.posY.value, bone.posZ.value);
final scale =
Vector3(bone.scaleX.value, bone.scaleY.value, bone.scaleZ.value);
final Quaternion rotation = Quaternion(
bone.quaternionX.value,
bone.quaternionY.value,
bone.quaternionZ.value,
bone.quaternionW.value,
);
Matrix4 localTransform = Matrix4.compose(translation, rotation, scale);
localTransform = localTransform * bone.bindPose!.matrix;
List<ModelVertex> toModelVertices() {
final List<ModelVertex> vertices = [];
final rootBone = bones.first;

branch.add(
ModelVertex(
localTransform.getTranslation(),
box: BoundingBoxModel.zero(),
positionOffset: Vector3.zero(),
),
);
_createJointsBranch(branch, treatedBones, boneTree[bone.guid.value]!.children,
localTransform);
vertices.add(branch);
}
Matrix4 localTransform = rootBone.localTransform;
localTransform = localTransform * rootBone.bindPose!.matrix;

vertices.add(
ModelVertex(
localTransform.getTranslation(),
box: BoundingBoxModel.zero(),
positionOffset: Vector3.zero(),
),
);
_createJointsBranch(
vertices, boneTree[rootBone.guid.value]!.children, localTransform);
assert(vertices.length == allBonesCount.value,
"There is more than one root bone in skeleton");
return vertices;
}
}
10 changes: 7 additions & 3 deletions lib/classes/gsf/header2/model_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ class ModelSettings extends GsfPart {
) {
final List<ModelMesh> meshes = [];
final List<ModelMesh> cloths = [];
List<List<ModelVertex>> skeleton = [];
List<List<ModelVertex>> skeletons = [];
final List<int> materialIndices =
fallbackTable?.usedMaterialIndexes.map((e) => e.value).toList() ?? [];
for (final chunk in chunksTable?.chunks ?? <Chunk>[]) {
if (chunk.type == ChunkType.skeleton) {
skeleton = (chunk as SkeletonChunk).toModelVertices();
skeletons.add((chunk as SkeletonChunk).toModelVertices());
} else if (chunk.type.isMeshLike()) {
meshes.add((chunk as MeshChunk).toModelMesh(
ChunkAttributes.fromValue(type, chunk.attributes.value),
Expand All @@ -263,13 +263,17 @@ class ModelSettings extends GsfPart {
));
}
}
assert(
skeletons.length == skeletonChunksCount.value,
'Skeletons count does not match',
);
return Model(
name: objectName.label,
type: type,
meshes: meshes,
cloth: cloths,
boundingBox: boundingBox.toModelBox(),
skeleton: skeleton,
skeletons: skeletons,
);
}

Expand Down
28 changes: 14 additions & 14 deletions lib/classes/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class Model {
required this.meshes,
required this.cloth,
required this.boundingBox,
required this.skeleton,
required this.skeletons,
});

final ModelType type;
final String name;
final List<ModelMesh> meshes;
final List<ModelMesh> cloth;
final BoundingBoxModel boundingBox;
final List<List<ModelVertex>> skeleton;
final List<List<ModelVertex>> skeletons;
// todo skeleton
// todo position links

Expand Down Expand Up @@ -238,14 +238,14 @@ class Model {
: (_paint..color = meshColor.withOpacity(0.3)),
);

if (showSkeleton && skeleton.isNotEmpty) {
final List<double> points = [];
if (showSkeleton && skeletons.isNotEmpty) {
final skeletonPaint = Paint()
..color = meshColor
..strokeWidth = 5
..strokeCap = StrokeCap.round;
for (final branch in skeleton) {
for (final joint in branch) {
for (final skeleton in skeletons) {
final List<double> points = [];
for (final joint in skeleton) {
final coords = joint.project(
widthOffset: projectionData.widthOffset,
heightOffset: projectionData.heightOffset,
Expand All @@ -255,15 +255,15 @@ class Model {
);

points.addAll([coords.pointProjection.x, coords.pointProjection.y]);
final pos = Float32List.fromList(points);
canvas.drawRawPoints(ui.PointMode.polygon, pos, skeletonPaint);
canvas.drawRawPoints(
ui.PointMode.points,
pos,
Paint()
..color = Colors.pink
..strokeWidth = 8);
}
final pos = Float32List.fromList(points);
canvas.drawRawPoints(ui.PointMode.polygon, pos, skeletonPaint);
canvas.drawRawPoints(
ui.PointMode.points,
pos,
Paint()
..color = Colors.pink
..strokeWidth = 8);
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion lib/widgets/header2/display.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,15 @@ List<Widget> getChunkWidgetByType(
],
withSkeleton: (data) => [
SkeletonDisplay(skeleton: data.skeleton),
if (data.bone != null) BoneDisplay(bone: data.bone!),
Flexible(
child: Column(
children: [
BoneTreeDisplay(
boneTree: data.skeleton.boneTree, bones: data.skeleton.bones),
if (data.bone != null) BoneDisplay(bone: data.bone!)
],
),
),
],
withLink: (data) => [LinkDisplay(link: data.linkChunk)],
orElse: () => [const SizedBox.shrink()],
Expand Down
104 changes: 78 additions & 26 deletions lib/widgets/header2/widgets/chunks/skeleton.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'package:animated_tree_view/animated_tree_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paraworld_gsf_viewer/classes/gsf/header2/chunks/bind_pose.dart';
import 'package:paraworld_gsf_viewer/classes/gsf/header2/chunks/bone.dart';
import 'package:paraworld_gsf_viewer/classes/gsf/header2/chunks/skeleton.dart';
import 'package:paraworld_gsf_viewer/widgets/header2/providers.dart';
import 'package:paraworld_gsf_viewer/widgets/header2/widgets/affine_transformation.dart';
import 'package:paraworld_gsf_viewer/widgets/header2/widgets/chunks/attributes/chunk_attributes.dart';
import 'package:paraworld_gsf_viewer/widgets/utils/data_display.dart';
import 'package:paraworld_gsf_viewer/widgets/utils/label.dart';

class SkeletonDisplay extends ConsumerWidget {
const SkeletonDisplay({super.key, required this.skeleton});
Expand Down Expand Up @@ -73,37 +74,88 @@ class BoneDisplay extends StatelessWidget {
}
}

class _BindPoseDisplay extends StatelessWidget {
const _BindPoseDisplay({
class BoneTreeDisplay extends ConsumerStatefulWidget {
const BoneTreeDisplay({
super.key,
required this.bindPose,
required this.boneTree,
required this.bones,
});

final BindPose bindPose;
final BoneTree boneTree;
final List<Bone> bones;

@override
ConsumerState<BoneTreeDisplay> createState() => _BoneTreeDisplayState();
}

class _BoneTreeDisplayState extends ConsumerState<BoneTreeDisplay> {
late final TreeNode<Bone> _computedBoneTree = TreeNode.root(
data: widget.bones.first,
);

createBranchFromBone(Bone bone, TreeNode<Bone> node) {
final children = widget.boneTree[bone.guid.value]!.children;
for (final child in children) {
final chilBone = widget.boneTree[child]!.bone;
final childNode = TreeNode<Bone>(
key: child.toString(),
data: widget.boneTree[child]!.bone,
);
node.add(childNode);
createBranchFromBone(chilBone, childNode);
}
}

@override
void initState() {
createBranchFromBone(widget.bones.first, _computedBoneTree);
super.initState();
}

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GsfDataTile(label: "a1_1", data: bindPose.float1To1),
GsfDataTile(label: "a1_2", data: bindPose.float1To2),
GsfDataTile(label: "a1_3", data: bindPose.float1To3),
GsfDataTile(label: "a1_4", data: bindPose.float1To4),
GsfDataTile(label: "a2_1", data: bindPose.float2To1),
GsfDataTile(label: "a2_2", data: bindPose.float2To2),
GsfDataTile(label: "a2_3", data: bindPose.float2To3),
GsfDataTile(label: "a2_4", data: bindPose.float2To4),
GsfDataTile(label: "a3_1", data: bindPose.float3To1),
GsfDataTile(label: "a3_2", data: bindPose.float3To2),
GsfDataTile(label: "a3_3", data: bindPose.float3To3),
GsfDataTile(label: "a3_4", data: bindPose.float3To4),
GsfDataTile(label: "a4_1", data: bindPose.float4To1),
GsfDataTile(label: "a4_2", data: bindPose.float4To2),
GsfDataTile(label: "a4_3", data: bindPose.float4To3),
GsfDataTile(label: "a4_4", data: bindPose.float4To4),
],
final theme = Theme.of(context);
final selectedBone = ref.watch(header2StateNotifierProvider).mapOrNull(
withModelSettings: (state) => state.selectedChunkState?.mapOrNull(
withSkeleton: (skeleton) => skeleton.bone,
),
);
return Flexible(
child: ListViewWrapper(
rightPadding: 0,
maxHeight: double.infinity,
child: TreeView.simpleTyped<Bone, TreeNode<Bone>>(
shrinkWrap: true,
onTreeReady: (controller) {
controller.expandAllChildren(_computedBoneTree);
},
builder: (context, node) {
return ListTile(
dense: true,
visualDensity: const VisualDensity(
horizontal: 0,
vertical: VisualDensity.minimumDensity,
),
shape: Border(
top: BorderSide(color: theme.colorScheme.onBackground),
left: BorderSide(color: theme.colorScheme.onBackground),
),
selected: selectedBone != null &&
selectedBone.guid.value == node.data?.guid.value,
selectedTileColor: theme.colorScheme.secondary,
title: Label.regular(node.data?.label ?? ""),
onTap: () {
if (node.data != null) {
ref
.read(header2StateNotifierProvider.notifier)
.setBone(node.data!);
}
},
);
},
tree: _computedBoneTree,
),
),
);
}
}
2 changes: 1 addition & 1 deletion lib/widgets/header2/widgets/chunks/submesh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class SubmeshDisplay extends StatelessWidget {
],
cloth: [],
boundingBox: modelData.box,
skeleton: [],
skeletons: [],
);
return Flexible(
child: Column(
Expand Down
Loading

0 comments on commit 5a15aac

Please sign in to comment.