code:
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true

# C# files
[*.cs]

#### Core EditorConfig Options ####

# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4

# New line preferences
end_of_line = crlf
insert_final_newline = false

#### .NET Coding Conventions ####

# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false

# this. and Me. preferences
dotnet_style_qualification_for_event = true:warning
dotnet_style_qualification_for_field = true:warning
dotnet_style_qualification_for_method = true:warning
dotnet_style_qualification_for_property = true:warning

# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning

# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent

# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent

# Expression-level preferences
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion

# Field preferences
dotnet_style_readonly_field = true:suggestion

# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion

#### C# Coding Conventions ####

# var preferences
csharp_style_var_elsewhere = false:warning
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = true:warning

# Expression-bodied members
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:suggestion

# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion

# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion

# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async

# Code-block preferences
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = true:suggestion

# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent

# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent

#### C# Formatting Rules ####

# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

#### Naming styles ####

# Naming rules

dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

# Symbol specifications

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.types.required_modifiers =

# Naming styles

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

repo_name: MadCake/Unity

path: .editorconfig

license: mit


code:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;

#if UNITY_EDITOR

/// <summary>
/// Sorts bone indexes in imported meshes.<br>
/// SkinnedMeshRenderer requires bone indexes to be sorted based on hierarchy.
/// </summary>
public class AssetPostProcessorReorderBones : AssetPostprocessor
{
	void OnPostprocessModel(GameObject g)
	{
        this.Process(g);
	}

	void Process(GameObject g)
	{
		SkinnedMeshRenderer smr = g.GetComponentInChildren<SkinnedMeshRenderer>();
		if (smr == null)
		{
			Debug.LogWarning("Unable to find Renderer" + smr.name);
			return;
		}

		//list of bones
		List<Transform> boneTransforms = smr.bones.ToList();

		//sort based on hierarchy
		boneTransforms.Sort(CompareTransform);

		//record bone index mappings (richardf advice)
		//build a Dictionary<int, int> that records the old bone index => new bone index mappings,
		//then run through every vertex and just do boneIndexN = dict[boneIndexN] for each weight on each vertex.
		var remap = new Dictionary<int, int>();
		for (int i = 0; i < smr.bones.Length; i++)
		{
			remap[i] = boneTransforms.IndexOf(smr.bones[i]);
		}

		//remap bone weight indexes
		BoneWeight[] bw = smr.sharedMesh.boneWeights;
		for (int i = 0; i < bw.Length; i++)
		{
			bw[i].boneIndex0 = remap[bw[i].boneIndex0];
			bw[i].boneIndex1 = remap[bw[i].boneIndex1];
			bw[i].boneIndex2 = remap[bw[i].boneIndex2];
			bw[i].boneIndex3 = remap[bw[i].boneIndex3];
		}

		//remap bindposes
		var bp = new Matrix4x4[smr.sharedMesh.bindposes.Length];
		for (int i = 0; i < bp.Length; i++)
		{
			bp[remap[i]] = smr.sharedMesh.bindposes[i];
		}

		//assign new data
		smr.bones = boneTransforms.ToArray();
		smr.sharedMesh.boneWeights = bw;
		smr.sharedMesh.bindposes = bp;
	}

	private static int CompareTransform(Transform A, Transform B)
	{
		if (B.IsChildOf(A))
        {
            return -1;
        }

        if (A.IsChildOf(B))
        {
            return -1;
        }

        return 0;
	}
}

#endif


code:
using UnityEngine;

///
/// Replaces Unity's default linear skinning with DQ skinning
///
/// Add this component to a GameObject that has SkinnedMeshRenderer attached.

/// Do not remove SkinnedMeshRenderer component!

/// Make sure that all materials of the animated object are using shader \"MadCake/Material/Standard hacked for DQ skinning\"
///
[RequireComponent(typeof(MeshFilter))]
public class DualQuaternionSkinner : MonoBehaviour
{
	///
	/// Bone orientation is required for bulge-compensation.

	/// Do not set directly, use custom editor instead.
	///
	public Vector3 boneOrientationVector = Vector3.up;

	///
	/// Do not edit directly, use SetViewFrustrumCulling() instead.
	///
	public bool viewFrustrumCulling = true;

	struct VertexInfo
	{
		// could use float3 instead of float4 but NVidia says structures not aligned to 128 bits are slow
		// https://developer.nvidia.com/content/understanding-structured-buffer-performance

		public Vector4 position;
		public Vector4 normal;
		public Vector4 tangent;

		public int boneIndex0;
		public int boneIndex1;
		public int boneIndex2;
		public int boneIndex3;

		public float weight0;
		public float weight1;
		public float weight2;
		public float weight3;

		public float compensation_coef;
	}

	struct MorphDelta
	{
		// could use float3 instead of float4 but NVidia says structures not aligned to 128 bits are slow
		// https://developer.nvidia.com/content/understanding-structured-buffer-performance

		public Vector4 position;
		public Vector4 normal;
		public Vector4 tangent;
	}

	struct DualQuaternion
	{
		public Quaternion rotationQuaternion;
		public Vector4 position;
	}

	const int numthreads = 1024;    // must be same in compute shader code
	const int textureWidth = 1024;  // no need to adjust compute shaders

	///
	/// Adjusts the amount of bulge-compensation.
	///
	[Range(0,1)]
	public float bulgeCompensation  = 0;

	public ComputeShader shaderComputeBoneDQ;
	public ComputeShader shaderDQBlend;
	public ComputeShader shaderApplyMorph;

	///
	/// Indicates whether DualQuaternionSkinner is currently active.
	///
	public bool started { get; private set; } = false;

	Matrix4x4[] poseMatrices;

	ComputeBuffer bufPoseMatrices;
	ComputeBuffer bufSkinnedDq;
	ComputeBuffer bufBindDq;

	ComputeBuffer bufVertInfo;
	ComputeBuffer bufMorphTemp_1;
	ComputeBuffer bufMorphTemp_2;

	ComputeBuffer bufBoneDirections;

	ComputeBuffer[] arrBufMorphDeltas;

	float[] morphWeights;

	MeshFilter mf
	{
		get
		{
			if (this._mf == null)
			{
				this._mf = this.GetComponent<MeshFilter>();
			}

			return this._mf;
		}
	}
	MeshFilter _mf;

	MeshRenderer mr
	{
		get
		{
			if (this._mr == null)
			{
				this._mr = this.GetComponent<MeshRenderer>();
				if (this._mr == null)
				{
					this._mr = this.gameObject.AddComponent<MeshRenderer>();
				}
			}

			return this._mr;
		}
	}
	MeshRenderer _mr;

	SkinnedMeshRenderer smr
	{
		get
		{
			if (this._smr == null)
			{
				this._smr = this.GetComponent<SkinnedMeshRenderer>();
			}

			return this._smr;
		}
	}
	SkinnedMeshRenderer _smr;

	MaterialPropertyBlock materialPropertyBlock;

	Transform[] bones;
	Matrix4x4[] bindPoses;

	/*
		Vulkan and OpenGL only support ComputeBuffer in compute shaders
		passing data to the vertex and fragment shaders is done through RenderTextures

		using ComputeBuffers would improve the efficiency slightly but it would only work with Dx11

		layout is as such:
			rtSkinnedData_1			float4			vertex.xyz,	normal.x
			rtSkinnedData_2			float4			normal.yz,	tangent.xy
			rtSkinnedData_3			float2			tangent.zw
	*/
	RenderTexture rtSkinnedData_1;
	RenderTexture rtSkinnedData_2;
	RenderTexture rtSkinnedData_3;

	int kernelHandleComputeBoneDQ;
	int kernelHandleDQBlend;
	int kernelHandleApplyMorph;

	public void SetViewFrustrumCulling(bool viewFrustrumculling)
	{
		if (this.viewFrustrumCulling == viewFrustrumculling)
			return;

		this.viewFrustrumCulling = viewFrustrumculling;

		if (this.started == true)
			UpdateViewFrustrumCulling();
	}

	void UpdateViewFrustrumCulling()
	{
		if (this.viewFrustrumCulling)
			this.mf.sharedMesh.bounds = this.smr.localBounds;
		else
			this.mf.sharedMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 100000000);
	}

	///
	/// Returns an array of currently applied blend shape weights.

	/// Default range is 0-100.

	/// It is possible to apply negative weights or exceeding 100.
	///
	/// <returns>Array of currently applied blend shape weights</returns>
	public float[] GetBlendShapeWeights()
	{
		float[] weights = new float[this.morphWeights.Length];
		for (int i = 0; i < weights.Length; i++)
        {
            weights[i] = this.morphWeights[i];
        }

        return weights;
	}

	///
	/// Applies blend shape weights from the given array.

	/// Default range is 0-100.

	/// It is possible to apply negative weights or exceeding 100.
	///
	/// <param name="weights">An array of weights to be applied</param>
	public void SetBlendShapeWeights(float[] weights)
	{
		if (weights.Length != this.morphWeights.Length)
        {
            throw new System.ArgumentException(
				"An array of weights must contain the number of elements " +
				$"equal to the number of available blendshapes. Currently " +
				$"{this.morphWeights.Length} blendshapes ara available but {weights.Length} weights were passed."
			);
        }

		for (int i = 0; i < weights.Length; i++)
        {
            this.morphWeights[i] = weights[i];
        }

		this.ApplyMorphs();
    }

	///
	/// Set weight for the blend shape with given index.

	/// Default range is 0-100.

	/// It is possible to apply negative weights or exceeding 100.
	///
	/// <param name="index">Index of the blend shape</param>
	/// <param name="weight">Weight to be applied</param>
	public void SetBlendShapeWeight(int index, float weight)
	{
		if (this.started == false)
		{
			this.GetComponent<SkinnedMeshRenderer>().SetBlendShapeWeight(index, weight);
			return;
		}

		if (index < 0 || index >= this.morphWeights.Length)
        {
            throw new System.IndexOutOfRangeException("Blend shape index out of range");
        }

        this.morphWeights[index] = weight;

		this.ApplyMorphs();
	}

	///
	/// Returns currently applied weight for the blend shape with given index.

	/// Default range is 0-100.

	/// It is possible to apply negative weights or exceeding 100.
	///
	/// <param name="index">Index of the blend shape</param>
	/// <returns>Currently applied weight</returns>
	public float GetBlendShapeWeight(int index)
	{
		if (this.started == false)
        {
            return this.GetComponent<SkinnedMeshRenderer>().GetBlendShapeWeight(index);
        }

        if (index < 0 || index >= this.morphWeights.Length)
        {
            throw new System.IndexOutOfRangeException("Blend shape index out of range");
        }

        return this.morphWeights[index];
	}

	///
	/// UnityEngine.Mesh that is currently being rendered.
	/// @see Mesh.GetBlendShapeName(int shapeIndex)
	/// @see Mesh.GetBlendShapeIndex(string blendShapeName)
	/// @see Mesh.blendShapeCount
	///
	public Mesh mesh
	{
		get
		{
			if (this.started == false)
            {
                return this.smr.sharedMesh;
            }

            return this.mf.sharedMesh;
        }
    }

	///
	/// If the value of boneOrientationVector was changed while DualQuaternionSkinner is active (started == true), UpdatePerVertexCompensationCoef() must be called in order for the change to take effect.
	///
	public void UpdatePerVertexCompensationCoef()
	{
		if (this.started == false)
		{
			return;
		}

		var vertInfos = new VertexInfo[this.mf.sharedMesh.vertexCount];
		this.bufVertInfo.GetData(vertInfos);

		for (int i = 0; i < vertInfos.Length; i++)
		{
			Matrix4x4 bindPose = this.bindPoses[vertInfos[i].boneIndex0].inverse;
            Quaternion 	boneBindRotation = bindPose.ExtractRotation();
			Vector3 boneDirection = boneBindRotation * this.boneOrientationVector;	// ToDo figure out bone orientation
			Vector3 bonePosition = bindPose.ExtractPosition();
			Vector3 toBone = bonePosition - (Vector3)vertInfos[i].position;

			vertInfos[i].compensation_coef = Vector3.Cross(toBone, boneDirection).magnitude;
		}

		this.bufVertInfo.SetData(vertInfos);
		this.ApplyMorphs();
	}

	int GetVertexTextureHeight(int vertexCount, int textureWidth)
	{
		int textureHeight = this.mf.sharedMesh.vertexCount / textureWidth;
		if (this.mf.sharedMesh.vertexCount % textureWidth != 0)
		{
			textureHeight++;
		}
		return textureHeight;
	}

	void GrabMeshFromSkinnedMeshRenderer()
	{
		this.ReleaseBuffers();

		this.mf.sharedMesh = this.smr.sharedMesh;
		this.bindPoses = this.mf.sharedMesh.bindposes;

		this.arrBufMorphDeltas = new ComputeBuffer[this.mf.sharedMesh.blendShapeCount];

		this.morphWeights = new float[this.mf.sharedMesh.blendShapeCount];

		var deltaVertices = new Vector3[this.mf.sharedMesh.vertexCount];
		var deltaNormals = new Vector3[this.mf.sharedMesh.vertexCount];
		var deltaTangents = new Vector3[this.mf.sharedMesh.vertexCount];

		var deltaVertInfos = new MorphDelta[this.mf.sharedMesh.vertexCount];

		for (int i = 0; i < this.mf.sharedMesh.blendShapeCount; i++)
		{
			this.mf.sharedMesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents);

			this.arrBufMorphDeltas[i] = new ComputeBuffer(this.mf.sharedMesh.vertexCount, sizeof(float) * 12);

			for (int k = 0; k < this.mf.sharedMesh.vertexCount; k++)
			{
				deltaVertInfos[k].position	= deltaVertices	!= null ? deltaVertices[k]	: Vector3.zero;
				deltaVertInfos[k].normal	= deltaNormals	!= null ? deltaNormals[k]	: Vector3.zero;
				deltaVertInfos[k].tangent	= deltaTangents	!= null ? deltaTangents[k]	: Vector3.zero;
			}

			this.arrBufMorphDeltas[i].SetData(deltaVertInfos);
		}

		Material[] materials = this.smr.sharedMaterials;
		for (int i = 0; i < materials.Length; i++)
		{
			materials[i].SetInt("_DoSkinning", 1);
		}
		this.mr.materials = materials;

		this.shaderDQBlend.SetInt("textureWidth", textureWidth);

		this.poseMatrices = new Matrix4x4[this.mf.sharedMesh.bindposes.Length];

		// initiate textures and buffers

		int textureHeight = GetVertexTextureHeight(this.mf.sharedMesh.vertexCount, textureWidth);

        this.rtSkinnedData_1 = new RenderTexture(textureWidth, textureHeight, 0, RenderTextureFormat.ARGBFloat)
        {
            filterMode = FilterMode.Point,
            enableRandomWrite = true
        };
        this.rtSkinnedData_1.Create();
		this.shaderDQBlend.SetTexture(this.kernelHandleDQBlend, "skinned_data_1", this.rtSkinnedData_1);

        this.rtSkinnedData_2 = new RenderTexture(textureWidth, textureHeight, 0, RenderTextureFormat.ARGBFloat)
        {
            filterMode = FilterMode.Point,
            enableRandomWrite = true
        };
        this.rtSkinnedData_2.Create();
		this.shaderDQBlend.SetTexture(this.kernelHandleDQBlend, "skinned_data_2", this.rtSkinnedData_2);

        this.rtSkinnedData_3 = new RenderTexture(textureWidth, textureHeight, 0, RenderTextureFormat.RGFloat)
        {
            filterMode = FilterMode.Point,
            enableRandomWrite = true
        };
        this.rtSkinnedData_3.Create();
		this.shaderDQBlend.SetTexture(this.kernelHandleDQBlend, "skinned_data_3", this.rtSkinnedData_3);

		this.bufPoseMatrices = new ComputeBuffer(this.mf.sharedMesh.bindposes.Length, sizeof(float) * 16);
		this.shaderComputeBoneDQ.SetBuffer(this.kernelHandleComputeBoneDQ, "pose_matrices", this.bufPoseMatrices);

		this.bufSkinnedDq = new ComputeBuffer(this.mf.sharedMesh.bindposes.Length, sizeof(float) * 8);
		this.shaderComputeBoneDQ.SetBuffer(this.kernelHandleComputeBoneDQ, "skinned_dual_quaternions", this.bufSkinnedDq);
		this.shaderDQBlend.SetBuffer(this.kernelHandleDQBlend, "skinned_dual_quaternions", this.bufSkinnedDq);

		this.bufBoneDirections = new ComputeBuffer(this.mf.sharedMesh.bindposes.Length, sizeof(float) * 4);
		this.shaderComputeBoneDQ.SetBuffer(this.kernelHandleComputeBoneDQ, "bone_directions", this.bufBoneDirections);
		this.shaderDQBlend.SetBuffer(this.kernelHandleDQBlend, "bone_directions", this.bufBoneDirections);

		this.bufVertInfo = new ComputeBuffer(this.mf.sharedMesh.vertexCount, sizeof(float) * 16 + sizeof(int) * 4 + sizeof(float));
		var vertInfos = new VertexInfo[this.mf.sharedMesh.vertexCount];
		Vector3[] vertices = this.mf.sharedMesh.vertices;
		Vector3[] normals = this.mf.sharedMesh.normals;
		Vector4[] tangents = this.mf.sharedMesh.tangents;
		BoneWeight[] boneWeights = this.mf.sharedMesh.boneWeights;
		for (int i = 0; i < vertInfos.Length; i++)
		{
			vertInfos[i].position = vertices[i];

			vertInfos[i].boneIndex0 = boneWeights[i].boneIndex0;
			vertInfos[i].boneIndex1 = boneWeights[i].boneIndex1;
			vertInfos[i].boneIndex2 = boneWeights[i].boneIndex2;
			vertInfos[i].boneIndex3 = boneWeights[i].boneIndex3;

			vertInfos[i].weight0 = boneWeights[i].weight0;
			vertInfos[i].weight1 = boneWeights[i].weight1;
			vertInfos[i].weight2 = boneWeights[i].weight2;
			vertInfos[i].weight3 = boneWeights[i].weight3;

			// determine per-vertex compensation coef

			Matrix4x4 bindPose = this.bindPoses[vertInfos[i].boneIndex0].inverse;
            Quaternion 	boneBindRotation = bindPose.ExtractRotation();
			Vector3 boneDirection = boneBindRotation * this.boneOrientationVector;	// ToDo figure out bone orientation
			Vector3 bonePosition = bindPose.ExtractPosition();
			Vector3 toBone = bonePosition - (Vector3)vertInfos[i].position;

			vertInfos[i].compensation_coef = Vector3.Cross(toBone, boneDirection).magnitude;
		}

		if (normals.Length > 0)
		{
			for (int i = 0; i < vertInfos.Length; i++)
			{
				vertInfos[i].normal = normals[i];
			}
		}

		if (tangents.Length > 0)
		{
			for (int i = 0; i < vertInfos.Length; i++)
			{
				vertInfos[i].tangent = tangents[i];
			}
		}

		this.bufVertInfo.SetData(vertInfos);
		this.shaderDQBlend.SetBuffer(this.kernelHandleDQBlend, "vertex_infos", this.bufVertInfo);

		this.bufMorphTemp_1 = new ComputeBuffer(this.mf.sharedMesh.vertexCount, sizeof(float) * 16 + sizeof(int) * 4 + sizeof(float));
		this.bufMorphTemp_2 = new ComputeBuffer(this.mf.sharedMesh.vertexCount, sizeof(float) * 16 + sizeof(int) * 4 + sizeof(float));

		// bind DQ buffer

		Matrix4x4[] bindPoses = this.mf.sharedMesh.bindposes;
		var bindDqs = new DualQuaternion[bindPoses.Length];
		for (int i = 0; i < bindPoses.Length; i++)
		{
			bindDqs[i].rotationQuaternion	= bindPoses[i].ExtractRotation();
			bindDqs[i].position				= bindPoses[i].ExtractPosition();
		}

		this.bufBindDq = new ComputeBuffer(bindDqs.Length, sizeof(float) * 8);
		this.bufBindDq.SetData(bindDqs);
		this.shaderComputeBoneDQ.SetBuffer(this.kernelHandleComputeBoneDQ, "bind_dual_quaternions", this.bufBindDq);

		this.UpdateViewFrustrumCulling();
		this.ApplyMorphs();
	}

	void ApplyMorphs()
	{
		ComputeBuffer bufMorphedVertexInfos = this.GetMorphedVertexInfos(
			this.bufVertInfo,
			ref this.bufMorphTemp_1,
			ref this.bufMorphTemp_2,
			this.arrBufMorphDeltas,
			this.morphWeights
		);

		this.shaderDQBlend.SetBuffer(this.kernelHandleDQBlend, "vertex_infos", bufMorphedVertexInfos);
	}

	ComputeBuffer GetMorphedVertexInfos(ComputeBuffer bufOriginal, ref ComputeBuffer bufTemp_1, ref ComputeBuffer bufTemp_2, ComputeBuffer[] arrBufDelta, float[] weights)
	{
		ComputeBuffer bufSource = bufOriginal;

		for (int i = 0; i < weights.Length; i++)
		{
			if (weights[i] == 0)
            {
                continue;
            }

            if (arrBufDelta[i] == null)
            {
                throw new System.NullReferenceException();
            }

            this.shaderApplyMorph.SetBuffer(this.kernelHandleApplyMorph, "source", bufSource);
			this.shaderApplyMorph.SetBuffer(this.kernelHandleApplyMorph, "target", bufTemp_1);
			this.shaderApplyMorph.SetBuffer(this.kernelHandleApplyMorph, "delta", arrBufDelta[i]);
			this.shaderApplyMorph.SetFloat("weight", weights[i] / 100f);

			int numThreadGroups = bufSource.count / numthreads;
			if (bufSource.count % numthreads != 0)
			{
				numThreadGroups++;
			}

			this.shaderApplyMorph.Dispatch(this.kernelHandleApplyMorph, numThreadGroups, 1, 1);

			bufSource = bufTemp_1;
			bufTemp_1 = bufTemp_2;
			bufTemp_2 = bufSource;
		}

		return bufSource;
	}

	void ReleaseBuffers()
	{
		this.bufBindDq?.Release();
		this.bufPoseMatrices?.Release();
		this.bufSkinnedDq?.Release();

		this.bufVertInfo?.Release();
		this.bufMorphTemp_1?.Release();
		this.bufMorphTemp_2?.Release();

		this.bufBoneDirections?.Release();

		if (this.arrBufMorphDeltas != null)
		{
			for (int i = 0; i < this.arrBufMorphDeltas.Length; i++)
			{
				this.arrBufMorphDeltas[i]?.Release();
			}
		}
	}
	void OnDestroy()
	{
		this.ReleaseBuffers();
	}

	// Use this for initialization
	void Start()
	{
		this.materialPropertyBlock = new MaterialPropertyBlock();

		this.shaderComputeBoneDQ = (ComputeShader)Instantiate(this.shaderComputeBoneDQ);    // bug workaround
		this.shaderDQBlend = (ComputeShader)Instantiate(this.shaderDQBlend);                // bug workaround
		this.shaderApplyMorph = (ComputeShader)Instantiate(this.shaderApplyMorph);          // bug workaround

		this.kernelHandleComputeBoneDQ = this.shaderComputeBoneDQ.FindKernel("CSMain");
		this.kernelHandleDQBlend = this.shaderDQBlend.FindKernel("CSMain");
		this.kernelHandleApplyMorph = this.shaderApplyMorph.FindKernel("CSMain");

		this.bones = this.smr.bones;

		this.started = true;
		this.GrabMeshFromSkinnedMeshRenderer();

		for (int i = 0; i < this.morphWeights.Length; i++)
		{
			this.morphWeights[i] = this.smr.GetBlendShapeWeight(i);
		}

		this.smr.enabled = false;
	}

	// Update is called once per frame
	void Update()
	{
		if (this.mr.isVisible == false)
		{
			return;
		}

		this.mf.sharedMesh.MarkDynamic();    // once or every frame? idk.
									   // at least it does not affect performance

		for (int i = 0; i < this.bones.Length; i++)
		{
			this.poseMatrices[i] = this.bones[i].localToWorldMatrix;
		}

		this.bufPoseMatrices.SetData(this.poseMatrices);

		// Calculate blended quaternions

		int numThreadGroups = this.bones.Length / numthreads;
		numThreadGroups += this.bones.Length % numthreads == 0 ? 0 : 1;

		this.shaderComputeBoneDQ.SetVector("boneOrientation", this.boneOrientationVector);
		this.shaderComputeBoneDQ.SetMatrix(
			"self_matrix",
			this.transform.worldToLocalMatrix
		);
		this.shaderComputeBoneDQ.Dispatch(this.kernelHandleComputeBoneDQ, numThreadGroups, 1, 1);

		numThreadGroups = this.mf.sharedMesh.vertexCount / numthreads;
		numThreadGroups += this.mf.sharedMesh.vertexCount % numthreads == 0 ? 0 : 1;

		this.shaderDQBlend.SetFloat("compensation_coef", this.bulgeCompensation);
		this.shaderDQBlend.Dispatch(this.kernelHandleDQBlend, numThreadGroups, 1, 1);

		this.materialPropertyBlock.SetTexture("skinned_data_1", this.rtSkinnedData_1);
		this.materialPropertyBlock.SetTexture("skinned_data_2", this.rtSkinnedData_2);
		this.materialPropertyBlock.SetTexture("skinned_data_3", this.rtSkinnedData_3);
		this.materialPropertyBlock.SetInt("skinned_tex_height", GetVertexTextureHeight(this.mf.sharedMesh.vertexCount, textureWidth));
		this.materialPropertyBlock.SetInt("skinned_tex_width", textureWidth);

		this.mr.SetPropertyBlock(this.materialPropertyBlock);
	}
}


code:
using UnityEngine;
using UnityEditor;

using System.Collections.Generic;

#if UNITY_EDITOR

/// <summary>
/// Provides custom inspector for {@link DualQuaternionSkinner}
/// </summary>
[CustomEditor(typeof(DualQuaternionSkinner))]
public class DualQuaternionSkinnerEditor : Editor
{
	SerializedProperty shaderComputeBoneDQ;
	SerializedProperty shaderDQBlend;
	SerializedProperty shaderApplyMorph;
	SerializedProperty bulgeCompensation;

	DualQuaternionSkinner dqs;
	SkinnedMeshRenderer smr
	{
		get
		{
			if (this._smr == null)
			{
				this._smr = ((DualQuaternionSkinner)this.target).gameObject.GetComponent<SkinnedMeshRenderer>();
			}

			return this._smr;
		}
	}
	SkinnedMeshRenderer _smr;

	bool showBlendShapes = false;

	enum BoneOrientation {X, Y, Z, negativeX, negativeY, negativeZ}
	readonly Dictionary<BoneOrientation, Vector3> boneOrientationVectors = new Dictionary<BoneOrientation, Vector3>();

	private void OnEnable()
	{
		this.shaderComputeBoneDQ	= this.serializedObject.FindProperty("shaderComputeBoneDQ");
		this.shaderDQBlend			= this.serializedObject.FindProperty("shaderDQBlend");
		this.shaderApplyMorph		= this.serializedObject.FindProperty("shaderApplyMorph");
		this.bulgeCompensation		= this.serializedObject.FindProperty("bulgeCompensation");

		this.boneOrientationVectors.Add(BoneOrientation.X, new Vector3(1,0,0));
		this.boneOrientationVectors.Add(BoneOrientation.Y, new Vector3(0,1,0));
		this.boneOrientationVectors.Add(BoneOrientation.Z, new Vector3(0,0,1));
		this.boneOrientationVectors.Add(BoneOrientation.negativeX, new Vector3(-1,0,0));
		this.boneOrientationVectors.Add(BoneOrientation.negativeY, new Vector3(0,-1,0));
		this.boneOrientationVectors.Add(BoneOrientation.negativeZ, new Vector3(0,0,-1));
	}

	public override void OnInspectorGUI()
	{
		this.serializedObject.Update();

		this.dqs = (DualQuaternionSkinner)this.target;

		EditorGUILayout.BeginHorizontal();
			EditorGUILayout.LabelField("Mode: ", GUILayout.Width(80));
			EditorGUILayout.LabelField(Application.isPlaying ? "Play" : "Editor", GUILayout.Width(80));
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal();
			EditorGUILayout.LabelField("DQ skinning: ", GUILayout.Width(80));
			EditorGUILayout.LabelField(this.dqs.started ? "ON" : "OFF", GUILayout.Width(80));
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.Space();
		this.dqs.SetViewFrustrumCulling(EditorGUILayout.Toggle("View frustrum culling: ", this.dqs.viewFrustrumCulling));
		EditorGUILayout.Space();

		BoneOrientation currentOrientation = BoneOrientation.X;
		foreach(BoneOrientation orientation in this.boneOrientationVectors.Keys)
		{
			if (this.dqs.boneOrientationVector == this.boneOrientationVectors[orientation])
			{
				currentOrientation = orientation;
				break;
			}
		}
		var newOrientation = (BoneOrientation)EditorGUILayout.EnumPopup("Bone orientation: ", currentOrientation);
		if (this.dqs.boneOrientationVector != this.boneOrientationVectors[newOrientation])
		{
			this.dqs.boneOrientationVector = this.boneOrientationVectors[newOrientation];
			this.dqs.UpdatePerVertexCompensationCoef();
		}

		EditorGUILayout.PropertyField(this.bulgeCompensation);
		EditorGUILayout.Space();

		this.showBlendShapes = EditorGUILayout.Foldout(this.showBlendShapes, "Blend shapes");

		if (this.showBlendShapes)
		{
			if (this.dqs.started == false)
			{
				EditorGUI.BeginChangeCheck();
				Undo.RecordObject(this.dqs.gameObject.GetComponent<SkinnedMeshRenderer>(), "changed blendshape weights by DualQuaternionSkinner component");
			}

			for (int i = 0; i < this.dqs.mesh.blendShapeCount; i++)
			{
				EditorGUILayout.BeginHorizontal();
				EditorGUILayout.LabelField("   " + this.dqs.mesh.GetBlendShapeName(i), GUILayout.Width(EditorGUIUtility.labelWidth - 10));
				float weight = EditorGUILayout.Slider(this.dqs.GetBlendShapeWeight(i), 0, 100);
				EditorGUILayout.EndHorizontal();
				this.dqs.SetBlendShapeWeight(i, weight);
			}
		}

		EditorGUILayout.Space();

		EditorGUILayout.PropertyField(this.shaderComputeBoneDQ);
		EditorGUILayout.PropertyField(this.shaderDQBlend);
		EditorGUILayout.PropertyField(this.shaderApplyMorph);

		EditorGUILayout.Space();
		EditorGUILayout.LabelField("Problems: ");

		if (this.CheckProblems() == false)
		{
			EditorGUILayout.LabelField("not detected (this is good)");
		}

		this.serializedObject.ApplyModifiedProperties();
	}

	bool CheckProblems()
	{
		var wrapStyle = new GUIStyle() { wordWrap = true };

		if (this.smr.sharedMesh.isReadable == false)
		{
			EditorGUILayout.Space();
			EditorGUILayout.LabelField("Skinned mesh must be read/write enabled (check import settings)", wrapStyle);
			return true;
		}

		if (this.smr.rootBone.parent != this.dqs.gameObject.transform.parent)
		{
			EditorGUILayout.Space();

			EditorGUILayout.BeginHorizontal();
				EditorGUILayout.LabelField("Skinned object and root bone must be children of the same parent", wrapStyle);
				if (GUILayout.Button("auto fix"))
				{
					Undo.SetTransformParent(
						this.smr.rootBone,
						this.dqs.gameObject.transform.parent,
						"Changed root bone's parent by Dual Quaternion Skinner (auto fix)"
					);
					EditorApplication.RepaintHierarchyWindow();
				}
			EditorGUILayout.EndHorizontal();

			return true;
		}

		foreach(Transform bone in this.smr.bones)
		{
			if (bone.localScale != Vector3.one)
			{
				EditorGUILayout.Space();

				EditorGUILayout.BeginHorizontal();
					EditorGUILayout.LabelField(string.Format("Bone scaling not supported: {0}", bone.name), wrapStyle);
					if (GUILayout.Button("auto fix"))
					{
						Undo.RecordObject(bone, "Set bone scale to (1,1,1) by Dual Quaternion Skinner (auto fix)");
						bone.localScale = Vector3.one;
					}
				EditorGUILayout.EndHorizontal();

				return true;
			}
		}

		return false;
	}
}

#endif


code:
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

#include "DQ.cginc"

struct vertex_info
{
	float4 position;
	float4 normal;
	float4 tangent;

	int4 bone_indexes;
	float4 bone_weights;

	float compensation_coef;
};

struct morph_delta
{
	float4 position;
	float4 normal;
	float4 tangent;
};

RWStructuredBuffer<vertex_info> source;
RWStructuredBuffer<vertex_info> target;
RWStructuredBuffer<morph_delta> delta;

float weight;

[numthreads(1024, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
	struct vertex_info vertinfo = source.Load(id.x);
	struct morph_delta morphinfo = delta.Load(id.x);

	vertinfo.position	+= morphinfo.position	* weight;
	vertinfo.normal		+= morphinfo.normal		* weight;
	vertinfo.tangent	+= morphinfo.tangent	* weight;

	target[id.x] = vertinfo;
}


code:
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

#include "DQ.cginc"

struct boneWeight
{
	int boneIndex0;
	int boneIndex1;
	int boneIndex2;
	int boneIndex3;

	float boneWeight0;
	float boneWeight1;
	float boneWeight2;
	float boneWeight3;
};

RWStructuredBuffer<float4x4> pose_matrices;
float4x4 self_matrix;

RWStructuredBuffer<dual_quaternion> bind_dual_quaternions;
RWStructuredBuffer<dual_quaternion> skinned_dual_quaternions;

RWStructuredBuffer<float4> bone_directions;

float4 boneOrientation;

[numthreads(1024,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
	struct dual_quaternion dq_bind;
	dq_bind	= bind_dual_quaternions.Load(id.x);
	dq_bind.translation_quaternion = QuaternionMultiply(dq_bind.translation_quaternion,	dq_bind.rotation_quaternion) * 0.5;

	float4x4 pose_matrix = transpose(pose_matrices.Load(id.x));
	pose_matrix = mul(self_matrix, pose_matrix);

	struct dual_quaternion dq_pose = DualQuaternionFromMatrix4x4(pose_matrix);
	struct dual_quaternion dq_skinned = DualQuaternionMultiply(dq_pose, dq_bind);

	skinned_dual_quaternions[id.x].rotation_quaternion		= dq_skinned.rotation_quaternion;
	skinned_dual_quaternions[id.x].translation_quaternion	= dq_skinned.translation_quaternion;

	bone_directions[id.x] = QuaternionApplyRotation(boneOrientation, dq_pose.rotation_quaternion);
}


code:
struct dual_quaternion
{
	float4 rotation_quaternion;
	float4 translation_quaternion;
};

float4 QuaternionInvert(float4 q)
{
	q.xyz *= -1;
	return q;
}

float4 QuaternionMultiply(float4 q1, float4 q2)
{
	float w = q1.w * q2.w - dot(q1.xyz, q2.xyz);
	q1.xyz = q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz);
	q1.w = w;
	return q1;
}

struct dual_quaternion DualQuaternionMultiply(struct dual_quaternion dq1, struct dual_quaternion dq2)
{
	struct dual_quaternion result;

	result.translation_quaternion = QuaternionMultiply(dq1.rotation_quaternion,		dq2.translation_quaternion) +
									QuaternionMultiply(dq1.translation_quaternion,	dq2.rotation_quaternion);

	result.rotation_quaternion = QuaternionMultiply(dq1.rotation_quaternion, dq2.rotation_quaternion);

	float mag = length(result.rotation_quaternion);
	result.rotation_quaternion /= mag;
	result.translation_quaternion /= mag;

	return result;
}

struct dual_quaternion DualQuaternionShortestPath(struct dual_quaternion dq1, struct dual_quaternion dq2)
{
	bool isBadPath = dot(dq1.rotation_quaternion, dq2.rotation_quaternion) < 0;
	dq1.rotation_quaternion		= isBadPath ? -dq1.rotation_quaternion		: dq1.rotation_quaternion;
	dq1.translation_quaternion	= isBadPath ? -dq1.translation_quaternion	: dq1.translation_quaternion;
	return dq1;
}

float4 QuaternionApplyRotation(float4 v, float4 rotQ)
{
	v = QuaternionMultiply(rotQ, v);
	return QuaternionMultiply(v, QuaternionInvert(rotQ));
}

inline float signNoZero(float x)
{
	float s = sign(x);
	if (s)
		return s;
	return 1;
}

struct dual_quaternion DualQuaternionFromMatrix4x4(float4x4 m)
{
	struct  dual_quaternion dq;

	// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
	// Alternative Method by Christian
	dq.rotation_quaternion.w = sqrt(max(0, 1.0 + m[0][0] + m[1][1] + m[2][2])) / 2.0;
	dq.rotation_quaternion.x = sqrt(max(0, 1.0 + m[0][0] - m[1][1] - m[2][2])) / 2.0;
	dq.rotation_quaternion.y = sqrt(max(0, 1.0 - m[0][0] + m[1][1] - m[2][2])) / 2.0;
	dq.rotation_quaternion.z = sqrt(max(0, 1.0 - m[0][0] - m[1][1] + m[2][2])) / 2.0;
	dq.rotation_quaternion.x *= signNoZero(m[2][1] - m[1][2]);
	dq.rotation_quaternion.y *= signNoZero(m[0][2] - m[2][0]);
	dq.rotation_quaternion.z *= signNoZero(m[1][0] - m[0][1]);

	dq.rotation_quaternion = normalize(dq.rotation_quaternion);	// ensure unit quaternion

	dq.translation_quaternion = float4(m[0][3], m[1][3], m[2][3], 0);
	dq.translation_quaternion = QuaternionMultiply(dq.translation_quaternion, dq.rotation_quaternion) * 0.5;

	return dq;
}


code:
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
#pragma multi_compile_local __ TWIST_COMPENSATION_EXPERIMENTAL

#include "DQ.cginc"

struct bone_weight_info
{
	int4 bone_indexes;
	float4 bone_weights;
};

struct vertex_info
{
	float4 position;
	float4 normal;
	float4 tangent;

	int4 bone_indexes;
	float4 bone_weights;

	float compensation_coef;
};

float compensation_coef;

RWStructuredBuffer<dual_quaternion> skinned_dual_quaternions;
RWStructuredBuffer<vertex_info> vertex_infos;

uint textureWidth;
RWTexture2D<float4> skinned_data_1;
RWTexture2D<float4> skinned_data_2;
RWTexture2D<float2> skinned_data_3;

RWStructuredBuffer<float4> bone_directions;

struct vertex_info SkinVertex(struct vertex_info vertinfo)
{
	int4 bone_indexes	= vertinfo.bone_indexes;
	float4 bone_weights	= vertinfo.bone_weights;

	struct dual_quaternion dq0 = skinned_dual_quaternions.Load(bone_indexes[0]);
	struct dual_quaternion dq1 = skinned_dual_quaternions.Load(bone_indexes[1]);
	struct dual_quaternion dq2 = skinned_dual_quaternions.Load(bone_indexes[2]);
	struct dual_quaternion dq3 = skinned_dual_quaternions.Load(bone_indexes[3]);

	dq1 = DualQuaternionShortestPath(dq1, dq0);
	dq2 = DualQuaternionShortestPath(dq2, dq0);
	dq3 = DualQuaternionShortestPath(dq3, dq0);

	struct dual_quaternion skinned_dq;
	skinned_dq.rotation_quaternion =	dq0.rotation_quaternion * bone_weights[0];
	skinned_dq.rotation_quaternion +=	dq1.rotation_quaternion * bone_weights[1];
	skinned_dq.rotation_quaternion +=	dq2.rotation_quaternion * bone_weights[2];
	skinned_dq.rotation_quaternion +=	dq3.rotation_quaternion * bone_weights[3];

	skinned_dq.translation_quaternion =		dq0.translation_quaternion * bone_weights[0];
	skinned_dq.translation_quaternion +=	dq1.translation_quaternion * bone_weights[1];
	skinned_dq.translation_quaternion +=	dq2.translation_quaternion * bone_weights[2];
	skinned_dq.translation_quaternion +=	dq3.translation_quaternion * bone_weights[3];

	float mag = length(skinned_dq.rotation_quaternion);
	skinned_dq.rotation_quaternion		/= mag;
	skinned_dq.translation_quaternion	/= mag;

	vertinfo.position	= QuaternionApplyRotation(vertinfo.position,	skinned_dq.rotation_quaternion);
	vertinfo.normal		= QuaternionApplyRotation(vertinfo.normal,		skinned_dq.rotation_quaternion);
	vertinfo.tangent	= QuaternionApplyRotation(vertinfo.tangent,		skinned_dq.rotation_quaternion);

	vertinfo.position += QuaternionMultiply(skinned_dq.translation_quaternion * 2, QuaternionInvert(skinned_dq.rotation_quaternion));
	vertinfo.normal = normalize(vertinfo.normal);
	vertinfo.tangent = normalize(vertinfo.tangent);

	// experimental

	float4 rq = QuaternionMultiply(dq0.rotation_quaternion, QuaternionInvert(dq1.rotation_quaternion));

	// branching is removed by the compiler optimization
	if((bone_weights[1] != 0.0 && length(rq.xyz) > 0.001))
	{
		float4 boneDir0 = bone_directions.Load(bone_indexes[0]);
		float4 boneDir1 = bone_directions.Load(bone_indexes[1]);

		float3 axis = normalize(rq.xyz);
		float3 bisector = normalize(boneDir0.xyz + boneDir1.xyz);
		bisector = bone_indexes[0] > bone_indexes[1] ? bisector : -bisector;

		float3 offset = bisector - axis * dot(axis, bisector);

		float angleCoef = saturate(2.0*sqrt(1.0-rq.w));

		float x = bone_weights[1] / (bone_weights[0] + bone_weights[1]);
		float compensation = 2.2*x-9.6*x*x+10.4*x*x*x;

		compensation *= vertinfo.compensation_coef;
		compensation *= compensation_coef;
		compensation *= angleCoef;
		compensation *= 1.0 - bone_weights[2] / bone_weights[1];
		compensation *= bone_weights[0] + bone_weights[1];

		vertinfo.position.xyz += offset * compensation;
	}

	return vertinfo;
}

[numthreads(1024,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
	int2 pos;
	pos.x = id.x % textureWidth;
	pos.y = id.x / textureWidth;

	struct vertex_info vertinfo = vertex_infos.Load(id.x);

	vertinfo = SkinVertex(vertinfo);

	skinned_data_1[pos] = float4(vertinfo.position.xyz, vertinfo.normal.x);
	skinned_data_2[pos] = float4(vertinfo.normal.yz, vertinfo.tangent.xy);
	skinned_data_3[pos] = vertinfo.tangent.zw;
}


code:
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

/*
	modified to use MadCake's dual quaternion skinning script

	modifications marked by	comments:
		// ----- DQ modification start -----
		---inserted code---
		// ----- DQ modification end -----

	modifications added to following passes:
		- Forward
		- ShadowCaster
		- Deferred
*/

Shader "MadCake/Material/Standard hacked for DQ skinning"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo", 2D) = "white" {}

        _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5

        _Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5
        _GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0
        [Enum(Metallic Alpha,0,Albedo Alpha,1)] _SmoothnessTextureChannel ("Smoothness texture channel", Float) = 0

        [Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
        _MetallicGlossMap("Metallic", 2D) = "white" {}

        [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
        [ToggleOff] _GlossyReflections("Glossy Reflections", Float) = 1.0

        _BumpScale("Scale", Float) = 1.0
        _BumpMap("Normal Map", 2D) = "bump" {}

        _Parallax ("Height Scale", Range (0.005, 0.08)) = 0.02
        _ParallaxMap ("Height Map", 2D) = "black" {}

        _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
        _OcclusionMap("Occlusion", 2D) = "white" {}

        _EmissionColor("Color", Color) = (0,0,0)
        _EmissionMap("Emission", 2D) = "white" {}

        _DetailMask("Detail Mask", 2D) = "white" {}

        _DetailAlbedoMap("Detail Albedo x2", 2D) = "grey" {}
        _DetailNormalMapScale("Scale", Float) = 1.0
        _DetailNormalMap("Normal Map", 2D) = "bump" {}

        [Enum(UV0,0,UV1,1)] _UVSec ("UV Set for secondary textures", Float) = 0

        // Blending state
        [HideInInspector] _Mode ("__mode", Float) = 0.0
        [HideInInspector] _SrcBlend ("__src", Float) = 1.0
        [HideInInspector] _DstBlend ("__dst", Float) = 0.0
        [HideInInspector] _ZWrite ("__zw", Float) = 1.0
    }

    CGINCLUDE
        #define UNITY_SETUP_BRDF_INPUT MetallicSetup
    ENDCG

    SubShader
    {
        Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
        LOD 300

        // ------------------------------------------------------------------
        //  Base forward pass (directional light, emission, lightmaps, ...)
        Pass
        {
            Name "FORWARD"
            Tags { "LightMode" = "ForwardBase" }

            Blend [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]

            CGPROGRAM
            #pragma target 3.0

            // -------------------------------------

            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
            #pragma shader_feature _PARALLAXMAP

            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
            //#pragma multi_compile _ LOD_FADE_CROSSFADE

			//#pragma vertex vertForwardBase
            #pragma fragment fragBase
            #include "UnityStandardCoreForward.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedForward // original #pragma vertex (above) was commented

			// expanded version of original vertex input structure
			struct VertexInputSkinningForward
			{
				float4 vertex   : POSITION;
				half3 normal    : NORMAL;
				float2 uv0      : TEXCOORD0;
				float2 uv1      : TEXCOORD1;

				// this was added, everything else remains unchanged
				uint id : SV_VertexID;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					float2 uv2      : TEXCOORD2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					half4 tangent   : TANGENT;
				#endif

				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			// variables used for skining, always the same for every pass
			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			// the actual skinning function
			// don't change the code but change the argument type to the name of vertex input structure used in current pass
			// for this pass it is VertexInputSkinningForward
			void vert(inout VertexInputSkinningForward v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#ifdef _TANGENT_TO_WORLD
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#ifdef _TANGENT_TO_WORLD
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			// this function will replace the original vertex function (vertForwardBase)
			// the return type is the same as in the original function
			// the argument type is our expanded structure
			VertexOutputForwardBase vertSkinnedForward(VertexInputSkinningForward vs)
			{
				// first we apply skinning
				vert(vs);

				// then we create the original vertex structure (VertexInput for this pass)
				// and fill it with the data from our expanded version
				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;
				v.uv1 = vs.uv1;

				// this variable is inside an "if defined" block in original structure
				// so accessing it should be enclosed in identical "if defined" block
				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					v.uv2 = vs.uv2;
				#endif

				// same here
				#ifdef _TANGENT_TO_WORLD
					v.tangent = vs.tangent;
				#endif

				// finally we pass the original vertex structure to the original vertex function
				// and return the result
				return vertForwardBase(v);
			}

			// ----- DQ modification end -----

            ENDCG
        }
        // ------------------------------------------------------------------
        //  Additive forward pass (one light per pass)
        Pass
        {
            Name "FORWARD_DELTA"
            Tags { "LightMode" = "ForwardAdd" }
            Blend [_SrcBlend] One
            Fog { Color (0,0,0,0) } // in additive pass fog should be black
            ZWrite Off
            ZTest LEqual

            CGPROGRAM
            #pragma target 3.0

            // -------------------------------------

            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature _PARALLAXMAP

            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog
            // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
            //#pragma multi_compile _ LOD_FADE_CROSSFADE

            //#pragma vertex vertAdd
            #pragma fragment fragAdd
            #include "UnityStandardCoreForward.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedForwardAdd // original #pragma vertex (above) was commented

			struct VertexInputSkinningForwardAdd
			{
				float4 vertex   : POSITION;
				half3 normal    : NORMAL;
				float2 uv0      : TEXCOORD0;
				float2 uv1      : TEXCOORD1;
				uint id : SV_VertexID;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					float2 uv2      : TEXCOORD2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					half4 tangent   : TANGENT;
				#endif

				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningForwardAdd v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#ifdef _TANGENT_TO_WORLD
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#ifdef _TANGENT_TO_WORLD
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			VertexOutputForwardAdd vertSkinnedForwardAdd(VertexInputSkinningForwardAdd vs)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;
				v.uv1 = vs.uv1;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					v.uv2 = vs.uv2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					v.tangent = vs.tangent;
				#endif

				return vertAdd(v);
			}

			// ----- DQ modification end -----

            ENDCG
        }
        // ------------------------------------------------------------------
        //  Shadow rendering pass
        Pass {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On ZTest LEqual

            CGPROGRAM
            #pragma target 3.0

            // -------------------------------------

            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _PARALLAXMAP
            #pragma multi_compile_shadowcaster
            #pragma multi_compile_instancing
            // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
            //#pragma multi_compile _ LOD_FADE_CROSSFADE

			//#pragma vertex verthadowCaster
            #pragma fragment fragShadowCaster

            #include "UnityStandardShadow.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedShadowCaster // original #pragma vertex (above) was commented

			struct VertexInputSkinningShadowCaster
			{
				float4 vertex   : POSITION;
				half3 normal    : NORMAL;
				float2 uv0      : TEXCOORD0;
				uint id : SV_VertexID;

				#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
					half4 tangent   : TANGENT;
				#endif

				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningShadowCaster v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			void vertSkinnedShadowCaster(
				VertexInputSkinningShadowCaster vs,
				out float4 opos : SV_POSITION

				#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
					, out VertexOutputShadowCaster o
				#endif

				#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
					, out VertexOutputStereoShadowCaster os
				#endif
			)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;

				#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
					v.tangent = vs.tangent;
				#endif

				vertShadowCaster(
					v,
					opos

					#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
						, o
					#endif

					#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
						, os
					#endif
				);
			}

			// ----- DQ modification end -----

			ENDCG
        }
        // ------------------------------------------------------------------
        //  Deferred pass
        Pass
        {
            Name "DEFERRED"
            Tags { "LightMode" = "Deferred" }

            CGPROGRAM
            #pragma target 3.0
            #pragma exclude_renderers nomrt

            // -------------------------------------

            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature _PARALLAXMAP

            #pragma multi_compile_prepassfinal
            #pragma multi_compile_instancing
            // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
            //#pragma multi_compile _ LOD_FADE_CROSSFADE

            //#pragma vertex vertDeferred
            #pragma fragment fragDeferred

            #include "UnityStandardCore.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedDeferred // original #pragma vertex (above) was commented

			struct VertexInputSkinningDeferred
				{
					float4 vertex   : POSITION;
					half3 normal    : NORMAL;
					float2 uv0      : TEXCOORD0;
					float2 uv1      : TEXCOORD1;
					uint id : SV_VertexID;

					#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
						float2 uv2      : TEXCOORD2;
					#endif

					#ifdef _TANGENT_TO_WORLD
						half4 tangent   : TANGENT;
					#endif

					UNITY_VERTEX_INPUT_INSTANCE_ID
				};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningDeferred v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#ifdef _TANGENT_TO_WORLD
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#ifdef _TANGENT_TO_WORLD
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			VertexOutputDeferred vertSkinnedDeferred(VertexInputSkinningDeferred vs)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;
				v.uv1 = vs.uv1;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					v.uv2 = vs.uv2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					v.tangent = vs.tangent;
				#endif

				return vertDeferred(v);
			}

			// ----- DQ modification end -----

            ENDCG
        }

        // ------------------------------------------------------------------
        // Extracts information for lightmapping, GI (emission, albedo, ...)
        // This pass it not used during regular rendering.
        Pass
        {
            Name "META"
            Tags { "LightMode"="Meta" }

            Cull Off

            CGPROGRAM
            //#pragma vertex vert_meta
            #pragma fragment frag_meta

            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature EDITOR_VISUALIZATION

            #include "UnityStandardMeta.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedMeta // original #pragma vertex (above) was commented

			struct VertexInputSkinningMeta
				{
					float4 vertex   : POSITION;
					half3 normal    : NORMAL;
					float2 uv0      : TEXCOORD0;
					float2 uv1      : TEXCOORD1;
					uint id : SV_VertexID;

					#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
						float2 uv2      : TEXCOORD2;
					#endif

					#ifdef _TANGENT_TO_WORLD
						half4 tangent   : TANGENT;
					#endif

					UNITY_VERTEX_INPUT_INSTANCE_ID
				};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningMeta v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#ifdef _TANGENT_TO_WORLD
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#ifdef _TANGENT_TO_WORLD
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			v2f_meta vertSkinnedMeta(VertexInputSkinningMeta vs)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;
				v.uv1 = vs.uv1;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					v.uv2 = vs.uv2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					v.tangent = vs.tangent;
				#endif

				return vert_meta(v);
			}

			// ----- DQ modification end -----

            ENDCG
        }
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
        LOD 150

        // ------------------------------------------------------------------
        //  Base forward pass (directional light, emission, lightmaps, ...)
        Pass
        {
            Name "FORWARD"
            Tags { "LightMode" = "ForwardBase" }

            Blend [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]

            CGPROGRAM
            #pragma target 2.0

            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
            // SM2.0: NOT SUPPORTED shader_feature ___ _DETAIL_MULX2
            // SM2.0: NOT SUPPORTED shader_feature _PARALLAXMAP

            #pragma skip_variants SHADOWS_SOFT DIRLIGHTMAP_COMBINED

            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog

            //#pragma vertex vertBase
            #pragma fragment fragBase
            #include "UnityStandardCoreForward.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedForward // original #pragma vertex (above) was commented

			struct VertexInputSkinningForward
			{
				float4 vertex   : POSITION;
				half3 normal    : NORMAL;
				float2 uv0      : TEXCOORD0;
				float2 uv1      : TEXCOORD1;
				uint id : SV_VertexID;

				#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
					float2 uv2      : TEXCOORD2;
				#endif

				#ifdef _TANGENT_TO_WORLD
					half4 tangent   : TANGENT;
				#endif

				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningForward v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#ifdef _TANGENT_TO_WORLD
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#ifdef _TANGENT_TO_WORLD
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			VertexOutputForwardBase vertSkinnedForward(VertexInputSkinningForward vs)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;
				v.uv1 = vs.uv1;
			#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
				v.uv2 = vs.uv2;
			#endif

			#ifdef _TANGENT_TO_WORLD
				v.tangent = vs.tangent;
			#endif
				return vertForwardBase(v);
			}

			// ----- DQ modification end -----

            ENDCG
        }
        // ------------------------------------------------------------------
        //  Additive forward pass (one light per pass)
        Pass
        {
            Name "FORWARD_DELTA"
            Tags { "LightMode" = "ForwardAdd" }
            Blend [_SrcBlend] One
            Fog { Color (0,0,0,0) } // in additive pass fog should be black
            ZWrite Off
            ZTest LEqual

            CGPROGRAM
            #pragma target 2.0

            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature ___ _DETAIL_MULX2
            // SM2.0: NOT SUPPORTED shader_feature _PARALLAXMAP
            #pragma skip_variants SHADOWS_SOFT

            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog

            #pragma vertex vertAdd
            #pragma fragment fragAdd
            #include "UnityStandardCoreForward.cginc"

            ENDCG
        }
        // ------------------------------------------------------------------
        //  Shadow rendering pass
        Pass {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On ZTest LEqual

            CGPROGRAM
            #pragma target 2.0

            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma skip_variants SHADOWS_SOFT
            #pragma multi_compile_shadowcaster

            //#pragma vertex vertShadowCaster
            #pragma fragment fragShadowCaster

            #include "UnityStandardShadow.cginc"

			// ----- DQ modification start -----

			#pragma vertex vertSkinnedShadowCaster // original #pragma vertex (above) was commented

			struct VertexInputSkinningShadowCaster
			{
				float4 vertex   : POSITION;
				half3 normal    : NORMAL;
				float2 uv0      : TEXCOORD0;
				uint id : SV_VertexID;

				#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
					half4 tangent   : TANGENT;
				#endif

				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			sampler2D skinned_data_1;
			sampler2D skinned_data_2;
			sampler2D skinned_data_3;
			uint skinned_tex_height;
			uint skinned_tex_width;
			bool _DoSkinning;

			void vert(inout VertexInputSkinningShadowCaster v) {
				if (_DoSkinning) {
					float2 skinned_tex_uv;

					skinned_tex_uv.x = (float(v.id % skinned_tex_width)) / skinned_tex_width;
					skinned_tex_uv.y = (float(v.id / skinned_tex_width)) / skinned_tex_height;

					float4 data_1 = tex2Dlod(skinned_data_1, float4(skinned_tex_uv, 0, 0));
					float4 data_2 = tex2Dlod(skinned_data_2, float4(skinned_tex_uv, 0, 0));

					#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
						float2 data_3 = tex2Dlod(skinned_data_3, float4(skinned_tex_uv, 0, 0)).xy;
					#endif

					v.vertex.xyz = data_1.xyz;
					v.vertex.w = 1;

					v.normal.x = data_1.w;
					v.normal.yz = data_2.xy;

					#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
						v.tangent.xy = data_2.zw;
						v.tangent.zw = data_3.xy;
					#endif
				}
			}

			void vertSkinnedShadowCaster(
				VertexInputSkinningShadowCaster vs,
				out float4 opos : SV_POSITION

				#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
					, out VertexOutputShadowCaster o
				#endif

				#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
					, out VertexOutputStereoShadowCaster os
				#endif
			)
			{
				vert(vs);

				VertexInput v;
				v.vertex = vs.vertex;
				v.normal = vs.normal;
				v.uv0 = vs.uv0;

				#if defined(UNITY_STANDARD_USE_SHADOW_UVS) && defined(_PARALLAXMAP)
					v.tangent = vs.tangent;
				#endif

				vertShadowCaster(
					v,
					opos

					#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
						, o
					#endif

					#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
						, os
					#endif
				);
			}

			// ----- DQ modification end -----

            ENDCG
        }

        // ------------------------------------------------------------------
        // Extracts information for lightmapping, GI (emission, albedo, ...)
        // This pass it not used during regular rendering.
        Pass
        {
            Name "META"
            Tags { "LightMode"="Meta" }

            Cull Off

            CGPROGRAM
            #pragma vertex vert_meta
            #pragma fragment frag_meta

            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature EDITOR_VISUALIZATION

            #include "UnityStandardMeta.cginc"
            ENDCG
        }
    }

    FallBack "VertexLit"
    CustomEditor "StandardShaderGUI"
}


code:
using UnityEngine;

public static class MatrixExtensions
{
	public static Quaternion ExtractRotation(this Matrix4x4 matrix)
	{
		return Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
	}

	public static Vector3 ExtractPosition(this Matrix4x4 matrix)
	{
		Vector4 position;
		position = matrix.GetColumn(3);
		position.w = 1;
		return position;
	}

	public static Vector3 ExtractScale(this Matrix4x4 matrix)
	{
		Vector3 scale;
		scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude;
		scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude;
		scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude;
		return scale;
	}
}


code:
using UnityEngine;

public static class QuaternionExtensions
{
	public static Vector4 ToVector4(this Quaternion quaternion)
	{
		return new Vector4(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
	}
}


code:
# Dual quaternion skinning for Unity

### Features:
* GPU skinning with compute shaders
* blend shape support (calculations performed in compute shader)
* works with any platform that supports compute shaders
* preserves volume with deformation (look comparison)
* zero GC allocations per frame
* original bulging compensation [method](#bulging-compensation-method)

### Comparison DQ vs built-in linear:

|Gif|Difference|
|----|----|
|||

### Bulging compensation demo:

# Warning:
You will not see any effect in edit mode.

The scipt only works in play mode.

If you see no effect in play mode verify that you are using the right shader.

## Unity version
The script was tested with following Unity versions:
* **2020.1.0a13.1443** (earlier versions do not support `#pragma multi_compile` in compute shaders)

## How to set up:

* Create a skinned character with `SkinnedMeshRenderer` component
* Add `DualQuaternionSkinner.cs` component (it will require a `MeshFilter` component)
* Enable mesh Read/Write in import settings

* All materials of the mesh should use a special shader to apply vertex positions. The shader is `MadCake/Material/Standard hacked for DQ skinning`
* In order for bulging compensation to work properly, all the character's bones must have one local axis (consistent for all bones) aligned with the limb controlled by the corresponding bone.

|Bones properly aligned|Bones not aligned|
|----|----|
|||

* Whichever local axis of the bones was aligned with the limbs must be selected in the script gui:

If bulging is increased instead of decreased, select same axis with different direction (**X** => **Negative X**)

## Common problems

The script is programmed to automatically detect common setup problems. Check out the editor:

## Known bugs

Must use [cullingMode](https://docs.unity3d.com/ScriptReference/Animator-cullingMode.html) = [AlwaysAnimate](https://docs.unity3d.com/ScriptReference/AnimatorCullingMode.AlwaysAnimate.html) in [Animator](https://docs.unity3d.com/ScriptReference/Animator.html). Otherwise, the mesh is treated as permanently invisible.

You can write a short script that will toggle [cullingMode](https://docs.unity3d.com/ScriptReference/Animator-cullingMode.html) based on [visibility](https://docs.unity3d.com/ScriptReference/Renderer-isVisible.html) to get proper culling.

## Why do i need SkinnedMeshRenderer?

My scripts uses `SkinnedMeshRenderer` to extract an array of bones from it. Yep, that's it.

The order of bones is unpredictable and does not depend on their hierarchy.

Only SkinnedMeshRenderer knows it    ¯\\\_(ツ)\_/¯

After extracting the bone array in `Start()` my script disables `SkinnedMeshRenderer` component as it is no longer needed. All the animations are performed by the script. You can verify it in the editor after hitting play button.

## Performance:

During my testing the amount of time spent on actual skinning was negligible compared to the amount of time extracting `localToWorldMatrix` from every bone in the hierarchy.

As long as you are not creating hundreds of characters with complex rigs (no matter the polycount) there should be no significant performance hit.

If anyone knows how to optimize extracting `localToWorldMatrix` of the bones please create an [issue](https://github.com/ConstantineRudenko/DQ-skinning-for-Unity/issues) or message me on [unity forum](https://forum.unity.com/threads/dual-quaternion-skinning-for-unity.501245/).

Works **A LOT** faster with IL2CPP, about 30% slower than built-in skinning in worst-case scenario according to my testing.

## How do I use custom shaders?

Alas it's complicated.

I added comments to "Standard hacked for DQ skinning" marking the alterations i made to the Standard shader.

You can try to do the same with your own shader to make it work with the script.

Feel free to contact me in [this thread](https://forum.unity.com/threads/dual-quaternion-skinning-for-unity.501245/) at unity forum if you need help.

I would also like to hear about your projects that use my script and your experience with it.

## API

[Documentation](https://constantinerudenko.github.io/Docs/DQ-skinning-for-Unity/index.html)

## Future plans

* Test/improve/optimize bulging compensation
* Implement toggle for bulging compensation
* Implement simple runtime switching between **DualQuaternionSkinned** and built-in [SkinnedMeshRenderer](https://docs.unity3d.com/Manual/class-SkinnedMeshRenderer.html)
* Implement proper animation culling (see **known bugs**)
* It might make sense to group the data from all instances of the script into one batch and run the compute shaders only once per frame regardless of how many animated characters you have

## Discussion

If you have any questions, ideas, bug reports, or just want to discuss the script, you can contact me on [unity forum](https://forum.unity.com/threads/dual-quaternion-skinning-for-unity.501245/)

## Bulging compensation method

The bulging compensation method used is described in the [article](https://journals.khnu.km.ua/vestnik/pdf/tech/pdfbase/2020/2020_1/1-2020_(281).pdf#page=12).

**Notes:**
* In formula (6) there is a mistake, **x** should be replaced by **w_2** (there is no variable named x)
* Since publishing the article I found a better polynomial instead of (9), the coefficients are as follows: 2.2; -9.6; 10.4
* In formula (12) there are weird brackets around V_bisector. This is a software bug, they should be ignored. This happens multiple times later in the article.
* The stretched region of the model in Fig (7), that looks weird even after applying the fix (though is no longer jagged), is actually caused by using improperly prepared armature. The spine and the shoulder have different local axes aligned along the joint. This contradicts one of the requirements listed at the top of page 17.


code:
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain

repo_name: Timura/rjw

path: .gitattributes


code:
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.csproj.user

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
/Archived
/MyTo-Do.txt
/LL_post.txt
/creditsnew.txt
/Assemblies/UnityEngine.UI.dll
/Assemblies/UnityEngine.dll
/Assemblies/TextMeshPro-1.0.55.56.0b11.dll
/Assemblies/NVorbis.dll
/Assemblies/NAudio.dll
/Assemblies/HugsLib.dll
/Assemblies/Assembly-CSharp.dll
/Assemblies/Assembly-CSharp-firstpass.dll
/Assemblies/0Harmony.dll
/RimJobWorld.Main.sln
/.gitignore

path: .gitignore


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<rjw.BackstoryDef>
		<defName>rjw_childC</defName>
		<baseDescription>[PAWN_nameDef] was born in civil settlement.</baseDescription>
		<title>Child</title>
		<titleShort>Child</titleShort>
		<slot>Childhood</slot>
		<categoryName>Civil</categoryName>
	</rjw.BackstoryDef>

	<rjw.BackstoryDef>
		<defName>rjw_childT</defName>
		<baseDescription>[PAWN_nameDef] was born in a tribe.</baseDescription>
		<title>Child</title>
		<titleShort>Child</titleShort>
		<slot>Childhood</slot>
		<categoryName>Tribal</categoryName>
	</rjw.BackstoryDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>Genitals</defName>
		genitals
		<hitPoints>12</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
		<tags>

RJW_Fertility

		</tags>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Chest</defName>
		chest
		<hitPoints>24</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Anus</defName>
		anus
		<hitPoints>8</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>MechGenitals</defName>
		mechanoid implanter
		<hitPoints>20</hitPoints>
		<skinCovered>true</skinCovered>
		<solid>true</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>0</bleedRate>
		<frostbiteVulnerability>0.0</frostbiteVulnerability>
		<tags>

RJW_Fertility

		</tags>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartTagDef>
		<defName>RJW_Fertility</defName>
		<label>fertility_source</label>
		<description>Neccessary for reproduction</description>
		<vital>false</vital>
	</BodyPartTagDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<DamageDef>
		<defName>ViralDamage</defName>
		<workerClass>DamageWorker_AddInjury</workerClass>
		<label>viral damage</label>
		<externalViolence>false</externalViolence>
		<deathMessage>{0} has died of a disease.</deathMessage>
		<hediff>VirusPerma</hediff>
		<hediffSkin>VirusPerma</hediffSkin>
		<hediffSolid>VirusPerma</hediffSolid>
		<makesBlood>false</makesBlood>
		<harmAllLayersUntilOutside>false</harmAllLayersUntilOutside>
	</DamageDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<ThingDef ParentName="MakeableDrugBase">
		<defName>Aphrodisiac</defName>
		Aphrodisiac drug
		<description>A smokable drug made from humpshroom.</description>
		<graphicData>
			<texPath>Things/Item/Drug/Penoxycyline</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>450</WorkToMake>
			<MarketValue>50</MarketValue>
			<Mass>0.05</Mass>
			<DeteriorationRate>6</DeteriorationRate>
			<Flammability>1.3</Flammability>
		</statBases>
		<techLevel>Medieval</techLevel>
		<ingestible>
			<foodType>Plant, Processed</foodType>
			<joyKind>Chemical</joyKind>
			<joy>0.80</joy>
			<baseIngestTicks>720</baseIngestTicks>
			<nurseable>true</nurseable>
			<drugCategory>Social</drugCategory>
			<ingestSound>Ingest_Smoke</ingestSound>
			<ingestEffect>Smoke_Joint</ingestEffect>
			<ingestEffectEat>EatVegetarian</ingestEffectEat>
			<ingestHoldOffsetStanding>
				<northDefault>
					<offset>(0.27,0,0.08)</offset>
					<behind>true</behind>
				</northDefault>
				<east>
					<offset>(0.45,0,0.08)</offset>
				</east>
				<south>
					<offset>(0.27,0,0.08)</offset>
				</south>
				<west>
					<offset>(-0.50,0,0.08)</offset>
					<flip>true</flip>
				</west>
			</ingestHoldOffsetStanding>
			<ingestHoldUsesTable>false</ingestHoldUsesTable>
			<ingestCommandString>Smoke {0}</ingestCommandString>
			<ingestReportString>Smoking {0}.</ingestReportString>
			<ingestReportStringEat>Consuming {0}.</ingestReportStringEat>
			<useEatingSpeedStat>false</useEatingSpeedStat>
			<outcomeDoers>

					<hediffDef>HumpShroomEffect</hediffDef>
					<severity>0.6</severity>
					<toleranceChemical>HumpShroom</toleranceChemical>

					<hediffDef>HumpShroomTolerance</hediffDef>
					<severity>0.042</severity>
					<divideByBodySize>true</divideByBodySize>

					<need>Sex</need>
					<offset>-0.5</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<recipeUsers>

CraftingSpot

DrugLab

			</recipeUsers>
			<workSpeedStat>DrugCookingSpeed</workSpeedStat>
			<workSkill>Cooking</workSkill>
		</recipeMaker>
		<costList>
			<HumpShroom>4</HumpShroom>
		</costList>
		<comps>

				<chemical>HumpShroom</chemical>
				<addictiveness>0.030</addictiveness>
				<minToleranceToAddict>0.15</minToleranceToAddict>
				<existingAddictionSeverityOffset>0.1</existingAddictionSeverityOffset>
				<needLevelOffset>1</needLevelOffset>
				<listOrder>30</listOrder>

		</comps>
	</ThingDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_Contraceptive</defName>
		Contraception pills
		<description>Deliver severe hit to fertility for one quadrum.</description>
		<graphicData>
			<texPath>Things/Item/Contraceptive</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_Contraceptive</hediffDef>
					<severity>1.0</severity>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_Contraceptive</defName>
		<hediffClass>HediffWithComps</hediffClass>
		contraceptive
		<description>Contraception pills deliver severe hit to fertility for one quadrum.</description>
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>900000</min>
					<max>900000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Contracepted
				<capMods>

						<capacity>RJW_Fertility</capacity>
						<setMax>0.1</setMax>

				</capMods>

		</stages>
	</HediffDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_FertPill</defName>
		Hyperfertility pills
		<description>Enhances fertility for 48 hours. Also works as a mild aphrodisiac.</description>
		<graphicData>
			<texPath>Things/Item/FertPill</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_FertPill</hediffDef>
					<severity>1.0</severity>

					<need>Sex</need>
					<offset>-0.25</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
			<HumpShroom>1</HumpShroom>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_FertPill</defName>
		<hediffClass>HediffWithComps</hediffClass>
		fertpill
		<description>Enhanced fertility. A mild aphrodisiac.</description>
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>120000</min>
					<max>120000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Hyperfertility
				<capMods>

						<capacity>RJW_Fertility</capacity>
						<offset>4</offset>

				</capMods>

		</stages>
	</HediffDef>
</Defs>