1 /** 2 Specific Parameter Implementations 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; 16 import inochi2d.param.bindings; 17 import inochi2d.param.utils; 18 import inochi2d.common; 19 import inochi2d.puppet; 20 import inochi2d.nodes; 21 import inochi2d.core; 22 import inochi2d.core.serde; 23 24 import numem; 25 import numem.core.memory; 26 27 import nulib.collections; 28 import nulib.string; 29 30 import numath; 31 32 /** 33 The public parameter registry. 34 */ 35 __gshared TypeRegistry!Parameter in_param_registry; 36 37 public import inochi2d.param.parameters.param1d; 38 public import inochi2d.param.parameters.param2d; 39 40 /** 41 Top-level parameter deserialization function. 42 */ 43 Parameter tryDeserializeParam(ref DataNode object, ref ModelState state) @nogc { 44 if (state.doUpgrade08) { 45 state.info(nstring("0.8->0.9: upgrading legacy parameter ", object["name"].text)); 46 auto param = object.tryGet!bool(state, "is_vec2", false) ? 47 nogc_new!Parameter2D() : nogc_new!Parameter1D(); 48 49 param.deserialize(object, state); 50 return param; 51 } 52 53 if (auto param = in_param_registry.tryCreateFrom(object)) { 54 param.deserialize(object, state); 55 return param; 56 } 57 58 state.warning(nstring("Encountered untyped parameter, ignoring...")); 59 return null; 60 } 61 62 /** 63 Parameters are configurable values that are used to drive mesh 64 deformations, property overrides, and more. 65 */ 66 abstract 67 class Parameter : NuRefCounted, ISerializable, IDeserializable!ModelState { 68 protected: 69 @nogc: 70 71 /** 72 Serialize this parameter. 73 */ 74 override 75 void onSerialize(ref DataNode object) { 76 object["guid"] = guid.toString()[]; 77 object["name"] = name[]; 78 // object["bindings"] = bindings.serialize(); 79 } 80 81 /** 82 Deserialize this parameter. 83 */ 84 override 85 void onDeserialize(ref DataNode object, ref ModelState state) { 86 guid = object.tryGetGUID(state, "uuid"); 87 object.tryGetRef(state, name, "name"); 88 89 auto pbindings = "bindings" in object; 90 if (pbindings && (*pbindings).isArray) { 91 foreach (ref binding; (*pbindings).array) { 92 this.bindings ~= binding.tryDeserializeBinding(state, this); 93 } 94 } 95 } 96 97 /** 98 Finalizes the parameter. 99 100 Params: 101 puppet = The parent puppet 102 state = The state of the deserializer. 103 */ 104 void onFinalize(Puppet puppet, ref ModelState state) { 105 foreach_reverse (i; 0 .. bindings.length) { 106 if (auto binding = bindings[i]) { 107 binding.finalize(puppet, state); 108 } else { 109 bindings.removeAt(i); 110 } 111 } 112 } 113 114 public: 115 116 /** 117 The globally unique ID of this parameter. 118 */ 119 GUID guid; 120 121 /** 122 The user-facing name of this parameter. 123 */ 124 nstring name; 125 126 /** 127 Whether this parameter currently updates the model. 128 */ 129 bool active = true; 130 131 /** 132 The bindings of this parameter to puppet nodes. 133 */ 134 vector!ParameterBinding bindings; 135 136 /** 137 The dimensionality of the parameter. 138 */ 139 abstract @property int dimensions(); 140 141 /** 142 Counts of elements in each axis. 143 */ 144 abstract @property uint[] elementCounts(); 145 146 /** 147 The current value of the parameter. 148 */ 149 abstract @property float[] currentValue(); 150 151 /** 152 The lower bound of this parameter. 153 */ 154 abstract @property float[] lowerBound(); 155 156 /** 157 The upper bound of this parameter. 158 */ 159 abstract @property float[] upperBound(); 160 161 /** 162 Check whether this parameter has a binding to the given target. 163 164 Params: 165 node = 166 prop = 167 168 Returns: 169 $(D true) if this parameter has a binding to the target, 170 $(D false) otherwise. 171 */ 172 bool hasBinding(Node node, string prop) { 173 //foreach (ref binding; bindings) { 174 // if (binding.target.node is node && binding.target.prop == prop) { 175 // return true; 176 // } 177 //} 178 179 return false; 180 } 181 182 /** 183 Check whether this parameter has any bindings to the given node. 184 185 Params: 186 node = 187 188 Returns: 189 $(D true) if this parameter has any bindings to the node, 190 $(D false) otherwise. 191 */ 192 bool hasAnyBindingsTo(Node node) { 193 //foreach (binding; bindings) { 194 // if (binding.target.node is node) { 195 // return true; 196 // } 197 //} 198 199 return false; 200 } 201 202 /** 203 Serializes this parameter. 204 */ 205 final void serialize(ref DataNode object) { 206 this.onSerialize(object); 207 } 208 209 /** 210 Deserializes this parameter. 211 */ 212 final void deserialize(ref DataNode object, ref ModelState state) { 213 this.onDeserialize(object, state); 214 } 215 216 /** 217 Finalizes the parameter. 218 219 Params: 220 puppet = The parent puppet 221 state = The state of the deserializer. 222 */ 223 final void finalize(Puppet puppet, ref ModelState state) { 224 this.onFinalize(puppet, state); 225 } 226 227 /** 228 Update our bindings with the value of this parameter. 229 */ 230 abstract void update(); 231 } 232 233 enum ParameterAxis { 234 /** 235 Axis along rows (vertical). 236 */ 237 rows = 0, 238 239 /** 240 Axis along columns (horizontal). 241 */ 242 columns = 1, 243 } 244 245 enum ParameterMergeMode { 246 /** 247 Parameters are merged additively 248 */ 249 additive = 0x00, 250 251 /** 252 Parameters are merged with a weighted average 253 */ 254 weighted = 0x01, 255 256 /** 257 Parameters are merged multiplicatively 258 */ 259 multiplicative = 0x02, 260 261 /** 262 Forces parameter to be given value 263 */ 264 forced = 0x03, 265 266 /** 267 Merge mode is passthrough 268 */ 269 passthrough = 0x04, 270 } 271 272 /** 273 Gets a parameter merge mode from its string name. 274 */ 275 ParameterMergeMode toParameterMergeMode(string value) @nogc { 276 switch (value) { 277 case "additive": 278 case "Additive": 279 return ParameterMergeMode.additive; 280 281 case "weighted": 282 case "Weighted": 283 return ParameterMergeMode.weighted; 284 285 case "multiplicative": 286 case "Multiplicative": 287 return ParameterMergeMode.multiplicative; 288 289 case "forced": 290 case "Forced": 291 return ParameterMergeMode.forced; 292 293 default: 294 case "passthrough": 295 case "Passthrough": 296 return ParameterMergeMode.passthrough; 297 } 298 } 299 300 /** 301 Find the index and normal of the given position among the given points. 302 303 Params: 304 points = The set of points to search, must contain at least two values. 305 pos = The position to search for among the given points. 306 norm = The given position, normalized between its two adjacent points. 307 308 Returns: 309 The index of the point right *before* the given position, 310 or $(D -1) if not found. 311 */ 312 ptrdiff_t searchPoints(float[] points, float pos, out float norm) pure @nogc { 313 314 // Find index of given position. 315 const index = searchPoints(points, pos); 316 317 if (index >= 0) { 318 319 // Normalize along two adjacent points. 320 const lo = points[index]; 321 const hi = points[index + 1]; 322 norm = (pos - lo) / (hi - lo); 323 } 324 325 return index; 326 } 327 328 /** 329 Find the index of the given position among the given points. 330 331 Params: 332 points = The set of points to search, must contain at least two values. 333 pos = The position for which to search among the given points. 334 335 Returns: 336 The index of the point right *before* the given position, 337 or $(D -1) if not found. 338 */ 339 ptrdiff_t searchPoints(float[] points, float pos) pure @nogc { 340 assert(points.length >= 2, "Cannot search lists of points with fewer than 2 elements."); 341 342 // Binary-search points list for our position. 343 auto cursor = points[0 .. $ - 1]; 344 while (cursor.length > 1) { 345 if (pos < cursor[$ / 2]) { 346 cursor = cursor[0 .. $ / 2]; 347 } else { 348 cursor = cursor[$ / 2 .. $]; 349 } 350 } 351 352 // Pointer distance from points start to cursor start. 353 return cast(ptrdiff_t)(&cursor[0] - &points[0]); 354 }