12void Skeleton2D::ensureSorted() {
13 if (sk_.bones().empty()) {
14 sk_.topologicalSort();
18void Skeleton2D::ensureStateSize() {
20 const unsigned n =
static_cast<unsigned>(sk_.bones().size());
21 if (state_.size() <
n) {
23 state_.position_.resize(
n);
24 state_.orientation_.resize(
n);
25 state_.scale_.resize(
n);
26 state_.rotation_.resize(
n);
30::ik::bone2d *Skeleton2D::boneAt(
int boneId)
const {
31 if (boneId < 0)
return nullptr;
32 const auto &bones = sk_.bones();
33 if (
static_cast<size_t>(boneId) >= bones.size())
return nullptr;
34 return bones[
static_cast<size_t>(boneId)];
37void Skeleton2D::requireBone(
int boneId)
const {
38 if (!boneAt(boneId)) {
39 throw Exception(
"Skeleton2D: invalid bone id %d", boneId);
45 requireBone(parentId);
47 throw Exception(
"Skeleton2D.createBone: length must be > 0");
49 ::ik::bone2d *
parent = boneAt(parentId);
50 ::ik::bone2d *child =
parent->createBone(length);
51 sk_.topologicalSort();
53 return static_cast<int>(child->
id);
57 const_cast<Skeleton2D *
>(
this)->ensureSorted();
58 return static_cast<int>(sk_.bones().size());
63 ::ik::bone2d *
b = boneAt(boneId);
64 if (!
b->parent)
return -1;
65 return static_cast<int>(
b->parent->id);
70 return static_cast<int>(boneAt(boneId)->children.size());
75 ::ik::bone2d *
b = boneAt(boneId);
76 if (childIndex < 0 ||
static_cast<size_t>(childIndex) >=
b->children.size()) {
77 throw Exception(
"Skeleton2D.getChild: childIndex out of range");
79 return static_cast<int>(
b->children[
static_cast<size_t>(childIndex)]->
id);
84 return boneAt(boneId)->length;
90 throw Exception(
"Skeleton2D.setLength: root length is always 0");
93 throw Exception(
"Skeleton2D.setLength: length must be > 0");
95 boneAt(boneId)->setLength(length);
101 boneAt(boneId)->position(state_) = {
x,
y};
106 const_cast<Skeleton2D *
>(
this)->ensureStateSize();
107 return boneAt(boneId)->position(state_)[0];
112 const_cast<Skeleton2D *
>(
this)->ensureStateSize();
113 return boneAt(boneId)->position(state_)[1];
119 boneAt(boneId)->orientation(state_) = {
x,
y};
124 const_cast<Skeleton2D *
>(
this)->ensureStateSize();
125 return boneAt(boneId)->orientation(state_)[0];
130 const_cast<Skeleton2D *
>(
this)->ensureStateSize();
131 return boneAt(boneId)->orientation(state_)[1];
137 boneAt(boneId)->rotation(state_) = {angle};
142 const_cast<Skeleton2D *
>(
this)->ensureStateSize();
143 return boneAt(boneId)->rotation(state_)[0];
148 if (minAngle > maxAngle) {
149 throw Exception(
"Skeleton2D.setConstraints: minAngle must be <= maxAngle");
151 boneAt(boneId)->setConstraints(::ik::vec<float, 1>{minAngle},
152 ::ik::vec<float, 1>{maxAngle});
157 boneAt(boneId)->clearConstraints();
162 return boneAt(boneId)->constrained;
168 sk_.init_straight_pose(state_, ::ik::vec2{rootX, rootY});
180 sk_.forward_kinematics(state_);
186 sk_.update_rotations(state_);
191 return sk_.total_length_to(boneAt(boneId));
Script-facing 2D skeleton + pose state (ik::skeleton2d + ik::ecs2d). Bone indices are stable after ea...
float getLength(int boneId) const
void setOrientation(int boneId, float x, float y)
void setRotation(int boneId, float angle)
Local hinge angle (radians) relative to parent forward.
float totalLengthTo(int boneId) const
float getY(int boneId) const
int getChildCount(int boneId) const
bool hasConstraints(int boneId) const
float getX(int boneId) const
void initStraightPose(float rootX=0.f, float rootY=0.f)
float getRotation(int boneId) const
int getParent(int boneId) const
void clearConstraints(int boneId)
float getOrientationX(int boneId) const
float getOrientationY(int boneId) const
void setConstraints(int boneId, float minAngle, float maxAngle)
Joint limits for the single 2D hinge DOF.
void setLength(int boneId, float length)
int createBone(int parentId, float length=1.f)
Create a child bone under parentId; returns new bone id.
void setPosition(int boneId, float x, float y)
int getChild(int boneId, int childIndex) const