Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
akasaki1211 committed Jul 29, 2024
2 parents 06f50cc + 1d3c273 commit cff30ca
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 39 deletions.
Binary file added .images/ground_collision.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .images/mesh_col_cutoff.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .images/mesh_collision.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .images/radius.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 49 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,32 +66,66 @@ All of the above checkpoints can be satisfied by making joints as usual.
## Features
### Collisions

4 types of collisions are available: ground, sphere, capsule, and infinite plane. Colliders can be created with [maya_expressionCollision](https://github.com/akasaki1211/maya_expressionCollision).

![collisions](.images/collisions.gif)
5 types of collisions are available: **Sphere, Capsule, Infinite plane, Mesh, and Ground**.

- `Radius` : Radius of the end-joint.
- `Iterations` : Higher values increase the accuracy of collisions. Recommended value is 3 to 5. 0 disables collisionss.
- `Enable Ground Col` : Enable ground collision.
- `Ground Height` : Height of the ground.
- `Sphere Col Matrix` : Connect the sphere collider worldMatrix.
- `Sphere Col Radius` : Radius of sphere collider.
- `Capsule Col Matrix A` `Capsule Col Matrix B` : Connect worldMatrix on one side of the capsule collider.
- `Capsule Col Radius A` `Capsule Col Radius B` : Connect the radius of one side of the capsule collider.
- `Infinite Plane Col Matrix` : Connect worldMatrix of infinite plane collider.
- `Iterations` : Higher values increase the accuracy of collisions. Recommended value is 3 to 5. 0 disables collisions.

The attributes that connect the collider are in a list, so you can use more than one. Ground Collision do not need connections.
Collisions are determined by the sphere centered around the end-joint. The radius of the end-joint is set with the Radius attribute.

![radius](.images/radius.png)

The attributes that connect the collider are in a list, so you can use more than one. Ground Collision does not need connections.

![collider_connections](.images/collider_connections.png)

> 💡**Visualize Radius**
> Place a nurbsSphere or implicitSphere as a child of end-joint and connect `Radius`.
> ![visualize_radius](.images/visualize_radius.png)
#### Sphere, Capsule, Infinite plane

![collisions](.images/collisions.gif)

The required connections for these three types of collisions are as follows.

- `Sphere Col Matrix` : Connect the sphere collider worldMatrix.
- `Sphere Col Radius` : Radius of sphere collider.
- `Capsule Col Matrix A` `Capsule Col Matrix B` : Connect worldMatrix on one side of the capsule collider.
- `Capsule Col Radius A` `Capsule Col Radius B` : Connect the radius of one side of the capsule collider.
- `Infinite Plane Col Matrix` : Connect worldMatrix of infinite plane collider.

> 💡**Note**
> Colliders do not necessarily need to use [expcol](https://github.com/akasaki1211/maya_expressionCollision). It can be anything as long as the required attributes are connected.
> Colliders can be created with [maya_expressionCollision (expcol)](https://github.com/akasaki1211/maya_expressionCollision), but it is not necessary to use it. Any collider can be used as long as the required attributes are connected.
> ![collider_note](.images/collider_note.gif)
#### Mesh (*Experimental)

![mesh_collision](.images/mesh_collision.gif)

Any mesh can be used for collisions. Compared to other types of collisions, mesh collisions are more loaded and less stable, so their use is not recommended.

- `Mesh Collider` : Connect worldMesh of any mesh.
- `Mesh Col Cutoff` : Max distance for mesh collision detection.

> 💡**Note**
> - As this is an **experimental** feature, it may be unstable.
> - All edges of the collision mesh should be made with **soft edges**.
> - If the collision mesh has a large number of polygons, it will increase the processing load.
> - If the collision mesh is not a closed shape, use `Mesh Col Cutoff` to control the range of detections.
>
> ![mesh_col_cutoff](.images/mesh_col_cutoff.gif)
#### Ground

![ground_collision](.images/ground_collision.png)

A horizontal infinite plane collision. It can be used only with the enable and ground height attribute, no connections are needed.

- `Enable Ground Col` : Enable ground collision.
- `Ground Height` : Height of the ground.


### Angle Limitation

The `Angle Limit` attribute allows for limiting the rotation angle of bones. It has a higher priority than collision, so depending on the posture, it may be penetrated in the colliders.
Expand All @@ -100,7 +134,7 @@ The `Angle Limit` attribute allows for limiting the rotation angle of bones. It

> 💡**Visualize Angle**
> Place an implicitCone node and a transform nodes in the same space as the joint, and set and connect their attributes as in the following image:
> ![visualize_angle](.images/visualize_angle.png)
> ![visualize_angle](.images/visualize_angle.png)
> ⚠️If joint.ty and tz contain values, cone direction will not match accurately.
### Specify Target Pose
Expand Down Expand Up @@ -149,7 +183,7 @@ Pre-built `boneDynamicsNode.mll` in the [plug-ins](./plug-ins) folder. Install t
|Maya 2022 Update 5 win64|[Download](./plug-ins/2022/boneDynamicsNode.mll)|
|Maya 2023 Update 3 win64|[Download](./plug-ins/2023/boneDynamicsNode.mll)|
|Maya 2024 Update 2 win64|[Download](./plug-ins/2024/boneDynamicsNode.mll)|
|Maya 2025 win64|[Download](./plug-ins/2025/boneDynamicsNode.mll)|
|Maya 2025 Update 1 win64|[Download](./plug-ins/2025/boneDynamicsNode.mll)|

## How to Build
For example, Maya 2024 in Windows:
Expand All @@ -167,10 +201,6 @@ cmake . -Bbuild_2024 -G "Visual Studio 17 2022" -A x64
cmake --build build_2024
```

## TODO
- [ ] Additional Force
- [ ] Stretchable

## Links
- [Maya用お手軽ボーンダイナミクスノード「boneDynamicsNode」詳細解説 - Qiita](https://qiita.com/akasaki1211/items/ddae66ec2d89d21bb2f4)
- [boneDynamicsNode Demo - Example of integration into FK rig - YouTube](https://www.youtube.com/watch?v=O5cpcMI_Jz0)
Binary file modified plug-ins/2022/boneDynamicsNode.mll
Binary file not shown.
Binary file modified plug-ins/2023/boneDynamicsNode.mll
Binary file not shown.
Binary file modified plug-ins/2024/boneDynamicsNode.mll
Binary file not shown.
Binary file modified plug-ins/2025/boneDynamicsNode.mll
Binary file not shown.
27 changes: 17 additions & 10 deletions sample_scripts/advanced_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ def create_dynamics_node(
cmds.setAttr(angle_cone_tm + '.overrideDisplayType', 2)

# collision radius
if colliders:
radius_sphere = cmds.createNode("implicitSphere")
cmds.connectAttr(boneDynamicsNode + '.radius', radius_sphere + '.radius', f=True)
radius_sphere_tm = cmds.listRelatives(radius_sphere, p=True)[0]
cmds.parent(radius_sphere_tm, end, r=True)
cmds.setAttr(radius_sphere_tm + '.overrideEnabled', 1)
cmds.setAttr(radius_sphere_tm + '.overrideDisplayType', 2)
radius_sphere = cmds.createNode("implicitSphere")
cmds.connectAttr(boneDynamicsNode + '.radius', radius_sphere + '.radius', f=True)
radius_sphere_tm = cmds.listRelatives(radius_sphere, p=True)[0]
cmds.parent(radius_sphere_tm, end, r=True)
cmds.setAttr(radius_sphere_tm + '.overrideEnabled', 1)
cmds.setAttr(radius_sphere_tm + '.overrideDisplayType', 2)
cmds.connectAttr(boneDynamicsNode + '.iterations', radius_sphere_tm + '.v', f=True)

sphere_col_idx = 0
capsule_col_idx = 0
iplane_col_cidx = 0
iplane_col_idx = 0
mesh_col_idx = 0

for col in colliders:

Expand All @@ -81,6 +82,12 @@ def create_dynamics_node(
continue

if not cmds.attributeQuery('colliderType', n=col, ex=True):
col_shape = cmds.listRelatives(col, s=True, f=True)
if col_shape:
if cmds.nodeType(col_shape[0]) == 'mesh':
cmds.connectAttr(col_shape[0] + '.worldMesh[0]', boneDynamicsNode + '.meshCollider[{}]'.format(mesh_col_idx), f=True)
mesh_col_idx += 1
continue
print("Skip: {} has no 'colliderType' attribute.".format(col))
continue

Expand All @@ -103,8 +110,8 @@ def create_dynamics_node(
capsule_col_idx += 1

elif colliderType == 'infinitePlane':
cmds.connectAttr(col + ".worldMatrix[0]", boneDynamicsNode + ".infinitePlaneCollider[{}].infinitePlaneColMatrix".format(iplane_col_cidx), f=True)
iplane_col_cidx += 1
cmds.connectAttr(col + ".worldMatrix[0]", boneDynamicsNode + ".infinitePlaneCollider[{}].infinitePlaneColMatrix".format(iplane_col_idx), f=True)
iplane_col_idx += 1

return boneDynamicsNode

Expand Down
71 changes: 62 additions & 9 deletions src/boneDynamicsNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ MObject boneDynamicsNode::s_capsuleColRadB;
MObject boneDynamicsNode::s_iPlaneCollider;
MObject boneDynamicsNode::s_iPlaneColMtx;

MObject boneDynamicsNode::s_meshCollider;
MObject boneDynamicsNode::s_meshColCutoff;

MObject boneDynamicsNode::s_outputRotate;

boneDynamicsNode::boneDynamicsNode()
Expand Down Expand Up @@ -79,6 +82,7 @@ MStatus boneDynamicsNode::initialize()
MFnCompoundAttribute cmpAttr;
MFnMatrixAttribute mAttr;
MFnUnitAttribute uAttr;
MFnTypedAttribute tAttr;
MObject x, y, z;

s_enable = nAttr.create("enable", "en", MFnNumericData::kBoolean, true);
Expand Down Expand Up @@ -255,6 +259,16 @@ MStatus boneDynamicsNode::initialize()
cmpAttr.setReadable(true);
cmpAttr.setUsesArrayDataBuilder(true);

// meshCollider
s_meshCollider = tAttr.create("meshCollider", "mc", MFnData::kMesh);
tAttr.setArray(true);
tAttr.setReadable(true);
tAttr.setUsesArrayDataBuilder(true);

s_meshColCutoff = nAttr.create("meshColCutoff", "mcc", MFnNumericData::kDouble, 10.0);
nAttr.setKeyable(true);
nAttr.setMin(0);

// output attributes
x = uAttr.create("outputRotateX", "outrx", MFnUnitAttribute::kAngle, 0.0);
y = uAttr.create("outputRotateY", "outry", MFnUnitAttribute::kAngle, 0.0);
Expand Down Expand Up @@ -311,7 +325,9 @@ MStatus boneDynamicsNode::initialize()
addAttribute(s_capsuleCollider);
addAttribute(s_iPlaneColMtx);
addAttribute(s_iPlaneCollider);

addAttribute(s_meshCollider);
addAttribute(s_meshColCutoff);

addAttribute(s_outputRotate);


Expand Down Expand Up @@ -362,6 +378,8 @@ MStatus boneDynamicsNode::initialize()
attributeAffects(s_capsuleCollider, s_outputRotate);
attributeAffects(s_iPlaneColMtx, s_outputRotate);
attributeAffects(s_iPlaneCollider, s_outputRotate);
attributeAffects(s_meshCollider, s_outputRotate);
attributeAffects(s_meshColCutoff, s_outputRotate);

return MS::kSuccess;
}
Expand Down Expand Up @@ -400,16 +418,10 @@ void boneDynamicsNode::angleLimit(const MVector& pivot, const MVector& a, MVecto
if (currentAngle > limitAngle)
{
const double rotateAngle = limitAngle - currentAngle;

const MVector axisNormal = axis.normal();
const double d = currentVec * axisNormal;
const MVector projectVec = axisNormal * d;
const MVector orthogonalVec = currentVec - projectVec;

// rotate vector around axis
const MVector rotatedVec = projectVec + orthogonalVec * cos(rotateAngle) + (axisNormal ^ orthogonalVec) * sin(rotateAngle);
const MVector rotatedVec = currentVec * cos(rotateAngle) + (axisNormal ^ currentVec) * sin(rotateAngle);

// update position
b = pivot + rotatedVec;
}
}
Expand All @@ -420,6 +432,12 @@ void boneDynamicsNode::distanceConstraint(const MVector& pivot, MVector& point,
point = pivot + ((point - pivot).normal() * distance);
}

void boneDynamicsNode::getClosestPoint(const MObject& mesh, const MPoint& point, MPoint& closestPoint, MVector& closestNormal)
{
MFnMesh fnMesh(mesh);
fnMesh.getClosestPointAndNormal(point, closestPoint, closestNormal, MSpace::kWorld);
}

MStatus boneDynamicsNode::compute(const MPlug& plug, MDataBlock& data)
{
const MPlug computePlug = plug.isChild() ? plug.parent() : plug;
Expand Down Expand Up @@ -573,6 +591,10 @@ MStatus boneDynamicsNode::compute(const MPlug& plug, MDataBlock& data)
MArrayDataHandle& iPlaneColArrayHandle = data.inputArrayValue(s_iPlaneCollider);
const unsigned int pcCount = iPlaneColArrayHandle.elementCount();

MArrayDataHandle& meshColArrayHandle = data.inputArrayValue(s_meshCollider);
const unsigned int mcCount = meshColArrayHandle.elementCount();
const double meshColCutoff = data.inputValue(s_meshColCutoff).asDouble();

MTransformationMatrix sphereCol_m;
MVector sphereCol_p;
double sphereCol_s[3];
Expand All @@ -587,6 +609,7 @@ MStatus boneDynamicsNode::compute(const MPlug& plug, MDataBlock& data)
MTransformationMatrix iPlaneCol_m;
MVector iPlaneCol_p;
MVector iPlaneCol_n;
MObject meshCol;

MVector v;
double r;
Expand Down Expand Up @@ -676,11 +699,41 @@ MStatus boneDynamicsNode::compute(const MPlug& plug, MDataBlock& data)
nextPosition = nextPosition - (iPlaneCol_n * (distancePointPlane - radius));
};
};

//mesh collision (experimental)
for (unsigned int i = 0; i < mcCount; i++) {
meshColArrayHandle.jumpToArrayElement(i);
MDataHandle& meshCollider = meshColArrayHandle.inputValue();
meshCol = meshCollider.asMesh();

// Get the closest point and closest normal on a mesh
MPoint closestPoint;
MVector closestNormal;
getClosestPoint(meshCol, MPoint(nextPosition), closestPoint, closestNormal);
closestNormal = closestNormal.normal();

// Get the vector from the closest point to the current point
const MVector contactVec = nextPosition - MVector(closestPoint);

// Get the distance to the surface
const double distanceContactPoint = closestNormal * contactVec;

// If it is penetrated in the surface, it will be pushed out
if (distanceContactPoint - radius < 0.0) {
// If it is completely penetrated, it uses the closest normal to push out, if it is only in contact, it uses the contactVec to push out.
MVector pushOutPos = MVector(closestPoint) + ((distanceContactPoint < 0) ? closestNormal : contactVec.normal()) * radius;

// If the position after push out is within meshColCutoff from the current position, it will be pushed out.
if ((nextPosition - pushOutPos).length() < meshColCutoff) {
nextPosition = pushOutPos;
}
}
};

//ground collision
if (enableGroundCol)
{
nextPosition[1] = nextPosition[1] < (groundHeight + radius) ? (groundHeight + radius) : nextPosition[1];
nextPosition[1] = nextPosition[1] < (groundHeight + radius) ? (groundHeight + radius) : nextPosition[1]; // Y-Up only
};

// angle limit
Expand Down
6 changes: 6 additions & 0 deletions src/boneDynamicsNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnMesh.h>
#include <maya/MTime.h>
#include <maya/MVector.h>
#include <maya/MPoint.h>
Expand Down Expand Up @@ -85,6 +87,9 @@ class boneDynamicsNode : public MPxNode

static MObject s_iPlaneCollider; // infinitePlaneCollider array
static MObject s_iPlaneColMtx; // infinitePlaneCollider matrix

static MObject s_meshCollider; // meshCollider array
static MObject s_meshColCutoff; // max distance for mesh collision detection

// output
static MObject s_outputRotate; // output euler rotation
Expand All @@ -94,6 +99,7 @@ class boneDynamicsNode : public MPxNode
double degToRad(double deg);
void angleLimit(const MVector& pivot, const MVector& a, MVector& b, const double limitAngle);
void distanceConstraint(const MVector& pivot, MVector& point, double distance);
void getClosestPoint(const MObject& mesh, const MPoint& position, MPoint& closestPoint, MVector& closestNormal);

static const MEulerRotation::RotationOrder ROTATION_ORDER = MEulerRotation::RotationOrder::kXYZ;

Expand Down
2 changes: 1 addition & 1 deletion src/pluginMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj, "Hiroyuki Akasaki", "0.2.1", "Any");
MFnPlugin plugin(obj, "Hiroyuki Akasaki", "0.3.0", "Any");

status = plugin.registerNode(boneDynamicsNodeName, boneDynamicsNode::s_id, boneDynamicsNode::creator, boneDynamicsNode::initialize);
if (!status) {
Expand Down

0 comments on commit cff30ca

Please sign in to comment.