1 /** 2 Inochi2D Bone Nodes 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.nodes.bone.bone; 14 import inochi2d.core.math.deform; 15 import inochi2d.core.math.simd; 16 import inochi2d.core.math; 17 import inochi2d.nodes; 18 import nulib.collections; 19 import numem; 20 21 /** 22 A bone, bones are used to build skeletal hirearchies that deform 23 other nodes via the use of bone weights. 24 */ 25 @TypeIdAbstract 26 @TypeId("Bone", IN_MAKE_TAG!(0, 3)) 27 class Bone : Node { 28 private: 29 @nogc: 30 vector!BoneTarget targets_; 31 vec2[] boneOffsets; 32 33 /** 34 Finds the given target's index within the bone mapping. 35 36 Params: 37 toFind = The deformable to find. 38 */ 39 ptrdiff_t findTargetIdx(IDeformable toFind) { 40 foreach (i, target; targets_) { 41 if (target.target is toFind) 42 return i; 43 } 44 return -1; 45 } 46 47 protected: 48 49 /** 50 Called during the update phase of a new frame. 51 52 Params: 53 delta = Time since the last frame. 54 drawList = The drawlist for the active scene. 55 */ 56 override 57 void onUpdate(float delta, DrawList drawList) { 58 auto offset = this.localTransformOffset; 59 foreach (ref BoneTarget target; targets_) { 60 size_t w_length = target.target.deformPoints.length; 61 vec2[] w_verts = target.target.deformPoints; 62 63 // Resize our temporary deformation buffer if need be. 64 if (boneOffsets.length < w_length) 65 boneOffsets = boneOffsets.nu_resize(w_length); 66 67 // Copy over the base bone locations from deformed points. 68 // Then add our delta bone offset transformation to every vertex. 69 // Finally we apply weights based on the weight paint. 70 boneOffsets[0 .. w_length] = w_verts[0 .. w_length]; 71 simd_mul(boneOffsets, offset.matrix); 72 simd_mul_weight(boneOffsets, target.weights); 73 74 // We then add the delta from the bone to the real deformation. 75 target.target.deform(boneOffsets); 76 } 77 } 78 79 public: 80 81 /** 82 An immutable slice of the targets of this bone. 83 */ 84 @property immutable(BoneTarget)[] targets() => cast(immutable(BoneTarget)[])targets_; 85 86 /// Destructor 87 ~this() { 88 targets_.clear(); 89 nu_freea(boneOffsets); 90 } 91 92 /** 93 Sets the weights for the given target. 94 95 Params: 96 target = The target to set weights for. 97 weights = The weights to set. 98 */ 99 void setWeights(IDeformable target, float[] weights) { 100 ptrdiff_t idx = this.findTargetIdx(target); 101 if (idx >= 0) { 102 targets_[idx].weights = weights.nu_dup(); 103 } 104 } 105 106 /** 107 Adds a target 108 109 Params: 110 target = The target to add. 111 */ 112 void addTarget(IDeformable target) { 113 if (this.findTargetIdx(target) == -1) { 114 this.targets_ ~= BoneTarget(target); 115 } 116 } 117 118 /** 119 Removes a target from this 120 121 Params: 122 target = The target to remove. 123 */ 124 void removeTarget(IDeformable target) { 125 ptrdiff_t idx = this.findTargetIdx(target); 126 if (idx >= 0) { 127 this.targets_.removeAt(idx); 128 } 129 } 130 } 131 132 mixin Register!(Bone, in_node_registry); 133 // dfmt off 134 135 136 137 138 /** 139 A target of a bone. 140 */ 141 struct BoneTarget { 142 @nogc: 143 144 /// Destructor. 145 ~this() { 146 nu_freea(weights); 147 } 148 149 /** 150 The target of the bone. 151 */ 152 IDeformable target; 153 154 /** 155 Bone weights 156 */ 157 float[] weights; 158 }