Example #1
0
void BoneAttachment::_check_bind() {

	if (get_parent() && get_parent()->cast_to<Skeleton>()) {
		Skeleton *sk = get_parent()->cast_to<Skeleton>();
		int idx = sk->find_bone(bone_name);
		if (idx!=-1) {
			sk->bind_child_node_to_bone(idx,this);;
			bound=true;
		}
	}
}
Example #2
0
void BoneAttachment::_check_unbind() {

	if (bound) {

		if (get_parent() && get_parent()->cast_to<Skeleton>()) {
			Skeleton *sk = get_parent()->cast_to<Skeleton>();
			int idx = sk->find_bone(bone_name);
			if (idx!=-1) {
				sk->unbind_child_node_from_bone(idx,this);;
			}
		}
		bound=false;
	}
}
Example #3
0
bool AnimationTree::_update_caches(AnimationPlayer *player) {

	setup_pass++;

	if (!player->has_node(player->get_root())) {
		ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
		set_active(false);
		return false;
	}
	Node *parent = player->get_node(player->get_root());

	List<StringName> sname;
	player->get_animation_list(&sname);

	for (List<StringName>::Element *E = sname.front(); E; E = E->next()) {
		Ref<Animation> anim = player->get_animation(E->get());
		for (int i = 0; i < anim->get_track_count(); i++) {
			NodePath path = anim->track_get_path(i);
			Animation::TrackType track_type = anim->track_get_type(i);

			TrackCache *track = NULL;
			if (track_cache.has(path)) {
				track = track_cache.get(path);
			}

			//if not valid, delete track
			if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == NULL)) {
				playing_caches.erase(track);
				memdelete(track);
				track_cache.erase(path);
				track = NULL;
			}

			if (!track) {

				RES resource;
				Vector<StringName> leftover_path;
				Node *child = parent->get_node_and_resource(path, resource, leftover_path);

				if (!child) {
					ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', couldn't resolve track:  '" + String(path) + "'");
					continue;
				}

				if (!child->is_connected("tree_exited", this, "_node_removed")) {
					child->connect("tree_exited", this, "_node_removed", varray(child));
				}

				switch (track_type) {
					case Animation::TYPE_VALUE: {

						TrackCacheValue *track_value = memnew(TrackCacheValue);

						if (resource.is_valid()) {
							track_value->object = resource.ptr();
						} else {
							track_value->object = child;
						}

						track_value->subpath = leftover_path;
						track_value->object_id = track_value->object->get_instance_id();

						track = track_value;

					} break;
					case Animation::TYPE_TRANSFORM: {

						Spatial *spatial = Object::cast_to<Spatial>(child);

						if (!spatial) {
							ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial:  '" + String(path) + "'");
							continue;
						}

						TrackCacheTransform *track_xform = memnew(TrackCacheTransform);

						track_xform->spatial = spatial;
						track_xform->skeleton = NULL;
						track_xform->bone_idx = -1;

						if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {

							Skeleton *sk = Object::cast_to<Skeleton>(spatial);
							int bone_idx = sk->find_bone(path.get_subname(0));
							if (bone_idx != -1 && !sk->is_bone_ignore_animation(bone_idx)) {

								track_xform->skeleton = sk;
								track_xform->bone_idx = bone_idx;
							}
						}

						track_xform->object = spatial;
						track_xform->object_id = track_xform->object->get_instance_id();

						track = track_xform;

					} break;
					case Animation::TYPE_METHOD: {

						TrackCacheMethod *track_method = memnew(TrackCacheMethod);

						if (resource.is_valid()) {
							track_method->object = resource.ptr();
						} else {
							track_method->object = child;
						}

						track_method->object_id = track_method->object->get_instance_id();

						track = track_method;

					} break;
					case Animation::TYPE_BEZIER: {

						TrackCacheBezier *track_bezier = memnew(TrackCacheBezier);

						if (resource.is_valid()) {
							track_bezier->object = resource.ptr();
						} else {
							track_bezier->object = child;
						}

						track_bezier->subpath = leftover_path;
						track_bezier->object_id = track_bezier->object->get_instance_id();

						track = track_bezier;
					} break;
					case Animation::TYPE_AUDIO: {

						TrackCacheAudio *track_audio = memnew(TrackCacheAudio);

						track_audio->object = child;
						track_audio->object_id = track_audio->object->get_instance_id();

						track = track_audio;

					} break;
					case Animation::TYPE_ANIMATION: {

						TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation);

						track_animation->object = child;
						track_animation->object_id = track_animation->object->get_instance_id();

						track = track_animation;

					} break;
					default: {
						ERR_PRINT("Animation corrupted (invalid track type)");
						continue;
					}
				}

				track_cache[path] = track;
			}

			track->setup_pass = setup_pass;
		}
	}

	List<NodePath> to_delete;

	const NodePath *K = NULL;
	while ((K = track_cache.next(K))) {
		TrackCache *tc = track_cache[*K];
		if (tc->setup_pass != setup_pass) {
			to_delete.push_back(*K);
		}
	}

	while (to_delete.front()) {
		NodePath np = to_delete.front()->get();
		memdelete(track_cache[np]);
		track_cache.erase(np);
		to_delete.pop_front();
	}

	state.track_map.clear();

	K = NULL;
	int idx = 0;
	while ((K = track_cache.next(K))) {
		state.track_map[*K] = idx;
		idx++;
	}

	state.track_count = idx;

	cache_valid = true;

	return true;
}
Example #4
0
void AnimationCache::_update_cache() {

	cache_valid=false;

	ERR_FAIL_COND(!root);
	ERR_FAIL_COND(!root->is_inside_tree());
	ERR_FAIL_COND(animation.is_null());

	for(int i=0;i<animation->get_track_count();i++) {



		NodePath np = animation->track_get_path(i);

		Node *node = root->get_node(np);
		if (!node) {

			path_cache.push_back(Path());
			ERR_EXPLAIN("Invalid Track Path in Animation: "+np);
			ERR_CONTINUE(!node);
		}


		Path path;

		Ref<Resource> res;

		if (np.get_subname_count()) {


			if (animation->track_get_type(i)==Animation::TYPE_TRANSFORM) {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Transform tracks can't have a subpath: "+np);
				ERR_CONTINUE(animation->track_get_type(i)==Animation::TYPE_TRANSFORM);

			}

			RES res;

			for(int j=0;j<np.get_subname_count();j++) {
				res = j==0 ? node->get(np.get_subname(j)) : res->get(np.get_subname(j));
				if (res.is_null())
					break;

			}

			if (res.is_null()) {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Invalid Track SubPath in Animation: "+np);
				ERR_CONTINUE(res.is_null());
			}

			path.resource=res;
			path.object=res.ptr();

		} else {


			if (animation->track_get_type(i)==Animation::TYPE_TRANSFORM) {
				StringName property = np.get_property();
				String ps = property;


				Spatial *sp = node->cast_to<Spatial>();

				if (!sp) {

					path_cache.push_back(Path());
					ERR_EXPLAIN("Transform track not of type Spatial: "+np);
					ERR_CONTINUE(!sp);
				}

				if (ps!="") {

					Skeleton *sk = node->cast_to<Skeleton>();
					if (!sk) {

						path_cache.push_back(Path());
						ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton!: "+np);
						ERR_CONTINUE(!sk);
					}

					int idx = sk->find_bone(ps);
					if (idx==-1) {

						path_cache.push_back(Path());
						ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton Bone!: "+np);
						ERR_CONTINUE(idx==-1);

					}

					path.bone_idx=idx;
					path.skeleton=sk;

				}

				path.spatial=sp;

			}

			path.node=node;
			path.object=node;

		}

		if (animation->track_get_type(i)==Animation::TYPE_VALUE) {

			if (np.get_property().operator String()=="") {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Value Track lacks property: "+np);
				ERR_CONTINUE(np.get_property().operator String()=="");

			}

			path.property=np.get_property();

		} else if (animation->track_get_type(i)==Animation::TYPE_METHOD) {

			if (np.get_property().operator String()!="") {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Method Track has property: "+np);
				ERR_CONTINUE(np.get_property().operator String()!="");

			}

		}


		path.valid=true;

		path_cache.push_back(path);

		if (!connected_nodes.has(path.node)) {
			connected_nodes.insert(path.node);
			path.node->connect("exit_tree",this,"_node_exit_tree",Node::make_binds(path.node),CONNECT_ONESHOT);
		}



	}



	cache_dirty=false;
	cache_valid=true;
}
void AnimationTreeEditor::_edit_filters() {

	filter_dialog->popup_centered_ratio();
	filter->clear();

	Set<String> npb;
	_find_paths_for_filter(edited_node,npb);

	TreeItem *root = filter->create_item();
	filter->set_hide_root(true);
	Map<String,TreeItem*> pm;

	Node *base = anim_tree->get_node( anim_tree->get_base_path() );

	for(Set<String>::Element *E=npb.front();E;E=E->next()) {

		TreeItem *parent=root;
		String descr=E->get();
		if (base) {
			NodePath np = E->get();

			if (np.get_property()!=StringName()) {
				Node *n = base->get_node(np);
				Skeleton *s = n->cast_to<Skeleton>();
				if (s) {

					String skelbase = E->get().substr(0,E->get().find(":"));


					int bidx = s->find_bone(np.get_property());

					if (bidx!=-1) {
						int bparent = s->get_bone_parent(bidx);
						//
						if (bparent!=-1) {


							String bpn = skelbase+":"+s->get_bone_name(bparent);
							if (pm.has(bpn)) {
								parent=pm[bpn];
								descr=np.get_property();
							}
						} else {

							if (pm.has(skelbase)) {
								parent=pm[skelbase];

							}
						}
					}
				}
			}
		}

		TreeItem *it = filter->create_item(parent);
		it->set_cell_mode(0,TreeItem::CELL_MODE_CHECK);
		it->set_text(0,descr);
		it->set_metadata(0,NodePath(E->get()));
		it->set_editable(0,true);
		if (anim_tree->node_get_type(edited_node)==AnimationTreePlayer::NODE_ONESHOT) {
			it->set_checked(0, anim_tree->oneshot_node_is_path_filtered(edited_node,E->get()));
		} else if (anim_tree->node_get_type(edited_node)==AnimationTreePlayer::NODE_BLEND2) {
			it->set_checked(0, anim_tree->blend2_node_is_path_filtered(edited_node,E->get()));
		}
		pm[E->get()]=it;
	}


}
bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) {

	if (updating || _filter_edit != anode)
		return false;

	NodePath player_path = anode->get_tree()->get_animation_player();

	if (!anode->get_tree()->has_node(player_path)) {
		EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names."));
		return false;
	}

	AnimationPlayer *player = Object::cast_to<AnimationPlayer>(anode->get_tree()->get_node(player_path));
	if (!player) {
		EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names."));
		return false;
	}

	Node *base = player->get_node(player->get_root());

	if (!base) {
		EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
		return false;
	}

	updating = true;

	Set<String> paths;
	{
		List<StringName> animations;
		player->get_animation_list(&animations);

		for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {

			Ref<Animation> anim = player->get_animation(E->get());
			for (int i = 0; i < anim->get_track_count(); i++) {
				paths.insert(anim->track_get_path(i));
			}
		}
	}

	filter_enabled->set_pressed(anode->is_filter_enabled());
	filters->clear();
	TreeItem *root = filters->create_item();

	Map<String, TreeItem *> parenthood;

	for (Set<String>::Element *E = paths.front(); E; E = E->next()) {

		NodePath path = E->get();
		TreeItem *ti = NULL;
		String accum;
		for (int i = 0; i < path.get_name_count(); i++) {
			String name = path.get_name(i);
			if (accum != String()) {
				accum += "/";
			}
			accum += name;
			if (!parenthood.has(accum)) {
				if (ti) {
					ti = filters->create_item(ti);
				} else {
					ti = filters->create_item(root);
				}
				parenthood[accum] = ti;
				ti->set_text(0, name);
				ti->set_selectable(0, false);
				ti->set_editable(0, false);

				if (base->has_node(accum)) {
					Node *node = base->get_node(accum);
					if (has_icon(node->get_class(), "EditorIcons")) {
						ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
					} else {
						ti->set_icon(0, get_icon("Node", "EditorIcons"));
					}
				}

			} else {
				ti = parenthood[accum];
			}
		}

		Node *node = NULL;
		if (base->has_node(accum)) {
			node = base->get_node(accum);
		}
		if (!node)
			continue; //no node, cant edit

		if (path.get_subname_count()) {

			String concat = path.get_concatenated_subnames();

			Skeleton *skeleton = Object::cast_to<Skeleton>(node);
			if (skeleton && skeleton->find_bone(concat) != -1) {
				//path in skeleton
				String bone = concat;
				int idx = skeleton->find_bone(bone);
				List<String> bone_path;
				while (idx != -1) {
					bone_path.push_front(skeleton->get_bone_name(idx));
					idx = skeleton->get_bone_parent(idx);
				}

				accum += ":";
				for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
					if (F != bone_path.front()) {
						accum += "/";
					}

					accum += F->get();
					if (!parenthood.has(accum)) {
						ti = filters->create_item(ti);
						parenthood[accum] = ti;
						ti->set_text(0, F->get());
						ti->set_selectable(0, false);
						ti->set_editable(0, false);
						ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
					} else {
						ti = parenthood[accum];
					}
				}

				ti->set_editable(0, true);
				ti->set_selectable(0, true);
				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
				ti->set_text(0, concat);
				ti->set_checked(0, anode->is_path_filtered(path));
				ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
				ti->set_metadata(0, path);

			} else {
				//just a property
				ti = filters->create_item(ti);
				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
				ti->set_text(0, concat);
				ti->set_editable(0, true);
				ti->set_selectable(0, true);
				ti->set_checked(0, anode->is_path_filtered(path));
				ti->set_metadata(0, path);
			}
		} else {
			if (ti) {
				//just a node, likely call or animation track
				ti->set_editable(0, true);
				ti->set_selectable(0, true);
				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
				ti->set_checked(0, anode->is_path_filtered(path));
				ti->set_metadata(0, path);
			}
		}
	}

	updating = false;

	return true;
}
void EditorPropertyRootMotion::_node_assign() {

	NodePath current = get_edited_object()->get(get_edited_property());

	AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object());
	if (!atree->has_node(atree->get_animation_player())) {
		EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer"));
		return;
	}
	AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player()));
	if (!player) {
		EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid"));
		return;
	}

	Node *base = player->get_node(player->get_root());

	if (!base) {
		EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
		return;
	}

	Set<String> paths;
	{
		List<StringName> animations;
		player->get_animation_list(&animations);

		for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {

			Ref<Animation> anim = player->get_animation(E->get());
			for (int i = 0; i < anim->get_track_count(); i++) {
				paths.insert(anim->track_get_path(i));
			}
		}
	}

	filters->clear();
	TreeItem *root = filters->create_item();

	Map<String, TreeItem *> parenthood;

	for (Set<String>::Element *E = paths.front(); E; E = E->next()) {

		NodePath path = E->get();
		TreeItem *ti = NULL;
		String accum;
		for (int i = 0; i < path.get_name_count(); i++) {
			String name = path.get_name(i);
			if (accum != String()) {
				accum += "/";
			}
			accum += name;
			if (!parenthood.has(accum)) {
				if (ti) {
					ti = filters->create_item(ti);
				} else {
					ti = filters->create_item(root);
				}
				parenthood[accum] = ti;
				ti->set_text(0, name);
				ti->set_selectable(0, false);
				ti->set_editable(0, false);

				if (base->has_node(accum)) {
					Node *node = base->get_node(accum);
					if (has_icon(node->get_class(), "EditorIcons")) {
						ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
					} else {
						ti->set_icon(0, get_icon("Node", "EditorIcons"));
					}
				}

			} else {
				ti = parenthood[accum];
			}
		}

		Node *node = NULL;
		if (base->has_node(accum)) {
			node = base->get_node(accum);
		}
		if (!node)
			continue; //no node, cant edit

		if (path.get_subname_count()) {

			String concat = path.get_concatenated_subnames();

			Skeleton *skeleton = Object::cast_to<Skeleton>(node);
			if (skeleton && skeleton->find_bone(concat) != -1) {
				//path in skeleton
				String bone = concat;
				int idx = skeleton->find_bone(bone);
				List<String> bone_path;
				while (idx != -1) {
					bone_path.push_front(skeleton->get_bone_name(idx));
					idx = skeleton->get_bone_parent(idx);
				}

				accum += ":";
				for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
					if (F != bone_path.front()) {
						accum += "/";
					}

					accum += F->get();
					if (!parenthood.has(accum)) {
						ti = filters->create_item(ti);
						parenthood[accum] = ti;
						ti->set_text(0, F->get());
						ti->set_selectable(0, true);
						ti->set_editable(0, false);
						ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
						ti->set_metadata(0, accum);
					} else {
						ti = parenthood[accum];
					}
				}

				ti->set_selectable(0, true);
				ti->set_text(0, concat);
				ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
				ti->set_metadata(0, path);
				if (path == current) {
					ti->select(0);
				}

			} else {
				//just a property
				ti = filters->create_item(ti);
				ti->set_text(0, concat);
				ti->set_selectable(0, true);
				ti->set_metadata(0, path);
				if (path == current) {
					ti->select(0);
				}
			}
		} else {
			if (ti) {
				//just a node, likely call or animation track
				ti->set_selectable(0, true);
				ti->set_metadata(0, path);
				if (path == current) {
					ti->select(0);
				}
			}
		}
	}

	filters->ensure_cursor_is_visible();
	filter_dialog->popup_centered_ratio();
}
Example #8
0
void AnimationCache::_update_cache() {

	cache_valid = false;

	ERR_FAIL_COND(!root);
	ERR_FAIL_COND(!root->is_inside_tree());
	ERR_FAIL_COND(animation.is_null());

	for (int i = 0; i < animation->get_track_count(); i++) {

		NodePath np = animation->track_get_path(i);

		Node *node = root->get_node(np);
		if (!node) {

			path_cache.push_back(Path());
			ERR_EXPLAIN("Invalid Track Path in Animation: " + np);
			ERR_CONTINUE(!node);
		}

		Path path;

		Ref<Resource> res;

		if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM) {

			if (np.get_subname_count() > 1) {
				path_cache.push_back(Path());
				ERR_EXPLAIN("Transform tracks can't have a subpath: " + np);
				ERR_CONTINUE(animation->track_get_type(i) == Animation::TYPE_TRANSFORM);
			}

			Spatial *sp = Object::cast_to<Spatial>(node);

			if (!sp) {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Transform track not of type Spatial: " + np);
				ERR_CONTINUE(!sp);
			}

			if (np.get_subname_count() == 1) {
				StringName property = np.get_subname(0);
				String ps = property;

				Skeleton *sk = Object::cast_to<Skeleton>(node);
				if (!sk) {

					path_cache.push_back(Path());
					ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton!: " + np);
					ERR_CONTINUE(!sk);
				}

				int idx = sk->find_bone(ps);
				if (idx == -1) {
					path_cache.push_back(Path());
					ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton Bone!: " + np);
					ERR_CONTINUE(idx == -1);
				}

				path.bone_idx = idx;
				path.skeleton = sk;
			}

			path.spatial = sp;

		} else {
			if (np.get_subname_count() > 0) {

				RES res2;
				Vector<StringName> leftover_subpath;

				// We don't want to cache the last resource unless it is a method call
				bool is_method = animation->track_get_type(i) == Animation::TYPE_METHOD;
				root->get_node_and_resource(np, res2, leftover_subpath, is_method);

				if (res2.is_valid()) {
					path.resource = res2;
				} else {
					path.node = node;
				}
				path.object = res2.is_valid() ? res2.ptr() : (Object *)node;
				path.subpath = leftover_subpath;

			} else {

				path.node = node;
				path.object = node;
				path.subpath = np.get_subnames();
			}
		}

		if (animation->track_get_type(i) == Animation::TYPE_VALUE) {

			if (np.get_subname_count() == 0) {

				path_cache.push_back(Path());
				ERR_EXPLAIN("Value Track lacks property: " + np);
				ERR_CONTINUE(np.get_subname_count() == 0);
			}

		} else if (animation->track_get_type(i) == Animation::TYPE_METHOD) {

			if (path.subpath.size() != 0) { // Trying to call a method of a non-resource

				path_cache.push_back(Path());
				ERR_EXPLAIN("Method Track has property: " + np);
				ERR_CONTINUE(path.subpath.size() != 0);
			}
		}

		path.valid = true;

		path_cache.push_back(path);

		if (!connected_nodes.has(path.node)) {
			connected_nodes.insert(path.node);
			path.node->connect("tree_exiting", this, "_node_exit_tree", Node::make_binds(path.node), CONNECT_ONESHOT);
		}
	}

	cache_dirty = false;
	cache_valid = true;
}
Error EditorSceneImporterFBXConv::_parse_animations(State& state) {

	AnimationPlayer *ap = memnew( AnimationPlayer );

	state.scene->add_child(ap);
	ap->set_owner(state.scene);

	for(int i=0;i<state.animations.size();i++) {

		Dictionary anim = state.animations[i];
		ERR_CONTINUE(!anim.has("id"));
		Ref<Animation> an = memnew( Animation );
		an->set_name(_id(anim["id"]));


		if (anim.has("bones")) {

			Array bone_tracks = anim["bones"];
			for(int j=0;j<bone_tracks.size();j++) {
				Dictionary bone_track=bone_tracks[j];
				String bone = bone_track["boneId"];
				if (!bone_track.has("keyframes"))
					continue;
				if (!state.bones.has(bone))
					continue;

				Skeleton *sk = state.bones[bone].skeleton;

				if (!sk)
					continue;
				int bone_idx=sk->find_bone(bone);
				if (bone_idx==-1)
					continue;



				String path = state.scene->get_path_to(sk);
				path+=":"+bone;
				an->add_track(Animation::TYPE_TRANSFORM);
				int tidx = an->get_track_count()-1;
				an->track_set_path(tidx,path);


				Dictionary parent_xform_dict;
				Dictionary xform_dict;

				if (state.bones.has(bone)) {
					xform_dict=state.bones[bone].node;
				}


				Array parent_keyframes;
				if (sk->get_bone_parent(bone_idx)!=-1) {
					String parent_name = sk->get_bone_name(sk->get_bone_parent(bone_idx));
					if (state.bones.has(parent_name)) {
						parent_xform_dict=state.bones[parent_name].node;
					}

					print_line("parent for "+bone+"? "+parent_name+" XFD: "+String(Variant(parent_xform_dict)));
					for(int k=0;k<bone_tracks.size();k++) {
						Dictionary d = bone_tracks[k];
						if (d["boneId"]==parent_name) {
							parent_keyframes=d["keyframes"];
							print_line("found keyframes");
							break;
						}
					}


				}

				print_line("BONE XFD "+String(Variant(xform_dict)));

				Array keyframes=bone_track["keyframes"];

				for(int k=0;k<keyframes.size();k++) {

					Dictionary key=keyframes[k];
					Transform xform=_get_transform_mixed(key,xform_dict);
					float time = key["keytime"];
					time=time/1000.0;
#if 0
					if (parent_keyframes.size()) {
						//localize
						print_line(itos(k)+" localizate for: "+bone);

						float prev_kt=-1;
						float kt;
						int idx=0;

						for(int l=0;l<parent_keyframes.size();l++) {

							Dictionary d=parent_keyframes[l];
							kt=d["keytime"];
							kt=kt/1000.0;
							if (kt>time)
								break;
							prev_kt=kt;
							idx++;

						}

						Transform t;
						if (idx==0) {
							t=_get_transform_mixed(parent_keyframes[0],parent_xform_dict);
						} else if (idx==parent_keyframes.size()){
							t=_get_transform_mixed(parent_keyframes[idx-1],parent_xform_dict);
						} else {
							t=_get_transform_mixed(parent_keyframes[idx-1],parent_xform_dict);
							float d = (time-prev_kt)/(kt-prev_kt);
							if (d>0) {
								Transform t2=_get_transform_mixed(parent_keyframes[idx],parent_xform_dict);
								t=t.interpolate_with(t2,d);
							} else {
								print_line("exact: "+rtos(kt));
							}
						}

						xform = t.affine_inverse() * xform; //localize
					} else if (!parent_xform_dict.empty()) {
						Transform t = _get_transform(parent_xform_dict);
						xform = t.affine_inverse() * xform; //localize
					}
#endif

					xform = sk->get_bone_rest(bone_idx).affine_inverse() * xform;


					Quat q = xform.basis;
					q.normalize();
					Vector3 s = xform.basis.get_scale();
					Vector3 l = xform.origin;



					an->transform_track_insert_key(tidx,time,l,q,s);

				}

			}


		}


		ap->add_animation(_id(anim["id"]),an);

	}

	return OK;
}