3D Physics Based Rope
From Unify Community Wiki
(Difference between revisions)
m (→Rope_Line.js: Updated for Unity v3.5 compatibility) |
m |
||
(One intermediate revision by one user not shown) | |||
Line 1: | Line 1: | ||
− | ==Description | + | Author: Jacob Fletcher |
− | This script makes 3D physics based ropes for things like | + | |
+ | == Description == | ||
+ | |||
+ | This script makes 3D physics based ropes for things like power-lines blowing in the wind, hoses flopping around, ropes you can swing on, and more. | ||
+ | |||
+ | == Usage == | ||
+ | |||
+ | Drop this script into one object, then add the second into the "target" variable and your ready to go. | ||
+ | |||
== Rope_Line.js == | == Rope_Line.js == | ||
Line 165: | Line 173: | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
//TubeRenderer.js | //TubeRenderer.js | ||
− | + | ||
//This script is created by Ray Nothnagel of Last Bastion Games. It is | //This script is created by Ray Nothnagel of Last Bastion Games. It is | ||
//free for use and available on the Unify Wiki. | //free for use and available on the Unify Wiki. | ||
− | + | ||
//For other components I've created, see: | //For other components I've created, see: | ||
//http://lastbastiongames.com/middleware/ | //http://lastbastiongames.com/middleware/ | ||
− | + | ||
//(C) 2008 Last Bastion Games | //(C) 2008 Last Bastion Games | ||
− | + | ||
//-------------------------------------------------------------- | //-------------------------------------------------------------- | ||
− | + | ||
//EDIT: MODIFIED BY JACOB FLETCHER FOR USE WITH THE ROPE SCRIPT | //EDIT: MODIFIED BY JACOB FLETCHER FOR USE WITH THE ROPE SCRIPT | ||
//http://www.reverieinteractive.com | //http://www.reverieinteractive.com | ||
− | + | ||
− | + | ||
class TubeVertex { | class TubeVertex { | ||
− | + | var point : Vector3 = Vector3.zero; | |
− | + | var radius : float = 1.0; | |
− | + | var color : Color = Color.white; | |
− | + | ||
− | + | function TubeVertex(pt : Vector3, r : float, c : Color) { | |
− | + | point=pt; | |
− | + | radius=r; | |
− | + | color=c; | |
− | + | } | |
} | } | ||
− | + | ||
var vertices : TubeVertex[]; | var vertices : TubeVertex[]; | ||
var material : Material; | var material : Material; | ||
Line 199: | Line 207: | ||
var maxRebuildTime = 0.1; | var maxRebuildTime = 0.1; | ||
var useMeshCollision = false; | var useMeshCollision = false; | ||
− | + | ||
private var lastCameraPosition1 : Vector3; | private var lastCameraPosition1 : Vector3; | ||
private var lastCameraPosition2 : Vector3; | private var lastCameraPosition2 : Vector3; | ||
Line 205: | Line 213: | ||
private var lastCrossSegments : int; | private var lastCrossSegments : int; | ||
private var lastRebuildTime = 0.00; | private var lastRebuildTime = 0.00; | ||
− | private var mesh; | + | private var mesh : Mesh; |
private var colliderExists = false; | private var colliderExists = false; | ||
private var usingBumpmap = false; | private var usingBumpmap = false; | ||
− | + | ||
function Reset() { | function Reset() { | ||
− | + | vertices = [TubeVertex(Vector3.zero, 1.0, Color.white), TubeVertex(Vector3(1,0,0), 1.0, Color.white)]; | |
} | } | ||
− | + | ||
function Start() { | function Start() { | ||
− | + | Reset(); | |
− | + | mesh = new Mesh(); | |
− | + | gameObject.AddComponent(MeshFilter); | |
− | + | var mr : MeshRenderer = gameObject.AddComponent(MeshRenderer); | |
− | + | mr.material = material; | |
− | + | if(material) { | |
− | + | if(material.GetTexture("_BumpMap")) usingBumpmap = true; | |
− | + | } | |
} | } | ||
− | + | ||
function LateUpdate () { | function LateUpdate () { | ||
− | + | if (!vertices || vertices.length <= 1) { | |
− | + | renderer.enabled=false; | |
− | + | return; | |
− | + | } | |
− | + | ||
− | + | renderer.enabled=true; | |
− | + | if (crossSegments != lastCrossSegments) { | |
− | + | crossPoints = new Vector3[crossSegments]; | |
− | + | var theta : float = 2.0*Mathf.PI/crossSegments; | |
− | + | for (var c:int=0;c<crossSegments;c++) { | |
− | + | crossPoints[c] = Vector3(Mathf.Cos(theta*c), Mathf.Sin(theta*c), 0); | |
− | + | } | |
− | + | lastCrossSegments = crossSegments; | |
− | + | } | |
− | + | ||
− | + | var meshVertices : Vector3[] = new Vector3[vertices.length*crossSegments]; | |
− | + | var uvs : Vector2[] = new Vector2[vertices.length*crossSegments]; | |
− | + | var colors : Color[] = new Color[vertices.length*crossSegments]; | |
− | + | var tris : int[] = new int[vertices.length*crossSegments*6]; | |
− | + | var lastVertices : int[] = new int[crossSegments]; | |
− | + | var theseVertices : int[] = new int[crossSegments]; | |
− | + | var rotation : Quaternion; | |
− | + | ||
− | + | for (var p:int=0;p<vertices.length;p++) { | |
− | + | if(p<vertices.length-1) | |
− | + | rotation = Quaternion.FromToRotation(Vector3.forward,vertices[p+1].point-vertices[p].point); | |
− | + | ||
− | + | for (c=0;c<crossSegments;c++) { | |
− | + | var vertexIndex : int = p*crossSegments+c; | |
− | + | meshVertices[vertexIndex] = vertices[p].point + rotation * crossPoints[c] * vertices[p].radius; | |
− | + | uvs[vertexIndex] = Vector2((0.0+c)/crossSegments,(0.0+p)/vertices.length); | |
− | + | colors[vertexIndex] = vertices[p].color; | |
− | + | ||
− | + | lastVertices[c]=theseVertices[c]; | |
− | + | theseVertices[c] = p*crossSegments+c; | |
− | + | } | |
− | + | ||
− | + | //make triangles | |
− | + | if (p>0) { | |
− | + | for (c=0;c<crossSegments;c++) { | |
− | + | var start : int= (p*crossSegments+c)*6; | |
− | + | tris[start] = lastVertices[c]; | |
− | + | tris[start+1] = lastVertices[(c+1)%crossSegments]; | |
− | + | tris[start+2] = theseVertices[c]; | |
− | + | tris[start+3] = tris[start+2]; | |
− | + | tris[start+4] = tris[start+1]; | |
− | + | tris[start+5] = theseVertices[(c+1)%crossSegments]; | |
− | + | } | |
− | + | } | |
− | + | } | |
− | + | ||
− | + | //Clear mesh for new build (jf) | |
− | + | mesh.Clear(); | |
− | + | mesh.vertices = meshVertices; | |
− | + | mesh.triangles = tris; | |
− | + | mesh.RecalculateNormals(); | |
− | + | if(usingBumpmap) | |
− | + | mesh.tangents = CalculateTangents(meshVertices); | |
− | + | mesh.uv = uvs; | |
− | + | ||
− | + | if(useMeshCollision) | |
− | + | if(colliderExists) { | |
− | + | gameObject.GetComponent(MeshCollider).sharedMesh = mesh; | |
− | + | } else { | |
− | + | gameObject.AddComponent(MeshCollider); | |
− | + | colliderExists = true; | |
− | + | } | |
− | + | GetComponent(MeshFilter).mesh = mesh; | |
} | } | ||
− | + | ||
− | + | ||
− | + | ||
− | function CalculateTangents(verts) | + | function CalculateTangents(verts : Vector3[]) |
{ | { | ||
− | + | var tangents : Vector4[] = new Vector4[verts.length]; | |
− | + | ||
− | + | for(var i:int=0;i<tangents.length;i++) | |
− | + | { | |
− | + | var vertex1 = i > 0 ? verts[i-1] : verts[i]; | |
− | + | var vertex2 = i < tangents.length - 1 ? verts[i+1] : verts[i]; | |
− | + | var tan = (vertex1 - vertex2).normalized; | |
− | + | tangents[i] = Vector4( tan.x, tan.y, tan.z, 1.0 ); | |
− | + | } | |
− | + | return tangents; | |
} | } | ||
− | + | ||
− | + | ||
− | + | ||
//sets all the points to points of a Vector3 array, as well as capping the ends. | //sets all the points to points of a Vector3 array, as well as capping the ends. | ||
function SetPoints(points : Vector3[], radius : float, col : Color) { | function SetPoints(points : Vector3[], radius : float, col : Color) { | ||
− | + | if (points.length < 2) return; | |
− | + | vertices = new TubeVertex[points.length+2]; | |
− | + | ||
− | + | var v0offset : Vector3 = (points[0]-points[1])*0.01; | |
− | + | vertices[0] = TubeVertex(v0offset+points[0], 0.0, col); | |
− | + | var v1offset : Vector3 = (points[points.length-1] - points[points.length-2])*0.01; | |
− | + | vertices[vertices.length-1] = TubeVertex(v1offset+points[points.length-1], 0.0, col); | |
− | + | ||
− | + | for (var p:int=0;p<points.length;p++) { | |
− | + | vertices[p+1] = TubeVertex(points[p], radius, col); | |
− | + | } | |
− | } | + | }</syntaxhighlight> |
− | </syntaxhighlight> | + | |
[[Category: JavaScript]] | [[Category: JavaScript]] | ||
[[Category: Physics]] | [[Category: Physics]] |
Latest revision as of 16:06, 11 December 2012
Author: Jacob Fletcher
Contents |
[edit] Description
This script makes 3D physics based ropes for things like power-lines blowing in the wind, hoses flopping around, ropes you can swing on, and more.
[edit] Usage
Drop this script into one object, then add the second into the "target" variable and your ready to go.
[edit] Rope_Line.js
//============================ //== Physics Based 3D Rope == //== File: Rope_Tube.js == //== By: Jacob Fletcher == //== Use and alter Freely == //============================ //To see other things I have created, visit me at www.reverieinterative.com //How To Use: // ( BASIC ) // 1. Simply add this script to the object you want a rope teathered to // 3. Assign the other end of the rope as the "Target" object in this script // 4. Play and enjoy! // (About Character Joints) // Sometimes your rope needs to be very limp and by that I mean NO SPRINGY EFFECT. // In order to do this, you must loosen it up using the swingAxis and twist limits. // For example, On my joints in my drawing app, I set the swingAxis to (0,0,1) sense // the only axis I want to swing is the Z axis (facing the camera) and the other settings to around -100 or 100. var target : Transform; var material : Material; var ropeWidth = 0.5; var resolution = 0.5; var ropeDrag = 0.1; var ropeMass = 0.5; var radialSegments = 6; var startRestrained = true; var endRestrained = false; var useMeshCollision = false; // Private Variables (Only change if you know what your doing) private var segmentPos : Vector3[]; private var joints : GameObject[]; private var tubeRenderer : GameObject; private var line : TubeRenderer; private var segments = 4; private var rope = false; //Joint Settings var swingAxis = Vector3(0,1,0); var lowTwistLimit = 0.0; var highTwistLimit = 0.0; var swing1Limit = 20.0; // Require a Rigidbody @script RequireComponent(Rigidbody) function OnDrawGizmos() { if(target) { Gizmos.color = Color.yellow; Gizmos.DrawLine (transform.position, target.position); Gizmos.DrawWireSphere ((transform.position+target.position)/2,ropeWidth); Gizmos.color = Color.green; Gizmos.DrawWireSphere (transform.position, ropeWidth); Gizmos.color = Color.red; Gizmos.DrawWireSphere (target.position, ropeWidth); } else { Gizmos.color = Color.green; Gizmos.DrawWireSphere (transform.position, ropeWidth); } } function Awake() { if(target) { BuildRope(); } else { Debug.LogError("You must have a gameobject attached to target: " + this.name,this); } } function LateUpdate() { if(target) { // Does rope exist? If so, update its position if(rope) { line.SetPoints(segmentPos, ropeWidth, Color.white); line.enabled = true; segmentPos[0] = transform.position; for(var s:int=1;s<segments;s++) { segmentPos[s] = joints[s].transform.position; } } } } function BuildRope() { tubeRenderer = new GameObject("TubeRenderer_" + gameObject.name); line = tubeRenderer.AddComponent(TubeRenderer); line.useMeshCollision = useMeshCollision; // Find the amount of segments based on the distance and resolution // Example: [resolution of 1.0 = 1 joint per unit of distance] segments = Vector3.Distance(transform.position,target.position)*resolution; if(material) { material.SetTextureScale("_MainTex",Vector2(1,segments+2)); if(material.GetTexture("_BumpMap")) material.SetTextureScale("_BumpMap",Vector2(1,segments+2)); } line.vertices = new TubeVertex[segments]; line.crossSegments = radialSegments; line.material = material; segmentPos = new Vector3[segments]; joints = new GameObject[segments]; segmentPos[0] = transform.position; segmentPos[segments-1] = target.position; // Find the distance between each segment var segs = segments-1; var seperation = ((target.position - transform.position)/segs); for(var s:int=0;s < segments;s++) { // Find the each segments position using the slope from above var vector : Vector3 = (seperation*s) + transform.position; segmentPos[s] = vector; //Add Physics to the segments AddJointPhysics(s); } // Attach the joints to the target object and parent it to this object var end : CharacterJoint = target.gameObject.AddComponent("CharacterJoint"); end.connectedBody = joints[joints.length-1].transform.rigidbody; end.swingAxis = swingAxis; end.lowTwistLimit.limit = lowTwistLimit; end.highTwistLimit.limit = highTwistLimit; end.swing1Limit.limit = swing1Limit; target.parent = transform; if(endRestrained) { end.rigidbody.isKinematic = true; } if(startRestrained) { transform.rigidbody.isKinematic = true; } // Rope = true, The rope now exists in the scene! rope = true; } function AddJointPhysics(n : int) { joints[n] = new GameObject("Joint_" + n); joints[n].transform.parent = transform; var rigid : Rigidbody = joints[n].AddComponent("Rigidbody"); if(!useMeshCollision) { var col : SphereCollider = joints[n].AddComponent("SphereCollider"); col.radius = ropeWidth; } var ph : CharacterJoint = joints[n].AddComponent("CharacterJoint"); ph.swingAxis = swingAxis; ph.lowTwistLimit.limit = lowTwistLimit; ph.highTwistLimit.limit = highTwistLimit; ph.swing1Limit.limit = swing1Limit; //ph.breakForce = ropeBreakForce; <--------------- TODO joints[n].transform.position = segmentPos[n]; rigid.drag = ropeDrag; rigid.mass = ropeMass; if(n==0){ ph.connectedBody = transform.rigidbody; } else { ph.connectedBody = joints[n-1].rigidbody; } }
[edit] MODIFIED TubeRenderer.js
//TubeRenderer.js //This script is created by Ray Nothnagel of Last Bastion Games. It is //free for use and available on the Unify Wiki. //For other components I've created, see: //http://lastbastiongames.com/middleware/ //(C) 2008 Last Bastion Games //-------------------------------------------------------------- //EDIT: MODIFIED BY JACOB FLETCHER FOR USE WITH THE ROPE SCRIPT //http://www.reverieinteractive.com class TubeVertex { var point : Vector3 = Vector3.zero; var radius : float = 1.0; var color : Color = Color.white; function TubeVertex(pt : Vector3, r : float, c : Color) { point=pt; radius=r; color=c; } } var vertices : TubeVertex[]; var material : Material; var crossSegments : int = 3; var flatAtDistance : float=-1; var movePixelsForRebuild = 6; var maxRebuildTime = 0.1; var useMeshCollision = false; private var lastCameraPosition1 : Vector3; private var lastCameraPosition2 : Vector3; private var crossPoints : Vector3[]; private var lastCrossSegments : int; private var lastRebuildTime = 0.00; private var mesh : Mesh; private var colliderExists = false; private var usingBumpmap = false; function Reset() { vertices = [TubeVertex(Vector3.zero, 1.0, Color.white), TubeVertex(Vector3(1,0,0), 1.0, Color.white)]; } function Start() { Reset(); mesh = new Mesh(); gameObject.AddComponent(MeshFilter); var mr : MeshRenderer = gameObject.AddComponent(MeshRenderer); mr.material = material; if(material) { if(material.GetTexture("_BumpMap")) usingBumpmap = true; } } function LateUpdate () { if (!vertices || vertices.length <= 1) { renderer.enabled=false; return; } renderer.enabled=true; if (crossSegments != lastCrossSegments) { crossPoints = new Vector3[crossSegments]; var theta : float = 2.0*Mathf.PI/crossSegments; for (var c:int=0;c<crossSegments;c++) { crossPoints[c] = Vector3(Mathf.Cos(theta*c), Mathf.Sin(theta*c), 0); } lastCrossSegments = crossSegments; } var meshVertices : Vector3[] = new Vector3[vertices.length*crossSegments]; var uvs : Vector2[] = new Vector2[vertices.length*crossSegments]; var colors : Color[] = new Color[vertices.length*crossSegments]; var tris : int[] = new int[vertices.length*crossSegments*6]; var lastVertices : int[] = new int[crossSegments]; var theseVertices : int[] = new int[crossSegments]; var rotation : Quaternion; for (var p:int=0;p<vertices.length;p++) { if(p<vertices.length-1) rotation = Quaternion.FromToRotation(Vector3.forward,vertices[p+1].point-vertices[p].point); for (c=0;c<crossSegments;c++) { var vertexIndex : int = p*crossSegments+c; meshVertices[vertexIndex] = vertices[p].point + rotation * crossPoints[c] * vertices[p].radius; uvs[vertexIndex] = Vector2((0.0+c)/crossSegments,(0.0+p)/vertices.length); colors[vertexIndex] = vertices[p].color; lastVertices[c]=theseVertices[c]; theseVertices[c] = p*crossSegments+c; } //make triangles if (p>0) { for (c=0;c<crossSegments;c++) { var start : int= (p*crossSegments+c)*6; tris[start] = lastVertices[c]; tris[start+1] = lastVertices[(c+1)%crossSegments]; tris[start+2] = theseVertices[c]; tris[start+3] = tris[start+2]; tris[start+4] = tris[start+1]; tris[start+5] = theseVertices[(c+1)%crossSegments]; } } } //Clear mesh for new build (jf) mesh.Clear(); mesh.vertices = meshVertices; mesh.triangles = tris; mesh.RecalculateNormals(); if(usingBumpmap) mesh.tangents = CalculateTangents(meshVertices); mesh.uv = uvs; if(useMeshCollision) if(colliderExists) { gameObject.GetComponent(MeshCollider).sharedMesh = mesh; } else { gameObject.AddComponent(MeshCollider); colliderExists = true; } GetComponent(MeshFilter).mesh = mesh; } function CalculateTangents(verts : Vector3[]) { var tangents : Vector4[] = new Vector4[verts.length]; for(var i:int=0;i<tangents.length;i++) { var vertex1 = i > 0 ? verts[i-1] : verts[i]; var vertex2 = i < tangents.length - 1 ? verts[i+1] : verts[i]; var tan = (vertex1 - vertex2).normalized; tangents[i] = Vector4( tan.x, tan.y, tan.z, 1.0 ); } return tangents; } //sets all the points to points of a Vector3 array, as well as capping the ends. function SetPoints(points : Vector3[], radius : float, col : Color) { if (points.length < 2) return; vertices = new TubeVertex[points.length+2]; var v0offset : Vector3 = (points[0]-points[1])*0.01; vertices[0] = TubeVertex(v0offset+points[0], 0.0, col); var v1offset : Vector3 = (points[points.length-1] - points[points.length-2])*0.01; vertices[vertices.length-1] = TubeVertex(v1offset+points[points.length-1], 0.0, col); for (var p:int=0;p<points.length;p++) { vertices[p+1] = TubeVertex(points[p], radius, col); } }