1 /** 2 Inochi2D Part Node 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 */ 13 module inochi2d.nodes.visual.part; 14 import inochi2d.nodes.visual; 15 import inochi2d.common; 16 import inochi2d.effect; 17 import inochi2d.nodes; 18 import inochi2d.core; 19 import numath; 20 import nulib.conv; 21 import nulib; 22 import numem; 23 24 public import inochi2d.core.render.state; 25 public import inochi2d.core.mesh; 26 27 enum NO_TEXTURE = uint.max; 28 enum TextureUsage : size_t { 29 Albedo, 30 Emissive, 31 Bumpmap, 32 COUNT 33 } 34 35 struct PartVars { 36 vec3 tint; 37 vec3 screenTint; 38 private void[4] __dummy; 39 float opacity; 40 float emissionStrength; 41 } 42 43 /** 44 Dynamic Mesh Part 45 */ 46 @TypeId("Part", IN_MAKE_TAG!(1, 1)) 47 class Part : Visual, IDeformable { 48 private: 49 @nogc: 50 Mesh mesh_; 51 DeformedMesh deformed_; 52 DeformedMesh base_; 53 weak_vector!MeshEffect effects_; 54 55 protected: 56 57 /** 58 The deformed mesh state of the part. 59 */ 60 @property ref DeformedMesh deformedMesh() => deformed_; 61 62 /** 63 The current active draw list slot for this 64 drawable. 65 */ 66 DrawListAlloc* drawListSlot; 67 68 /** 69 Serializes this node to a DataNode. 70 71 Params: 72 object = The DataNode to serialize to. 73 */ 74 override 75 void onSerialize(ref DataNode object) { 76 super.onSerialize(object); 77 78 MeshData data = MeshData(mesh_); 79 object["mesh"] = data.serialize(); 80 object["textures"] = DataNode.createArray(); 81 foreach (ref texture; textures) { 82 if (texture) { 83 ptrdiff_t index = puppet.getTextureSlotIndexFor(texture); 84 object["textures"].array ~= DataNode(index >= 0 ? index : NO_TEXTURE); 85 } else { 86 object["textures"].array ~= DataNode(NO_TEXTURE); 87 } 88 } 89 90 // Serialize attached effects. 91 if (effects_.length > 0) { 92 object["effects"] = DataNode.createArray(); 93 foreach (effect; effects_) { 94 95 DataNode effectObj; 96 effect.serialize(effectObj); 97 object["effects"].array ~= effectObj; 98 } 99 } 100 101 // Serialize basic data. 102 object["blend_mode"] = cast(uint)blendingMode; 103 object["tint"] = tint.serialize(); 104 object["screenTint"] = screenTint.serialize(); 105 object["emissionStrength"] = emissionStrength; 106 object["opacity"] = opacity; 107 } 108 109 /** 110 Deserializes this node from a DataNode. 111 112 Params: 113 object = The DataNode to deserialize from. 114 state = The state of the deserializer. 115 */ 116 override 117 void onDeserialize(ref DataNode object, ref ModelState state) { 118 super.onDeserialize(object, state); 119 120 auto meshData = object.tryGet!MeshData(state, "mesh"); 121 this.deformed_ = nogc_new!DeformedMesh(); 122 this.base_ = nogc_new!DeformedMesh(); 123 this.mesh = Mesh.fromMeshData(meshData); 124 125 // Textures 126 if ("textures" in object && object["textures"].isArray) { 127 foreach (i, ref DataNode element; object["textures"].array) { 128 129 uint textureId = element.tryGet!uint(state, NO_TEXTURE); 130 if (textureId == NO_TEXTURE) 131 continue; 132 133 // TODO: Abstract this to properly handle refcounts. 134 this.textures[i] = puppet.textureCache.get(textureId); 135 if (!this.textures[i]) { 136 state.error(nstring(this.name[], ": failed to find texture ", .toString!ulong(textureId), "...")); 137 } 138 139 if (this.textures[i]) 140 this.textures[i].retain(); 141 } 142 } 143 144 // Effects 145 if ("effects" in object && object["effects"].isArray) { 146 foreach (i, ref element; object["effects"].array) { 147 if (MeshEffect effect = in_effect_registry.tryCreateFrom(element, this)) { 148 effect.deserialize(element, state); 149 } 150 } 151 } 152 153 object.tryGetRef(state, opacity, "opacity", 1); 154 object.tryGetRef(state, tint.data, "tint"); 155 object.tryGetRef(state, screenTint.data, "screenTint"); 156 object.tryGetRef(state, emissionStrength, "emissionStrength"); 157 158 if ("blend_mode" in object && object["blend_mode"].isNumber) 159 blendingMode = cast(BlendMode)object.tryGet!uint(state, "blend_mode", blendingMode.normal); 160 else 161 blendingMode = object.tryGet!string(state, "blend_mode", "Normal").toBlendMode(); 162 } 163 164 /** 165 Called when the node is to finalize its deserialization from disk. 166 167 Params: 168 state = The state of the deserializer. 169 */ 170 override 171 void onFinalize(ref ModelState state) { 172 super.onFinalize(state); 173 foreach (effect; effects_) { 174 effect.finalize(state); 175 } 176 } 177 178 /** 179 Called during the early update phase of a new frame. 180 181 Params: 182 drawList = The drawlist for the active scene. 183 */ 184 override 185 void onPreUpdate(DrawList drawList) { 186 super.onPreUpdate(drawList); 187 this.resetDeform(); 188 } 189 190 /** 191 Called during the update phase of a new frame. 192 193 Params: 194 delta = Time since the last frame. 195 drawList = The drawlist for the active scene. 196 */ 197 override 198 void onUpdate(float delta, DrawList drawList) { 199 super.onUpdate(delta, drawList); 200 deformed_.pushMatrix(this.deformMatrix); 201 } 202 203 /** 204 Called during the late update phase of a new frame. 205 206 Params: 207 drawList = The drawlist for the active scene. 208 */ 209 override 210 void onPostUpdate(DrawList drawList) { 211 super.onPostUpdate(drawList); 212 213 this.drawListSlot = drawList.allocate(deformed_.vertices, deformed_.indices); 214 215 // Apply mesh effects. 216 foreach (effect; effects_) 217 effect.apply(drawList); 218 } 219 220 /** 221 Called when the node is to be redrawn. 222 223 Params: 224 delta = Time since the last frame. 225 drawList = The drawlist for the active scene. 226 */ 227 override 228 void onDraw(float delta, DrawList drawList) { 229 if (!this.enabled) 230 return; 231 232 PartVars vars = PartVars( 233 tint: tint * props.get!vec3(PROP_TINT_RGB), 234 screenTint: screenTint * props.get!vec3(PROP_SCREEN_RGB), 235 opacity: opacity * props.get!float(PROP_OPACITY), 236 emissionStrength: emissionStrength * props.get!float(PROP_EMISSION_STRENGTH) 237 ); 238 239 drawList.setMesh(drawListSlot); 240 drawList.setVariables!PartVars(nid, vars); 241 drawList.setBlending(blendingMode); 242 drawList.setSources(textures); 243 drawList.next(); 244 } 245 246 /** 247 Called when the node should be drawn to a mask. 248 249 Params: 250 delta = Time since the last frame. 251 drawList = The drawlist for the active scene. 252 mode = The masking mode to draw with. 253 */ 254 override 255 void onDrawMask(float delta, DrawList drawList, MaskingMode mode) { 256 drawList.setMesh(drawListSlot); 257 drawList.setSources(textures); 258 drawList.setMasking(mode); 259 drawList.next(); 260 } 261 262 /** 263 Called when the node is to define its properties. 264 265 Call $(D propList.define) with a quark to do this. 266 267 Params: 268 propList = The property list to populate. 269 */ 270 override 271 void onDefineProperties(ref PropertyStore propList) { 272 super.onDefineProperties(propList); 273 274 propList.define!float(PROP_SCREEN_R, 0); 275 propList.define!float(PROP_SCREEN_G, 0); 276 propList.define!float(PROP_SCREEN_B, 0); 277 propList.define!float(PROP_TINT_R, 1); 278 propList.define!float(PROP_TINT_G, 1); 279 propList.define!float(PROP_TINT_B, 1); 280 propList.define!float(PROP_OPACITY, 1); 281 propList.define!float(PROP_EMISSION_STRENGTH, 1); 282 283 // Define combined overlays. 284 propList.defineOverlay!vec3(PROP_SCREEN_RGB, propList.offsetOf(PROP_SCREEN_R)); 285 propList.defineOverlay!vec3(PROP_TINT_RGB, propList.offsetOf(PROP_TINT_R)); 286 } 287 288 public: 289 290 /** 291 Whether the node can be used as a source of masking operations. 292 */ 293 override @property bool isMasking() @nogc nothrow pure => true; 294 295 /** 296 The mesh of the part.. 297 */ 298 final @property Mesh mesh() @nogc => mesh_; 299 final @property void mesh(Mesh value) @nogc { 300 if (value is mesh_) 301 return; 302 303 if (mesh_) 304 mesh_.release(); 305 306 this.mesh_ = value.retained(); 307 this.deformed_.parent = value; 308 this.base_.parent = value; 309 } 310 311 /** 312 Mesh effects applied to the part. 313 */ 314 final @property MeshEffect[] effects() => effects_; 315 316 /** 317 The base matrix of the object before any parameters have been applied. 318 */ 319 override @property Basis deformBaseMatrix() @nogc => baseMatrix; 320 321 /** 322 World matrix of the deformable object. 323 */ 324 override @property Basis deformMatrix() @nogc => matrix; 325 326 /** 327 The base position of the deformable's points. 328 */ 329 @property const(vec2)[] basePoints() => base_.points; 330 331 /** 332 The points which may be deformed by the deformer. 333 */ 334 override @property vec2[] deformPoints() => deformed_.points; 335 336 /** 337 List of textures this part can use 338 339 TODO: use more than texture 0 340 */ 341 Texture[IN_MAX_ATTACHMENTS] textures; 342 343 /** 344 Blending mode 345 */ 346 BlendMode blendingMode = BlendMode.normal; 347 348 /** 349 Opacity of the mesh 350 */ 351 float opacity = 1; 352 353 /** 354 Strength of emission 355 */ 356 float emissionStrength = 1; 357 358 /** 359 Multiplicative tint color 360 */ 361 vec3 tint = vec3(1, 1, 1); 362 363 /** 364 Screen tint color 365 */ 366 vec3 screenTint = vec3(0, 0, 0); 367 368 /// Destructor 369 ~this() { 370 mesh_.release(); 371 nogc_delete(deformed_); 372 nogc_delete(base_); 373 foreach (texture; textures) { 374 if (texture) 375 texture.release(); 376 } 377 } 378 379 /** 380 Constructs a new part 381 */ 382 this(Node parent = null) { 383 super(parent); 384 } 385 386 /** 387 Constructs a new part 388 */ 389 this(MeshData data, Node parent = null) { 390 this(data, inNewGUID(), parent); 391 } 392 393 /** 394 Constructs a new part 395 */ 396 this(MeshData data, GUID guid, Node parent = null) { 397 super(guid, parent); 398 399 this.deformed_ = nogc_new!DeformedMesh(); 400 this.base_ = nogc_new!DeformedMesh(); 401 this.mesh = Mesh.fromMeshData(data); 402 } 403 404 /** 405 Constructs a new part 406 */ 407 this(MeshData data, Texture[] textures, Node parent = null) { 408 this(data, textures, inNewGUID(), parent); 409 } 410 411 /** 412 Constructs a new part 413 */ 414 this(MeshData data, Texture[] textures, GUID guid, Node parent = null) { 415 this(data, guid, parent); 416 foreach (i; 0 .. TextureUsage.COUNT) { 417 if (i >= textures.length) 418 break; 419 this.textures[i] = textures[i]; 420 } 421 } 422 423 /** 424 Resets the deformation for the IDeformable. 425 */ 426 override 427 void resetDeform() { 428 deformed_.reset(); 429 430 base_.reset(); 431 base_.pushMatrix(this.deformBaseMatrix); 432 } 433 434 /** 435 Deforms the IDeformable. 436 437 Params: 438 deformed = The deformation delta. 439 absolute = Whether the deformation is absolute, 440 replacing the original deformation. 441 */ 442 override 443 void deform(vec2[] deformed, bool absolute = false) { 444 deformed_.deform(deformed); 445 } 446 447 /** 448 Deforms a single vertex in the IDeformable 449 450 Params: 451 offset = The offset into the point list to deform. 452 deform = The deformation delta. 453 absolute = Whether the deformation is absolute, 454 replacing the original deformation. 455 */ 456 override 457 void deform(size_t offset, vec2 deform, bool absolute = false) { 458 deformed_.deform(offset, deform); 459 } 460 461 /** 462 Adds a mesh effect to the part. 463 464 Param: 465 T = The type of the mesh effect to add. 466 args = The arguments to pass to the effect's constructor. 467 */ 468 void addEffect(T, Args...)(Args args) 469 if (is(T : MeshEffect)) { 470 this.effects_ ~= nogc_new!T(this, args); 471 } 472 473 /** 474 Adds a mesh effect to the part. 475 476 Param: 477 effect = The mesh effect to add. 478 */ 479 void addEffect(MeshEffect effect) { 480 if (this.effects_.find(effect) == -1) 481 this.effects_ ~= effect.retained(); 482 } 483 484 /** 485 Removes a given mesh effect from this part. 486 487 Params: 488 effect = The effect to remove. 489 */ 490 void removeEffect(MeshEffect effect) { 491 auto i = this.effects_.find(effect); 492 if (i >= 0) { 493 this.effects_.removeAt(i); 494 effect.release(); 495 } 496 } 497 } 498 499 mixin Register!(Part, in_node_registry);