1 /* 2 Inochi2D Welding 3 4 Copyright © 2020-2025, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.effect.weld; 10 import inochi2d.effect; 11 import inochi2d.core.math.deform; 12 import inochi2d.nodes.visual.part; 13 import inochi2d.nodes; 14 import inochi2d.core; 15 import inochi2d.common; 16 import inp.format; 17 import numath; 18 import numem; 19 import nulib; 20 21 /** 22 An effect that welds other meshes to this mesh, forcing 23 the welded meshes to be dragged towards paired vertices 24 */ 25 @TypeId("WeldEffect", 0x0001) 26 class WeldEffect : MeshEffect { 27 private: 28 @nogc: 29 30 /** 31 Target of the weld 32 */ 33 IDeformable target_; 34 GUID targetGUID; 35 36 /** 37 Vertices of the weld 38 */ 39 WeldVertex[] vertices_; 40 41 protected: 42 43 /** 44 Serializes this MeshEffect to a DataNode. 45 46 Params: 47 object = The DataNode to serialize to. 48 */ 49 override 50 void onSerialize(ref DataNode object) { 51 super.onSerialize(object); 52 53 object["target"] = (cast(Node)target_).guid.toString()[0 .. $]; 54 object["weights"] = DataNode.createArray(); 55 foreach (vertex; vertices_) { 56 object["weights"] ~= DataNode(vertex.weight); 57 } 58 } 59 60 /** 61 Deserializes this node from a DataNode. 62 63 Params: 64 object = The DataNode to deserialize from. 65 state = The state of the deserializer. 66 */ 67 override 68 void onDeserialize(ref DataNode object, ref ModelState state) { 69 targetGUID = object.tryGetGUID(state, "target", "target"); 70 if ("weights" in object && object["weights"].isArray) { 71 vertices_ = nu_malloca!WeldVertex(object["weights"].length); 72 foreach (i, ref value; object["weights"].array) { 73 vertices_[i].weight = value.tryCoerce!float(0.0); 74 } 75 } 76 } 77 78 /** 79 Called when the Effect is to be finalized during loading. 80 81 Params: 82 state = The state of the deserializer. 83 */ 84 override 85 void onFinalize(ref ModelState state) { 86 if (targetGUID != GUID.nil) { 87 this.target_ = cast(IDeformable)parent.puppet.find(targetGUID); 88 this.rebuildWeights(); 89 } 90 } 91 92 /** 93 Called after the full node update cycle. 94 95 Params: 96 drawList = The drawlist for the active scene. 97 */ 98 override 99 void onApply(DrawList drawList) { 100 vec2[] targetMesh = target_.deformPoints; 101 vec2[] sourceMesh = parent.deformPoints; 102 foreach (i, ref WeldVertex weld; vertices_) { 103 vec2 pdelta = targetMesh[weld.index] + weld.delta; 104 vec2 p = lerp(sourceMesh[i], pdelta, weld.weight); 105 parent.deform(i, p, true); 106 } 107 } 108 109 void rebuildWelds() { 110 if (vertices_) 111 nu_freea(vertices_); 112 113 if (target_) { 114 vertices_ = nu_malloca!WeldVertex(parent.basePoints.length); 115 this.rebuildWeights(); 116 } 117 } 118 119 public: 120 121 /** 122 Target of the weld 123 */ 124 final @property IDeformable target() => target_; 125 final @property void target(IDeformable value) { 126 this.target_ = value; 127 this.rebuildWelds(); 128 } 129 130 /** 131 Constructs a new mesh effect. 132 133 Params: 134 parent = The parent of the mesh effect. 135 */ 136 this(Part parent) { 137 super(parent); 138 } 139 140 /** 141 Rebuilds the weight deltas for the WeldEffect. 142 */ 143 void rebuildWeights() { 144 foreach (i, ref WeldVertex vtx; vertices_) { 145 146 // Rebuild target vertex 147 vec2 parentVtx = parent.basePoints[i]; 148 vtx.index = cast(uint)findClosest(parentVtx, target.basePoints); 149 150 // Rebuild delta 151 vec2 targetVtx = target.basePoints[vtx.index]; 152 vtx.delta = targetVtx - parentVtx; 153 } 154 } 155 } 156 157 /** 158 A welded vertex 159 */ 160 struct WeldVertex { 161 162 /** 163 Weld delta 164 */ 165 vec2 delta = vec2(0, 0); 166 167 /** 168 Index of the vertex to drag towards 169 */ 170 uint index; 171 172 /** 173 Weight of the given line segment. 174 */ 175 float weight = 0; 176 }