1 /* 2 Inochi2D Bone Group 3 4 Copyright © 2020, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.core.nodes.pathdeform; 10 import inochi2d.fmt.serialize; 11 import inochi2d.core.nodes.part; 12 import inochi2d.core.nodes; 13 import inochi2d.core.dbg; 14 import inochi2d.core; 15 import inochi2d.math; 16 17 package(inochi2d) { 18 void inInitPathDeform() { 19 inRegisterNodeType!PathDeform; 20 } 21 } 22 23 /** 24 A node that deforms multiple nodes against a path. 25 */ 26 @TypeId("PathDeform") 27 class PathDeform : Node { 28 private: 29 30 // Joint Origins 31 vec2[] jointOrigins; 32 33 // Computed joint matrices 34 mat3[] computedJoints; 35 36 37 void recomputeJoints() { 38 foreach(i; 0..joints.length) { 39 float startAngle; 40 float endAngle; 41 size_t next = i+1; 42 43 // Special Case: 44 // We're at the end of the joints list 45 // There's nothing to "orient" ourselves against, so 46 // We'll just have a rotational value of 0 47 if (next >= joints.length) { 48 startAngle = atan2( 49 jointOrigins[i-1].y - jointOrigins[i].y, 50 jointOrigins[i-1].x - jointOrigins[i].x 51 ); 52 53 endAngle = atan2( 54 joints[i-1].y - joints[i].y, 55 joints[i-1].x - joints[i].x 56 ); 57 } else { 58 59 // Get the angles between our origin positions and our 60 // Current joint positions to get the difference. 61 // The difference between the root angle and the current 62 // angle determines how much the point and path is rotated. 63 startAngle = atan2( 64 jointOrigins[i].y - jointOrigins[next].y, 65 jointOrigins[i].x - jointOrigins[next].x 66 ); 67 68 endAngle = atan2( 69 joints[i].y - joints[next].y, 70 joints[i].x - joints[next].x 71 ); 72 } 73 74 75 // Apply our wonky math to our computed joint 76 computedJoints[i] = mat3.translation(vec3(joints[i], 0)) * mat3.zrotation(startAngle-endAngle); 77 } 78 } 79 80 /** 81 Bindings queued for finalization 82 */ 83 size_t[][][uint] queuedBindings; 84 85 protected: 86 87 override 88 string typeId() { return "PathDeform"; } 89 90 /** 91 Allows serializing self data (with pretty serializer) 92 */ 93 override 94 void serializeSelf(ref InochiSerializer serializer) { 95 super.serializeSelf(serializer); 96 serializer.putKey("joints"); 97 auto state = serializer.arrayBegin(); 98 foreach(joint; jointOrigins) { 99 serializer.elemBegin; 100 joint.serialize(serializer); 101 } 102 serializer.arrayEnd(state); 103 104 serializer.putKey("bindings"); 105 state = serializer.arrayBegin(); 106 foreach(item, data; bindings) { 107 serializer.elemBegin; 108 auto obj = serializer.objectBegin(); 109 serializer.putKey("bound_to"); 110 serializer.putValue(item.uuid); 111 serializer.putKey("bind_data"); 112 serializer.serializeValue(data); 113 serializer.objectEnd(obj); 114 } 115 serializer.arrayEnd(state); 116 } 117 118 /** 119 Allows serializing self data (with compact serializer) 120 */ 121 override 122 void serializeSelf(ref InochiSerializerCompact serializer) { 123 super.serializeSelf(serializer); 124 serializer.putKey("joints"); 125 auto state = serializer.arrayBegin(); 126 foreach(joint; jointOrigins) { 127 serializer.elemBegin; 128 joint.serialize(serializer); 129 } 130 serializer.arrayEnd(state); 131 132 if (bindings.length > 0) { 133 serializer.putKey("bindings"); 134 state = serializer.arrayBegin(); 135 foreach(item, data; bindings) { 136 137 // (Safety measure) Skip null items 138 if (item is null) continue; 139 140 serializer.elemBegin; 141 auto obj = serializer.objectBegin(); 142 serializer.putKey("bound_to"); 143 serializer.putValue(item.uuid); 144 145 serializer.putKey("bind_data"); 146 serializer.serializeValue(data); 147 serializer.objectEnd(obj); 148 } 149 serializer.arrayEnd(state); 150 } 151 152 // TODO: serialize bindings 153 } 154 155 override 156 SerdeException deserializeFromFghj(Fghj data) { 157 super.deserializeFromFghj(data); 158 159 foreach(jointData; data["joints"].byElement) { 160 vec2 val; 161 val.deserialize(jointData); 162 163 jointOrigins ~= val; 164 } 165 joints = jointOrigins.dup; 166 this.computedJoints = new mat3[joints.length]; 167 168 if (!data["bindings"].isEmpty) { 169 foreach(bindingData; data["bindings"].byElement) { 170 uint uuid; 171 size_t[][] qdata; 172 bindingData["bound_to"].deserializeValue(uuid); 173 bindingData["bind_data"].deserializeValue(qdata); 174 queuedBindings[uuid] = qdata; 175 } 176 } 177 178 // TODO: deserialize bindings 179 return null; 180 } 181 182 public: 183 184 /** 185 The current joint locations of the deformation 186 */ 187 vec2[] joints; 188 189 /** 190 The bindings from joints to verticies in multiple parts 191 192 [Drawable] = Every drawable that is affected 193 [] = the entry of the joint 194 size_t[] = the entry of verticies in that part that should be affected. 195 */ 196 size_t[][][Drawable] bindings; 197 198 /** 199 Gets joint origins 200 */ 201 vec2[] origins() { 202 return jointOrigins; 203 } 204 205 /** 206 Constructs a new path deform 207 */ 208 this(Node parent = null) { 209 super(parent); 210 } 211 212 /** 213 Constructs a new path deform 214 */ 215 this(vec2[] joints, Node parent = null) { 216 this.setJoints(joints); 217 super(parent); 218 } 219 220 /** 221 Sets the joints for this path deform 222 */ 223 void setJoints(vec2[] joints) { 224 this.jointOrigins = joints.dup; 225 this.joints = joints.dup; 226 this.computedJoints = new mat3[joints.length]; 227 } 228 229 /** 230 Adds a joint with the specified offset to the end of the joints list 231 */ 232 void addJoint(vec2 joint) { 233 jointOrigins ~= jointOrigins[$-1] + joint; 234 joints ~= jointOrigins[$-1]; 235 computedJoints.length++; 236 } 237 238 /** 239 Sets the position of joint as its new origin 240 */ 241 void setJointOriginFor(size_t index) { 242 if (index >= joints.length) return; 243 jointOrigins[index] = joints[index]; 244 } 245 246 /** 247 Updates the spline group. 248 */ 249 override 250 void update() { 251 this.recomputeJoints(); 252 253 // Iterates over every part attached to this deform 254 // Then iterates over every joint that should affect that part 255 // Then appplies the deformation across that part's joints 256 foreach(Drawable part, size_t[][] entry; bindings) { 257 if (part is null) continue; 258 MeshData mesh = part.getMesh(); 259 260 foreach(jointEntry, vertList; entry) { 261 mat3 joint = computedJoints[jointEntry]; 262 263 // Deform vertices 264 foreach(i; vertList) { 265 part.vertices[i] = (joint * vec3(mesh.vertices[i], 0)).xy; 266 } 267 } 268 } 269 270 super.update(); 271 } 272 273 274 void drawHandles() { 275 276 } 277 278 /** 279 Resets the positions of joints 280 */ 281 void resetJoints() { 282 joints = jointOrigins; 283 computedJoints.length = joints.length; 284 } 285 286 override 287 void finalize() { 288 super.finalize(); 289 290 // Finalize by moving the data over to the actual bindings 291 foreach(uuid, data; queuedBindings) { 292 bindings[puppet.find!Drawable(uuid)] = data.dup; 293 } 294 295 // Clear this memory 296 destroy(queuedBindings); 297 } 298 }