1 /** 2 2D Parameter 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 Mireille Arseneault 13 Hoshino Lina 14 */ 15 module inochi2d.param.parameters.param2d; 16 import inochi2d.param.parameters; 17 import inochi2d.param.utils; 18 import inochi2d.core.registry; 19 import inochi2d.core.serde; 20 import inochi2d.core.math; 21 import inochi2d.core.guid; 22 import inochi2d.common; 23 import inochi2d.puppet; 24 import nulib; 25 import numem; 26 27 /** 28 2D variant of a parameter. 29 */ 30 @TypeId("2d", IN_MAKE_TAG!(2, 0)) 31 class Parameter2D : Parameter { 32 protected: 33 @nogc: 34 35 /** 36 Serialize this parameter. 37 */ 38 override 39 void onSerialize(ref DataNode object) { 40 super.onSerialize(object); 41 object["min"] = min.serialize(); 42 object["max"] = max.serialize(); 43 object["defaults"] = defaults.serialize(); 44 object["hpoints"] = hpoints.serialize(); 45 object["vpoints"] = vpoints.serialize(); 46 } 47 48 /** 49 Deserialize this parameter. 50 */ 51 override 52 void onDeserialize(ref DataNode object, ref ModelState state) { 53 super.onDeserialize(object, state); 54 object.tryGetRef(state, min, "min"); 55 object.tryGetRef(state, max, "max"); 56 object.tryGetRef(state, defaults, "defaults"); 57 58 // 0.8->0.9 upgrades 59 if (state.doUpgrade08) { 60 state.info("0.8->0.9: Upgrading 2D axis mapping..."); 61 62 auto axes = "axis_points" in object; 63 if (axes && (*axes).isArray) { 64 (*axes).tryGetRef(state, hpoints, 0); 65 (*axes).tryGetRef(state, vpoints, 1); 66 } 67 return; 68 } 69 70 object.tryGetRef(state, hpoints, "hpoints"); 71 object.tryGetRef(state, vpoints, "vpoints"); 72 } 73 74 /** 75 Finalizes the parameter. 76 77 Params: 78 puppet = The parent puppet 79 state = The state of the deserializer. 80 */ 81 override 82 void onFinalize(Puppet puppet, ref ModelState state) { 83 super.onFinalize(puppet, state); 84 value = defaults; 85 } 86 87 public: 88 89 /** 90 The current value of this parameter. 91 */ 92 vec2 value = vec2(0, 0); 93 94 /** 95 The previous value of this parameter. 96 */ 97 vec2 prev = vec2(0, 0); 98 99 /** 100 The default value of this parameter. 101 */ 102 vec2 defaults = vec2(0, 0); 103 104 /** 105 The lower bounds of this parameter. 106 */ 107 vec2 min = vec2(0, 0); 108 109 /** 110 The upper bounds of this parameter. 111 */ 112 vec2 max = vec2(1, 1); 113 114 /** 115 Our horizontal keypoints' positions. 116 */ 117 vector!float hpoints; 118 119 /** 120 Our vertical keypoints' positions. 121 */ 122 vector!float vpoints; 123 124 /** 125 The dimensionality of the parameter. 126 */ 127 override @property int dimensions() => 2; 128 129 /** 130 The current value of the parameter. 131 */ 132 override @property float[] currentValue() @trusted => value.data[]; 133 134 /** 135 The lower bound of this parameter. 136 */ 137 override @property float[] lowerBound() => min.data[]; 138 139 /** 140 The upper bound of this parameter. 141 */ 142 override @property float[] upperBound() => max.data[]; 143 144 /** 145 Counts of elements in each axis. 146 */ 147 override @property uint[] elementCounts() { 148 static uint[2] counts; 149 counts = [cast(uint)hpoints.length, cast(uint)vpoints.length]; 150 return counts[0 .. 2]; 151 } 152 153 /** 154 Construct a new named parameter. 155 */ 156 this(string name = null) { 157 float[2] points_init = [0, 1]; 158 hpoints = points_init; 159 vpoints = points_init; 160 guid = inNewGUID(); 161 this.name = name; 162 } 163 164 /** 165 Update our bindings with the value of this parameter. 166 */ 167 override 168 void update() { 169 if (!active) 170 return; 171 172 vec2 norm; 173 vec2u index = findKeypointAndNormal(value, norm); 174 175 foreach (binding; bindings) { 176 binding.apply(index, norm); 177 } 178 } 179 180 /** 181 Force this parameter to take on the given value. 182 183 Params: 184 value = The new value this parameter will take on. 185 */ 186 void pushValue(vec2 value) { 187 this.value = value; 188 } 189 190 /** 191 Force this parameter to take on the given value. 192 193 Params: 194 axis = The axis along which the value is set. 195 value = The new value this parameter will take on. 196 */ 197 void pushValue(ParameterAxis axis, float value) { 198 final switch (axis) { 199 case ParameterAxis.rows: 200 this.value.y = value; 201 break; 202 203 case ParameterAxis.columns: 204 this.value.x = value; 205 break; 206 } 207 } 208 209 /** 210 Find the keypoint index of the given position, as well as its normal. 211 212 Params: 213 pos = A position along our keypoints. Must be within min & max. 214 norm = The given position, normalized within its keypoint. 215 216 Returns: 217 The index of the keypoint the given position falls on. 218 */ 219 vec2i findKeypointAndNormal(vec2 pos, out vec2 norm) { 220 assert(pos.x >= min.x && pos.x <= max.x); 221 assert(pos.y >= min.y && pos.y <= max.y); 222 const x = searchPoints(hpoints, pos.x, norm.x); 223 const y = searchPoints(vpoints, pos.y, norm.y); 224 return vec2i(x, y); 225 } 226 227 /** 228 Normalize the given position memberwise between min & max. 229 */ 230 vec2 normalize(vec2 pos) const { 231 const x = (pos.x - min.x) / (max.x - min.x); 232 const y = (pos.y - min.y) / (max.y - min.y); 233 return vec2(x, y); 234 } 235 236 /** 237 Linearly interpolate min & max by the given value. 238 */ 239 vec2 lerp(vec2 norm) const { 240 const x = .lerp(min.x, max.x, norm.x); 241 const y = .lerp(min.y, max.y, norm.y); 242 return vec2(x, y); 243 } 244 } 245 246 mixin Register!(Parameter2D, in_param_registry);