From 28c8e52eddfb25e71f79813a104dccdcbb8b805f Mon Sep 17 00:00:00 2001 From: Michael Abrahams Date: Fri, 19 Feb 2021 12:03:20 -0500 Subject: [PATCH] Add spring node gravity and simple collision Features for the spring node with negligible runtime cost. Gravity adds a target bias in the -y direction. Add support for two types of colliders: a ground plane and a list of spherical colliders. The ground plane prevents the spring from crossing below y=0. Spherical colliders repel the springs from a point in space. For a geometrical representation, create a NURBS sphere, connect its world space position to the input position, and its world space scale to the collider radius. Collision can be given a softness value to create soft or hard collisions. Zero softness prevents springs from crossing the barriers entirely. --- src/mgear_solvers.h | 13 ++++- src/springNode.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/mgear_solvers.h b/src/mgear_solvers.h index 29c47ab..05e7e65 100644 --- a/src/mgear_solvers.h +++ b/src/mgear_solvers.h @@ -533,11 +533,22 @@ class mgear_springNode : public MPxNode static MObject aGoalY; static MObject aGoalZ; static MObject aDamping; + static MObject aGravity; static MObject aStiffness; static MObject aTime; + static MObject aCollider; + static MObject aColliderX; + static MObject aColliderY; + static MObject aColliderZ; + static MObject aColliderRadius; + static MObject aColliderSoftness; + static MObject aColliderList; //static MObject aParentInverse; static MObject aSpringIntensity; + static MObject aSpringActive; + static MObject aUseGroundPlane; + static MObject aGroundPlaneHeight; ////variables bool _initialized; @@ -713,4 +724,4 @@ MTransformationMatrix mapObjectPoseToWorldSpace(MTransformationMatrix objectSpac MTransformationMatrix interpolateTransform(MTransformationMatrix xf1, MTransformationMatrix xf2, double blend); -#endif \ No newline at end of file +#endif diff --git a/src/springNode.cpp b/src/springNode.cpp index 64ac44b..ee22ca6 100644 --- a/src/springNode.cpp +++ b/src/springNode.cpp @@ -32,6 +32,9 @@ Date: 2016 / 10 / 10 // INCLUDE ///////////////////////////////////////////////// #include "mgear_solvers.h" +#include +#include +#include ///////////////////////////////////////////////// // GLOBAL @@ -49,8 +52,22 @@ MObject mgear_springNode::aGoalZ; MObject mgear_springNode::aDamping; MObject mgear_springNode::aStiffness; MObject mgear_springNode::aTime; + +MObject mgear_springNode::aGravity; + +MObject mgear_springNode::aCollider; +MObject mgear_springNode::aColliderX; +MObject mgear_springNode::aColliderY; +MObject mgear_springNode::aColliderZ; +MObject mgear_springNode::aColliderRadius; +MObject mgear_springNode::aColliderList; +MObject mgear_springNode::aColliderSoftness; +MObject mgear_springNode::aUseGroundPlane; +MObject mgear_springNode::aGroundPlaneHeight; + //MObject mgear_springNode::aParentInverse; MObject mgear_springNode::aSpringIntensity; +MObject mgear_springNode::aSpringActive; mgear_springNode::mgear_springNode(){} @@ -75,6 +92,8 @@ MStatus mgear_springNode::initialize() MFnNumericAttribute nAttr; MFnUnitAttribute uAttr; MFnMatrixAttribute mAttr; + MFnCompoundAttribute cmpAttr; + aOutput = nAttr.createPoint("output", "out"); // initializing attribute nAttr.setWritable(false); @@ -116,6 +135,68 @@ MStatus mgear_springNode::initialize() addAttribute(aSpringIntensity); attributeAffects(aSpringIntensity, aOutput); + aSpringActive = nAttr.create("active", "active", MFnNumericData::kFloat, 1.0f); + nAttr.setKeyable(true); + nAttr.setMin(0.0f); + nAttr.setMax(1.0f); + addAttribute(aSpringActive); + attributeAffects(aSpringActive, aOutput); + + aGravity = nAttr.create("gravity", "gravity", MFnNumericData::kFloat, 0.0f); + nAttr.setKeyable(true); + nAttr.setMin(-1000.0f); + nAttr.setMax(1000.0f); + addAttribute(aGravity); + attributeAffects(aGravity, aOutput); + + aColliderSoftness = nAttr.create("collide_softness", "collide_softness", MFnNumericData::kFloat, 0.5f); + nAttr.setKeyable(true); + nAttr.setMin(0.0f); + nAttr.setMax(1.0f); + addAttribute(aColliderSoftness); + attributeAffects(aColliderSoftness, aOutput); + + aUseGroundPlane = nAttr.create("use_ground", "use_ground", MFnNumericData::kBoolean, 0); + nAttr.setKeyable(true); + addAttribute(aUseGroundPlane); + attributeAffects(aUseGroundPlane, aOutput); + + aGroundPlaneHeight = nAttr.create("ground_height", "ground_height", MFnNumericData::kFloat, 0.0f); + nAttr.setKeyable(true); + addAttribute(aGroundPlaneHeight); + attributeAffects(aGroundPlaneHeight, aOutput); + + + aCollider = nAttr.createPoint("collider", "collider"); + aColliderX = nAttr.child(0); + aColliderY = nAttr.child(1); + aColliderZ = nAttr.child(2); + nAttr.setKeyable(true); + McheckErr(nAttr.setDisconnectBehavior(MFnAttribute::kDelete), "setDisconnectBehavior"); + addAttribute(aCollider); + attributeAffects(aCollider, aOutput); + + + aColliderRadius = nAttr.create("collide_radius", "collide_radius", MFnNumericData::kFloat, 0.0f); + nAttr.setKeyable(true); + nAttr.setMin(-1000.0f); + nAttr.setMax(1000.0f); + McheckErr(nAttr.setDisconnectBehavior(MFnAttribute::kDelete), "setDisconnectBehavior"); + addAttribute(aColliderRadius); + attributeAffects(aColliderRadius, aOutput); + + + aColliderList = cmpAttr.create("colliders_list", "colliders_list", &status); + cmpAttr.setArray(true); + cmpAttr.setKeyable(true); + McheckErr(cmpAttr.addChild(aCollider), "compoundAttr.addChild"); + McheckErr(cmpAttr.addChild(aColliderRadius), "compoundAttr.addChild"); + McheckErr(cmpAttr.setDisconnectBehavior(MFnAttribute::kDelete), "setDisconnectBehavior"); + McheckErr(addAttribute(aColliderList), "addAttribute"); + McheckErr(attributeAffects(aColliderList, aOutput), "attributeAffects"); + + + /*aParentInverse = mAttr.create("parentInverse", "parentInverse"); mAttr.setStorable(true); mAttr.setKeyable(true); @@ -144,6 +225,7 @@ MStatus mgear_springNode::compute(const MPlug& plug, MDataBlock& data) // getting inputs attributes float damping = data.inputValue(aDamping, &status).asFloat(); float stiffness = data.inputValue(aStiffness, &status).asFloat(); + float gravity = data.inputValue(aGravity, &status).asFloat(); //MVector goal = data.inputValue(aGoal, &status).asVector(); MDataHandle h; @@ -151,14 +233,12 @@ MStatus mgear_springNode::compute(const MPlug& plug, MDataBlock& data) h = data.inputValue(aGoal, &status); McheckStatusAndReturnIt(status); goal = h.asFloatVector(); - // float aGoalX = goal.x; - // float aGoalY = goal.y; - // float aGoalZ = goal.z; MTime currentTime = data.inputValue(aTime, &status).asTime(); //MMatrix parentInverse = data.inputValue(aParentInverse, &status).asMatrix(); float springIntensity = data.inputValue(aSpringIntensity, &status).asFloat(); + float springActive = data.inputValue(aSpringActive, &status).asFloat(); if (_initialized == false) { @@ -185,8 +265,42 @@ MStatus mgear_springNode::compute(const MPlug& plug, MDataBlock& data) MFloatVector velocity = (_currentPosition - _previousPosition) * (1.0 - damping); MVector newPosition = _currentPosition + velocity; MFloatVector goalForce = (goal - newPosition) * stiffness; + goalForce.y += gravity; newPosition += goalForce; + + + // Apply collision + float colliderStrength = 1.0 - data.inputValue(aColliderSoftness, &status).asFloat(); + MVector colliderPos; + MVector distFromCollider; + float colliderRadius; + float collideAmount; + MArrayDataHandle arrayHandle = data.inputArrayValue(aColliderList, &status); + McheckErr(status, "arrayHandle construction for aColliderList failed\n"); + unsigned count = arrayHandle.elementCount(); + for( int i = 0; i < count; i++) { + arrayHandle.jumpToArrayElement(i); + h = arrayHandle.inputValue(&status); + McheckErr(status, "handle evaluation failed\n"); + colliderPos = h.child(aCollider).asFloatVector(); + colliderRadius = h.child(aColliderRadius).asFloat(); + distFromCollider = newPosition - colliderPos; + collideAmount = (colliderRadius - distFromCollider.length()) / distFromCollider.length() * colliderStrength; + collideAmount = collideAmount > 0 ? collideAmount : 0; // Only push outward + newPosition += collideAmount * distFromCollider; // Move radially + } + + + bool useGroundPlane = data.inputValue(aUseGroundPlane, &status).asBool(); + float groundPlaneHeight = data.inputValue(aGroundPlaneHeight, &status).asFloat(); + + float distFromGround = (newPosition[1] - groundPlaneHeight); + float collideAmount = -1.0f * distFromGround * colliderStrength; + collideAmount = collideAmount > 0 ? collideAmount : 0; + newPosition[1] += ((float)useGroundPlane) * collideAmount; + + // store the states for the next calculation _previousPosition = _currentPosition; _currentPosition = newPosition; @@ -194,7 +308,7 @@ MStatus mgear_springNode::compute(const MPlug& plug, MDataBlock& data) //multipply the position by the spring intensity //calculamos depues de los states, para no afectarlos - newPosition = goal + ((newPosition - goal) * springIntensity); + newPosition = goal + (((newPosition - goal) * springIntensity) * springActive); //Setting the output in local space // esto lo hacemos depues de hacer el store de los states @@ -210,4 +324,4 @@ MStatus mgear_springNode::compute(const MPlug& plug, MDataBlock& data) return MS::kSuccess; -} \ No newline at end of file +}