1 /* 2 Copyright © 2022, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module inochi2d.core.automation.phys; 8 import inochi2d.core.automation; 9 import inochi2d; 10 import std.math; 11 12 struct VerletNode { 13 float distance = 1f; 14 vec2 position; 15 vec2 oldPosition; 16 17 this(vec2 pos) { 18 this.position = pos; 19 this.oldPosition = pos; 20 } 21 22 /** 23 Serializes a parameter 24 */ 25 void serialize(S)(ref S serializer) { 26 auto state = serializer.objectBegin; 27 serializer.putKey("distance"); 28 serializer.putValue(distance); 29 serializer.putKey("position"); 30 position.serialize(serializer); 31 serializer.putKey("old_position"); 32 oldPosition.serialize(serializer); 33 serializer.objectEnd(state); 34 } 35 36 /** 37 Deserializes a parameter 38 */ 39 SerdeException deserializeFromFghj(Fghj data) { 40 data["distance"].deserializeValue(distance); 41 position.deserialize(data["position"]); 42 oldPosition.deserialize(data["old_position"]); 43 return null; 44 } 45 } 46 47 @TypeId("physics") 48 class PhysicsAutomation : Automation { 49 protected: 50 51 void simulate(size_t i, ref AutomationBinding binding) { 52 auto node = &nodes[i]; 53 54 vec2 tmp = node.position; 55 node.position = (node.position - node.oldPosition) + vec2(0, gravity) * (deltaTime * deltaTime) * bounciness; 56 node.oldPosition = tmp; 57 } 58 59 void constrain() { 60 foreach(i; 0..nodes.length-1) { 61 auto node1 = &nodes[i]; 62 auto node2 = &nodes[i+1]; 63 64 // idx 0 = first node in param, this always is the reference node. 65 // We base our "hinge" of the value of this reference value 66 if (i == 0) { 67 node1.position = vec2(bindings[i].getAxisValue(), 0); 68 } 69 70 // Then we calculate the distance of the difference between 71 // node 1 and 2, 72 float diffX = node1.position.x - node2.position.x; 73 float diffY = node1.position.y - node2.position.y; 74 float dist = node1.position.distance(node2.position); 75 float diff = 0; 76 77 // We need the distance to be larger than 0 so that 78 // we don't get division by zero problems. 79 if (dist > 0) { 80 // Node2 decides how far away it wants to be from Node1 81 diff = (node2.distance - dist) / dist; 82 } 83 84 // Apply out fancy new link 85 vec2 trans = vec2(diffX, diffY) * (.5 * diff); 86 node1.position += trans; 87 node2.position -= trans; 88 89 // Clamp so that we don't start flopping above the hinge above us 90 node2.position.y = clamp(node2.position.y, node1.position.y, float.max); 91 } 92 } 93 94 override 95 void onUpdate() { 96 if (bindings.length > 1) { 97 98 // simulate each link in our chain 99 foreach(i, ref binding; bindings) { 100 this.simulate(i, binding); 101 } 102 103 // Constrain values 104 foreach(i; 0..4+(bindings.length*2)) { 105 this.constrain(); 106 } 107 108 // Clamp and apply everything to be within range 109 foreach(i, ref node; nodes) { 110 node.position.x = clamp(node.position.x, bindings[i].range.x, bindings[i].range.y); 111 bindings[i].addAxisOffset(node.position.x); 112 } 113 } 114 } 115 116 override 117 void serializeSelf(ref InochiSerializer serializer) { 118 serializer.putKey("nodes"); 119 serializer.serializeValue(nodes); 120 serializer.putKey("damping"); 121 serializer.putValue(damping); 122 serializer.putKey("bounciness"); 123 serializer.putValue(bounciness); 124 serializer.putKey("gravity"); 125 serializer.putValue(gravity); 126 } 127 128 override 129 void serializeSelf(ref InochiSerializerCompact serializer) { 130 serializer.putKey("nodes"); 131 serializer.serializeValue(nodes); 132 serializer.putKey("damping"); 133 serializer.putValue(damping); 134 serializer.putKey("bounciness"); 135 serializer.putValue(bounciness); 136 serializer.putKey("gravity"); 137 serializer.putValue(gravity); 138 } 139 140 override 141 void deserializeSelf(Fghj data) { 142 data["nodes"].deserializeValue(nodes); 143 data["damping"].deserializeValue(damping); 144 data["bounciness"].deserializeValue(bounciness); 145 data["gravity"].deserializeValue(gravity); 146 } 147 public: 148 149 /** 150 A node in the internal verlet simulation 151 */ 152 VerletNode[] nodes; 153 154 /** 155 Amount of damping to apply to movement 156 */ 157 float damping = 0.05f; 158 159 /** 160 How bouncy movement should be 161 1 = default bounciness 162 */ 163 float bounciness = 1f; 164 165 /** 166 Gravity to apply to each link 167 */ 168 float gravity = 20f; 169 170 /** 171 Adds a binding 172 */ 173 override 174 void bind(AutomationBinding binding) { 175 super.bind(binding); 176 177 nodes ~= VerletNode(vec2(0, 1)); 178 } 179 180 this(Puppet parent) { 181 this.typeId = "physics"; 182 super(parent); 183 } 184 } 185 186 mixin InAutomation!PhysicsAutomation;