public Vertex3D SurfaceVelocity(Vertex3D surfP) { // linear velocity plus tangential velocity due to rotation return(Vel .Clone() .Add(Vertex3D.CrossProduct(AngularVelocity, surfP))); }
public void ApplyFriction(Vertex3D hitNormal, float dTime, float frictionCoeff, PlayerPhysics physics) { // surface contact point relative to center of mass var surfP = hitNormal.Clone().MultiplyScalar(-_data.Radius); var surfVel = SurfaceVelocity(surfP); // calc the tangential slip velocity var slip = surfVel.Clone().Sub(hitNormal.Clone().MultiplyScalar(surfVel.Dot(hitNormal))); var maxFriction = frictionCoeff * _data.Mass * -physics.Gravity.Dot(hitNormal); var slipSpeed = slip.Length(); Vertex3D slipDir; float numer; //#ifdef C_BALL_SPIN_HACK var normVel = Vel.Dot(hitNormal); if (normVel <= 0.025 || slipSpeed < PhysicsConstants.Precision) { // check for <=0.025 originated from ball<->rubber collisions pushing the ball upwards, but this is still not enough, some could even use <=0.2 // slip speed zero - static friction case var surfAcc = SurfaceAcceleration(surfP, physics); // calc the tangential slip acceleration var slipAcc = surfAcc.Clone() .Sub(hitNormal.Clone().MultiplyScalar(surfAcc.Dot(hitNormal))); // neither slip velocity nor slip acceleration? nothing to do here if (slipAcc.LengthSq() < 1e-6) { return; } slipDir = slipAcc.Clone().Normalize(); numer = -slipDir.Dot(surfAcc); } else { // nonzero slip speed - dynamic friction case slipDir = slip.Clone().DivideScalar(slipSpeed); numer = -slipDir.Dot(surfVel); } var cp = Vertex3D.CrossProduct(surfP, slipDir); var p1 = cp.Clone().DivideScalar(Inertia); var denom = InvMass + slipDir.Dot(Vertex3D.CrossProduct(p1, surfP)); var friction = Functions.Clamp(numer / denom, -maxFriction, maxFriction); if (!float.IsNaN(friction) && !float.IsInfinity(friction)) { ApplySurfaceImpulse( cp.Clone().MultiplyScalar(dTime * friction), slipDir.Clone().MultiplyScalar(dTime * friction) ); } }
public Vertex3D SurfaceAcceleration(Vertex3D surfP, PlayerPhysics physics) { // if we had any external torque, we would have to add "(deriv. of ang.Vel.) x surfP" here var p2 = Vertex3D.CrossProduct(AngularVelocity, surfP); var acceleration = physics.Gravity .Clone() .MultiplyScalar(InvMass) // linear acceleration .Add(Vertex3D.CrossProduct(AngularVelocity, p2)); // centripetal acceleration return(acceleration); }
private static List <HitObject> CheckJoint(HitTriangle ph3d1, HitTriangle ph3d2, IItem item) { var hitObjects = new List <HitObject>(); if (ph3d1 != null) // may be null in case of degenerate triangles { var jointNormal = Vertex3D.CrossProduct(ph3d1.Normal, ph3d2.Normal); if (jointNormal.LengthSq() < 1e-8) // coplanar triangles need no joints { return(hitObjects); } } // By convention of the calling function, points 1 [0] and 2 [1] of the second polygon will // be the common-edge points hitObjects.Add(GenerateJoint(ph3d2.Rgv[0], ph3d2.Rgv[1], item)); return(hitObjects); }
public HitTriangle(Vertex3D[] rgv, ItemType itemType, IItem item) : base(itemType, item) { Rgv = rgv; /* NB: due to the swapping of the order of e0 and e1, * the vertices must be passed in counterclockwise order * (but rendering uses clockwise order!) */ var e0 = Rgv[2].Clone().Sub(Rgv[0]); var e1 = Rgv[1].Clone().Sub(Rgv[0]); Normal = Vertex3D.CrossProduct(e0, e1); Normal.NormalizeSafe(); Elasticity = 0.3f; SetFriction(0.3f); Scatter = 0; }
public void OrthoNormalize() { var vX = new Vertex3D(Matrix[0][0], Matrix[1][0], Matrix[2][0]); var vY = new Vertex3D(Matrix[0][1], Matrix[1][1], Matrix[2][1]); var vZ = Vertex3D.CrossProduct(vX, vY); vX.Normalize(); vZ.Normalize(); var vYY = Vertex3D.CrossProduct(vZ, vX); Matrix[0][0] = vX.X; Matrix[0][1] = vYY.X; Matrix[0][2] = vZ.X; Matrix[1][0] = vX.Y; Matrix[1][1] = vYY.Y; Matrix[1][2] = vZ.Y; Matrix[2][0] = vX.Z; Matrix[2][1] = vYY.Z; Matrix[2][2] = vZ.Z; }
public static void ComputeNormals(Vertex3DNoTex2[] vertices, int numVertices, int[] indices, int numIndices) { for (var i = 0; i < numVertices; i++) { vertices[i].Nx = vertices[i].Ny = vertices[i].Nz = 0.0f; } for (var i = 0; i < numIndices; i += 3) { var a = vertices[indices[i]]; var b = vertices[indices[i + 1]]; var c = vertices[indices[i + 2]]; var e0 = new Vertex3D(b.X - a.X, b.Y - a.Y, b.Z - a.Z); var e1 = new Vertex3D(c.X - a.X, c.Y - a.Y, c.Z - a.Z); var normal = Vertex3D.CrossProduct(e0, e1); normal.NormalizeSafe(); a.Nx += normal.X; a.Ny += normal.Y; a.Nz += normal.Z; b.Nx += normal.X; b.Ny += normal.Y; b.Nz += normal.Z; c.Nx += normal.X; c.Ny += normal.Y; c.Nz += normal.Z; vertices[indices[i]] = a; vertices[indices[i + 1]] = b; vertices[indices[i + 2]] = c; } for (var i = 0; i < numVertices; i++) { var v = vertices[i]; var l = v.Nx * v.Nx + v.Ny * v.Ny + v.Nz * v.Nz; var invL = l >= Constants.FloatMin ? 1.0f / MathF.Sqrt(l) : 0.0f; v.Nx *= invL; v.Ny *= invL; v.Nz *= invL; vertices[i] = v; } }
public override void Contact(CollisionEvent coll, float dTime, PlayerPhysics physics) { var ball = coll.Ball; var normal = coll.HitNormal; //#ifdef PhysicsConstants.EMBEDDED if (coll.HitDistance < -PhysicsConstants.Embedded) { // magic to avoid balls being pushed by each other through resting flippers! ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(0.1f)); } //#endif var vRel = new Vertex3D(); var rB = new Vertex3D(); var rF = new Vertex3D(); GetRelativeVelocity(normal, ball, vRel, rB, rF); // this should be zero, but only up to +/- C_CONTACTVEL var normVel = vRel.Dot(normal); // If some collision has changed the ball's velocity, we may not have to do anything. if (normVel <= PhysicsConstants.ContactVel) { // compute accelerations of point on ball and flipper var aB = ball.Hit.SurfaceAcceleration(rB, physics); var aF = _mover.SurfaceAcceleration(rF); var aRel = aB.Clone().Sub(aF); // time derivative of the normal vector var normalDeriv = Vertex3D.CrossZ(_mover.AngleSpeed, normal); // relative acceleration in the normal direction var normAcc = aRel.Dot(normal) + 2.0f * normalDeriv.Dot(vRel); if (normAcc >= 0) { return; // objects accelerating away from each other, nothing to do } // hypothetical accelerations arising from a unit contact force in normal direction var aBc = normal.Clone().MultiplyScalar(ball.Hit.InvMass); var pv2 = normal.Clone().MultiplyScalar(-1); var cross = Vertex3D.CrossProduct(rF, pv2); var pv1 = cross.Clone().DivideScalar(_mover.Inertia); var aFc = Vertex3D.CrossProduct(pv1, rF); var contactForceAcc = normal.Dot(aBc.Clone().Sub(aFc)); // find j >= 0 such that normAcc + j * contactForceAcc >= 0 (bodies should not accelerate towards each other) var j = -normAcc / contactForceAcc; // kill any existing normal velocity ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(j * dTime * ball.Hit.InvMass - coll.HitOrgNormalVelocity)); _mover.ApplyImpulse(cross.Clone().MultiplyScalar(j * dTime)); // apply friction // first check for slippage var slip = vRel.Clone().Sub(normal.Clone().MultiplyScalar(normVel)); // calc the tangential slip velocity var maxFriction = j * Friction; var slipSpeed = slip.Length(); Vertex3D slipDir; Vertex3D crossF; float numer; float denomF; Vertex3D pv13; if (slipSpeed < PhysicsConstants.Precision) { // slip speed zero - static friction case var slipAcc = aRel.Clone().Sub(normal.Clone().MultiplyScalar(aRel.Dot(normal))); // calc the tangential slip acceleration // neither slip velocity nor slip acceleration? nothing to do here if (slipAcc.LengthSq() < 1e-6) { return; } slipDir = slipAcc.Normalize(); numer = -slipDir.Dot(aRel); crossF = Vertex3D.CrossProduct(rF, slipDir); pv13 = crossF.Clone().DivideScalar(-_mover.Inertia); denomF = slipDir.Dot(Vertex3D.CrossProduct(pv13, rF)); } else { // nonzero slip speed - dynamic friction case slipDir = slip.Clone().DivideScalar(slipSpeed); numer = -slipDir.Dot(vRel); crossF = Vertex3D.CrossProduct(rF, slipDir); pv13 = crossF.Clone().DivideScalar(_mover.Inertia); denomF = slipDir.Dot(Vertex3D.CrossProduct(pv13, rF)); } var crossB = Vertex3D.CrossProduct(rB, slipDir); var pv12 = crossB.Clone().DivideScalar(ball.Hit.Inertia); var denomB = ball.Hit.InvMass + slipDir.Dot(Vertex3D.CrossProduct(pv12, rB)); var friction = Functions.Clamp(numer / (denomB + denomF), -maxFriction, maxFriction); ball.Hit.ApplySurfaceImpulse( crossB.Clone().MultiplyScalar(dTime * friction), slipDir.Clone().MultiplyScalar(dTime * friction) ); _mover.ApplyImpulse(crossF.Clone().MultiplyScalar(-dTime * friction)); } }
public override void Collide(CollisionEvent coll, PlayerPhysics physics) { var ball = coll.Ball; var normal = coll.HitNormal; var vRel = new Vertex3D(); var rB = new Vertex3D(); var rF = new Vertex3D(); GetRelativeVelocity(normal, ball, vRel, rB, rF); var bnv = normal.Dot(vRel); // relative normal velocity if (bnv >= -PhysicsConstants.LowNormVel) { // nearly receding ... make sure of conditions if (bnv > PhysicsConstants.LowNormVel) { // otherwise if clearly approaching .. process the collision // is this velocity clearly receding (i.E must > a minimum) return; } //#ifdef PhysicsConstants.EMBEDDED if (coll.HitDistance < -PhysicsConstants.Embedded) { bnv = -PhysicsConstants.EmbedShot; // has ball become embedded???, give it a kick } else { return; } //#endif } //#ifdef PhysicsConstants.DISP_GAIN // correct displacements, mostly from low velocity blindness, an alternative to true acceleration processing var hitDist = -PhysicsConstants.DispGain * coll.HitDistance; // distance found in hit detection if (hitDist > 1.0e-4) { if (hitDist > PhysicsConstants.DispLimit) { hitDist = PhysicsConstants.DispLimit; // crossing ramps, delta noise } // push along norm, back to free area; use the norm, but is not correct ball.State.Pos.Add(coll.HitNormal.Clone().MultiplyScalar(hitDist)); } //#endif // angular response to impulse in normal direction var angResp = Vertex3D.CrossProduct(rF, normal); /* * Check if flipper is in contact with its stopper and the collision impulse * would push it beyond the stopper. In that case, don"t allow any transfer * of kinetic energy from ball to flipper. This avoids overly dead bounces * in that case. */ var angImp = -angResp.Z; // minus because impulse will apply in -normal direction var flipperResponseScaling = 1.0f; if (_mover.IsInContact && _mover.ContactTorque * angImp >= 0f) { // if impulse pushes against stopper, allow no loss of kinetic energy to flipper // (still allow flipper recoil, but a diminished amount) angResp.SetZero(); flipperResponseScaling = 0.5f; } /* * Rubber has a coefficient of restitution which decreases with the impact velocity. * We use a heuristic model which decreases the COR according to a falloff parameter: * 0 = no falloff, 1 = half the COR at 1 m/s (18.53 speed units) */ var epsilon = Functions.ElasticityWithFalloff(Elasticity, ElasticityFalloff, bnv); var pv1 = angResp.Clone().DivideScalar(_mover.Inertia); var impulse = -(1.0f + epsilon) * bnv / (ball.Hit.InvMass + normal.Dot(Vertex3D.CrossProduct(pv1, rF))); var flipperImp = normal.Clone().MultiplyScalar(-(impulse * flipperResponseScaling)); var rotI = Vertex3D.CrossProduct(rF, flipperImp); if (_mover.IsInContact) { if (rotI.Z * _mover.ContactTorque < 0) { // pushing against the solenoid? // Get a bound on the time the flipper needs to return to static conditions. // If it"s too short, we treat the flipper as static during the whole collision. var recoilTime = -rotI.Z / _mover.ContactTorque; // time flipper needs to eliminate this impulse, in 10ms // Check ball normal velocity after collision. If the ball rebounded // off the flipper, we need to make sure it does so with full // reflection, i.E., treat the flipper as static, otherwise // we get overly dead bounces. var bnvAfter = bnv + impulse * ball.Hit.InvMass; if (recoilTime <= 0.5 || bnvAfter > 0) { // treat flipper as static for this impact impulse = -(1.0f + epsilon) * bnv * ball.Data.Mass; flipperImp.SetZero(); rotI.SetZero(); } } } ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(impulse * ball.Hit.InvMass)); // new velocity for ball after impact _mover.ApplyImpulse(rotI); // apply friction var tangent = vRel.Clone().Sub(normal.Clone().MultiplyScalar(vRel.Dot(normal))); // calc the tangential velocity var tangentSpSq = tangent.LengthSq(); if (tangentSpSq > 1e-6) { tangent.DivideScalar(MathF.Sqrt(tangentSpSq)); // normalize to get tangent direction var vt = vRel.Dot(tangent); // get speed in tangential direction // compute friction impulse var crossB = Vertex3D.CrossProduct(rB, tangent); var pv12 = crossB.Clone().DivideScalar(ball.Hit.Inertia); var kt = ball.Hit.InvMass + tangent.Dot(Vertex3D.CrossProduct(pv12, rB)); var crossF = Vertex3D.CrossProduct(rF, tangent); var pv13 = crossF.Clone().DivideScalar(_mover.Inertia); kt += tangent.Dot(Vertex3D.CrossProduct(pv13, rF)); // flipper only has angular response // friction impulse can't be greater than coefficient of friction times collision impulse (Coulomb friction cone) var maxFriction = Friction * impulse; var jt = Functions.Clamp(-vt / kt, -maxFriction, maxFriction); ball.Hit.ApplySurfaceImpulse( crossB.Clone().MultiplyScalar(jt), tangent.Clone().MultiplyScalar(jt) ); _mover.ApplyImpulse(crossF.Clone().MultiplyScalar(-jt)); } if (bnv < -0.25 && physics.TimeMsec - _lastHitTime > 250) { // limit rate to 250 milliseconds per event var flipperHit = coll.HitMomentBit ? -1.0 : -bnv; // move event processing to end of collision handler... if (flipperHit < 0) { _events.FireGroupEvent(Event.HitEventsHit); // simple hit event } else { // collision velocity (normal to face) _events.FireVoidEventParam(Event.FlipperEventsCollide, flipperHit); } } _lastHitTime = physics.TimeMsec; // keep resetting until idle for 250 milliseconds }
private Mesh GetMesh(float playfieldHeight, float meshHeight, int detailLevel, int acc = -1, bool createHitShape = false, float margin = 0f) { var mesh = new Mesh(); // i dont understand the calculation of splineaccuracy here /cupiii var accuracy = (int)(10.0f * 1.2f); if (acc != -1) { // hit shapes and UI display have the same, static, precision accuracy = acc; } var splineAccuracy = acc != -1 ? 4.0f * MathF.Pow(10.0f, (10.0f - PhysicsConstants.HitShapeDetailLevel) * (float)(1.0 / 1.5)) : -1.0f; SplineVertex sv = new SplineVertex(_data.DragPoints, (int)(_data.Thickness + 0.5), detailLevel, splineAccuracy, margin: margin, loop: true); var height = playfieldHeight + meshHeight; // one ring for each Splinevertex var numRings = sv.VertexCount; var numSegments = accuracy; var up = new Vertex3D(0f, 0f, 1f); var points = new Vertex3D[numRings]; // middlepoints of rings var tangents = new Vertex3D[numRings]; // pointing into the direction of the spline, even first and last var right = new Vertex3D[numRings]; // pointing right, looking into tangent direction var down = new Vertex3D[numRings]; // pointing down from tangent view var accLength = new float[numRings]; // accumulated length of the wire beginning at 0; // copy the data from the pline into the middle of the new variables for (int i = 0; i < sv.VertexCount; i++) { points[i] = new Vertex3D(sv.MiddlePoints[i].X, sv.MiddlePoints[i].Y, height); right[i] = new Vertex3D(sv.RgvLocal[i].X - sv.MiddlePoints[i].X, sv.RgvLocal[i].Y - sv.MiddlePoints[i].Y, 0f); right[i].Normalize(); tangents[i] = Vertex3D.CrossProduct(right[i], new Vertex3D(0f, 0f, 1f)); tangents[i].Normalize(); } // calculate downvectors for (int i = 0; i < numRings; i++) { down[i] = Vertex3D.CrossProduct(right[i], tangents[i]); down[i].Normalize(); } // For UV calculation we need the whole length of the rubber accLength[0] = 0.0f; for (int i = 1; i < numRings; i++) { accLength[i] = accLength[i - 1] + (points[i] - points[i - 1]).Length(); } // add the lenth from the last ring to the first ring var totalLength = accLength[numRings - 1] + (points[0] - points[numRings - 1]).Length();; var numVertices = (numRings + 1) * numSegments; var numIndices = numRings * numSegments * 6; mesh.Vertices = new Vertex3DNoTex2[numVertices]; mesh.Indices = new int[numIndices]; // precalculate the rings (positive X is left, positive Y is up) Starting at the bottom clockwise (X=0, Y=1) var ringsX = new float[numSegments]; var ringsY = new float[numSegments]; for (int i = 0; i < numSegments; i++) { ringsX[i] = -1.0f * (float)System.Math.Sin(System.Math.PI * 2 * i / numSegments) * _data.Thickness; ringsY[i] = -1.0f * (float)System.Math.Cos(System.Math.PI + System.Math.PI * 2 * i / numSegments) * _data.Thickness; } var verticesIndex = 0; var indicesIndex = 0; // calculate Vertices first for (int currentRing = 0; currentRing < numRings; currentRing++) { // calculate one ring for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { mesh.Vertices[verticesIndex++] = new Vertex3DNoTex2 { X = points[currentRing].X + right[currentRing].X * ringsX[currentSegment] + down[currentRing].X * ringsY[currentSegment], Y = points[currentRing].Y + right[currentRing].Y * ringsX[currentSegment] + down[currentRing].Y * ringsY[currentSegment], Z = points[currentRing].Z + right[currentRing].Z * ringsX[currentSegment] + down[currentRing].Z * ringsY[currentSegment], //normals seem to be somehow off, but are caculated again at the end of mesh creation. Nx = right[currentRing].X * ringsX[currentSegment] + down[currentRing].X * ringsY[currentSegment], Ny = right[currentRing].Y * ringsX[currentSegment] + down[currentRing].Y * ringsY[currentSegment], Nz = right[currentRing].Z * ringsX[currentSegment] + down[currentRing].Z * ringsY[currentSegment], Tu = accLength[currentRing] / totalLength, Tv = (float)currentSegment / ((float)numSegments - 1) }; } // could be integrated in above for loop, but better to read and will be optimized anyway by compiler if (currentRing > 0) { for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { var csp1 = currentSegment + 1; if (csp1 >= numSegments) { csp1 = 0; } mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + csp1; mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + csp1; mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + csp1; } } } // copy first ring into last ring for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { mesh.Vertices[verticesIndex++] = new Vertex3DNoTex2 { X = points[0].X + right[0].X * ringsX[currentSegment] + down[0].X * ringsY[currentSegment], Y = points[0].Y + right[0].Y * ringsX[currentSegment] + down[0].Y * ringsY[currentSegment], Z = points[0].Z + right[0].Z * ringsX[currentSegment] + down[0].Z * ringsY[currentSegment], //normals seem to be somehow off, but are caculated again at the end of mesh creation. Nx = right[0].X * ringsX[currentSegment] + down[0].X * ringsY[currentSegment], Ny = right[0].Y * ringsX[currentSegment] + down[0].Y * ringsY[currentSegment], Nz = right[0].Z * ringsX[currentSegment] + down[0].Z * ringsY[currentSegment], Tu = 1f, Tv = (float)currentSegment / ((float)numSegments - 1) }; } for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { var csp1 = currentSegment + 1; if (csp1 >= numSegments) { csp1 = 0; } mesh.Indices[indicesIndex++] = (numRings - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = (numRings) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = (numRings) * numSegments + csp1; mesh.Indices[indicesIndex++] = (numRings - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = (numRings) * numSegments + csp1; mesh.Indices[indicesIndex++] = (numRings - 1) * numSegments + csp1; } Mesh.ComputeNormals(mesh.Vertices, numVertices, mesh.Indices, numIndices); var maxX = Constants.FloatMin; var minX = Constants.FloatMax; var maxY = Constants.FloatMin; var minY = Constants.FloatMax; var maxZ = Constants.FloatMin; var minZ = Constants.FloatMax; for (var i = 0; i < numVertices; i++) { MathF.Max(maxX, mesh.Vertices[i].X); MathF.Max(maxY, mesh.Vertices[i].Y); MathF.Max(maxZ, mesh.Vertices[i].Z); MathF.Min(minX, mesh.Vertices[i].X); MathF.Min(minY, mesh.Vertices[i].X); MathF.Min(minZ, mesh.Vertices[i].X); } _middlePoint.X = (maxX + minX) * 0.5f; _middlePoint.Y = (maxY + minY) * 0.5f; _middlePoint.Z = (maxZ + minZ) * 0.5f; return(mesh); }
private Vertex3DNoTex2[] CreateWire(int numRings, int numSegments, IReadOnlyList <Vertex2D> midPoints, IReadOnlyList <float> initialHeights) { var vertices = new Vertex3DNoTex2[numRings * numSegments]; var prev = new Vertex3D(); var index = 0; for (var i = 0; i < numRings; i++) { var i2 = i == numRings - 1 ? i : i + 1; var height = initialHeights[i]; var tangent = new Vertex3D( midPoints[i2].X - midPoints[i].X, midPoints[i2].Y - midPoints[i].Y, initialHeights[i2] - initialHeights[i] ); if (i == numRings - 1) { // for the last spline point use the previous tangent again, otherwise we won't see the complete wire (it stops one control point too early) tangent.X = midPoints[i].X - midPoints[i - 1].X; tangent.Y = midPoints[i].Y - midPoints[i - 1].Y; } Vertex3D biNormal; Vertex3D normal; if (i == 0) { var up = new Vertex3D( midPoints[i2].X + midPoints[i].X, midPoints[i2].Y + midPoints[i].Y, initialHeights[i2] - height ); normal = Vertex3D.CrossProduct(tangent, up); //normal biNormal = Vertex3D.CrossProduct(tangent, normal); } else { normal = Vertex3D.CrossProduct(prev, tangent); biNormal = Vertex3D.CrossProduct(tangent, normal); } biNormal.Normalize(); normal.Normalize(); prev = biNormal; var invNumRings = 1.0f / numRings; var invNumSegments = 1.0f / numSegments; var u = i * invNumRings; for (var j = 0; j < numSegments; j++, index++) { var v = (j + u) * invNumSegments; var tmp = Vertex3D.GetRotatedAxis(j * (360.0f * invNumSegments), tangent, normal) * (_data.WireDiameter * 0.5f); vertices[index] = new Vertex3DNoTex2 { X = midPoints[i].X + tmp.X, Y = midPoints[i].Y + tmp.Y, Z = height + tmp.Z, Tu = u, Tv = v }; // normals var n = new Vertex3D( vertices[index].X - midPoints[i].X, vertices[index].Y - midPoints[i].Y, vertices[index].Z - height ); var len = 1.0f / MathF.Sqrt(n.X * n.X + n.Y * n.Y + n.Z * n.Z); vertices[index].Nx = n.X * len; vertices[index].Ny = n.Y * len; vertices[index].Nz = n.Z * len; } } return(vertices); }
public Mesh GetMesh(Table.Table table, int acc = -1, bool createHitShape = false) { var mesh = new Mesh(_data.Name); var accuracy = (int)(10.0f * 1.2f); if (acc != -1) // hit shapes and UI display have the same, static, precision { accuracy = acc; } var splineAccuracy = acc != -1 ? 4.0f * MathF.Pow(10.0f, (10.0f - PhysicsConstants.HitShapeDetailLevel) * (float)(1.0 / 1.5)) : -1.0f; var sv = new SplineVertex(_data.DragPoints, _data.Thickness, table.GetDetailLevel(), splineAccuracy); var numRings = sv.VertexCount - 1; var numSegments = accuracy; var numVertices = numRings * numSegments; var numIndices = 6 * numVertices; //m_numVertices*2+2; var height = _data.HitHeight + table.TableHeight; mesh.Vertices = new Vertex3DNoTex2[numVertices]; mesh.Indices = new int[numIndices]; var prevB = new Vertex3D(); var invNr = 1.0f / numRings; var invNs = 1.0f / numSegments; var index = 0; for (var i = 0; i < numRings; i++) { var i2 = i == numRings - 1 ? 0 : i + 1; var tangent = new Vertex3D(sv.MiddlePoints[i2].X - sv.MiddlePoints[i].X, sv.MiddlePoints[i2].Y - sv.MiddlePoints[i].Y, 0.0f); Vertex3D biNormal; Vertex3D normal; if (i == 0) { var up = new Vertex3D(sv.MiddlePoints[i2].X + sv.MiddlePoints[i].X, sv.MiddlePoints[i2].Y + sv.MiddlePoints[i].Y, height * 2.0f); normal = new Vertex3D(tangent.Y * up.Z, -tangent.X * up.Z, tangent.X * up.Y - tangent.Y * up.X); // = CrossProduct(tangent, up) biNormal = new Vertex3D(tangent.Y * normal.Z, -tangent.X * normal.Z, tangent.X * normal.Y - tangent.Y * normal.X); // = CrossProduct(tangent, normal) } else { normal = Vertex3D.CrossProduct(prevB, tangent); biNormal = Vertex3D.CrossProduct(tangent, normal); } biNormal.Normalize(); normal.Normalize(); prevB = biNormal; var u = i * invNr; for (var j = 0; j < numSegments; j++) { var v = ((float)j + u) * invNs; var tmp = Vertex3D.GetRotatedAxis(j * (360.0f * invNs), tangent, normal) * (_data.Thickness * 0.5f); mesh.Vertices[index] = new Vertex3DNoTex2 { X = sv.MiddlePoints[i].X + tmp.X, Y = sv.MiddlePoints[i].Y + tmp.Y }; if (createHitShape && (j == 0 || j == 3)) { //!! hack, adapt if changing detail level for hitshape // for a hit shape create a more rectangle mesh and not a smooth one tmp.Z *= 0.6f; } mesh.Vertices[index].Z = height + tmp.Z; //texel mesh.Vertices[index].Tu = u; mesh.Vertices[index].Tv = v; index++; } } // calculate faces for (var i = 0; i < numRings; i++) { for (var j = 0; j < numSegments; j++) { var quad = new int[4]; quad[0] = i * numSegments + j; if (j != numSegments - 1) { quad[1] = i * numSegments + j + 1; } else { quad[1] = i * numSegments; } if (i != numRings - 1) { quad[2] = (i + 1) * numSegments + j; if (j != numSegments - 1) { quad[3] = (i + 1) * numSegments + j + 1; } else { quad[3] = (i + 1) * numSegments; } } else { quad[2] = j; if (j != numSegments - 1) { quad[3] = j + 1; } else { quad[3] = 0; } } mesh.Indices[(i * numSegments + j) * 6] = quad[0]; mesh.Indices[(i * numSegments + j) * 6 + 1] = quad[1]; mesh.Indices[(i * numSegments + j) * 6 + 2] = quad[2]; mesh.Indices[(i * numSegments + j) * 6 + 3] = quad[3]; mesh.Indices[(i * numSegments + j) * 6 + 4] = quad[2]; mesh.Indices[(i * numSegments + j) * 6 + 5] = quad[1]; } } Mesh.ComputeNormals(mesh.Vertices, numVertices, mesh.Indices, numIndices); var maxX = Constants.FloatMin; var minX = Constants.FloatMax; var maxY = Constants.FloatMin; var minY = Constants.FloatMax; var maxZ = Constants.FloatMin; var minZ = Constants.FloatMax; for (var i = 0; i < numVertices; i++) { if (maxX < mesh.Vertices[i].X) { maxX = mesh.Vertices[i].X; } if (minX > mesh.Vertices[i].X) { minX = mesh.Vertices[i].X; } if (maxY < mesh.Vertices[i].Y) { maxY = mesh.Vertices[i].Y; } if (minY > mesh.Vertices[i].Y) { minY = mesh.Vertices[i].Y; } if (maxZ < mesh.Vertices[i].Z) { maxZ = mesh.Vertices[i].Z; } if (minZ > mesh.Vertices[i].Z) { minZ = mesh.Vertices[i].Z; } } _data.MiddlePoint.X = (maxX + minX) * 0.5f; _data.MiddlePoint.Y = (maxY + minY) * 0.5f; _data.MiddlePoint.Z = (maxZ + minZ) * 0.5f; return(mesh); }
public void Collide3DWall(Vertex3D hitNormal, float elasticity, float elasticityFalloff, float friction, float scatterAngle) { // speed normal to wall var dot = Vel.Dot(hitNormal); if (dot >= -PhysicsConstants.LowNormVel) { // nearly receding ... make sure of conditions if (dot > PhysicsConstants.LowNormVel) { // otherwise if clearly approaching .. process the collision return; // is this velocity clearly receding (i.E must > a minimum) } //#ifdef PhysicsConstants.Embedded if (Coll.HitDistance < -PhysicsConstants.Embedded) { dot = -PhysicsConstants.EmbedShot; // has ball become embedded???, give it a kick } else { return; } //#endif } //#ifdef PhysicsConstants.DispGain // correct displacements, mostly from low velocity, alternative to acceleration processing var hDist = -PhysicsConstants.DispGain * Coll.HitDistance; // limit delta noise crossing ramps, if (hDist > 1.0e-4) { // when hit detection checked it what was the displacement if (hDist > PhysicsConstants.DispLimit) { hDist = PhysicsConstants.DispLimit; // crossing ramps, delta noise } // push along norm, back to free area _state.Pos.Add(hitNormal.Clone().MultiplyScalar(hDist)); // use the norm, but this is not correct, reverse time is correct } //#endif // magnitude of the impulse which is just sufficient to keep the ball from // penetrating the wall (needed for friction computations) var reactionImpulse = _data.Mass * MathF.Abs(dot); elasticity = Functions.ElasticityWithFalloff(elasticity, elasticityFalloff, dot); dot *= -(1.0f + elasticity); Vel.Add(hitNormal.Clone().MultiplyScalar(dot)); // apply collision impulse (along normal, so no torque) // compute friction impulse var surfP = hitNormal.Clone().MultiplyScalar(-_data.Radius); // surface contact point relative to center of mass var surfVel = SurfaceVelocity(surfP); // velocity at impact point var tangent = surfVel.Clone() // calc the tangential velocity .Sub(hitNormal.Clone() .MultiplyScalar(surfVel.Dot(hitNormal))); var tangentSpSq = tangent.LengthSq(); if (tangentSpSq > 1e-6) { tangent.DivideScalar(MathF.Sqrt(tangentSpSq)); // normalize to get tangent direction var vt = surfVel.Dot(tangent); // get speed in tangential direction // compute friction impulse var cross = Vertex3D.CrossProduct(surfP, tangent); var crossInertia = cross.Clone().DivideScalar(Inertia); var kt = InvMass + tangent.Dot(Vertex3D.CrossProduct(crossInertia, surfP)); // friction impulse can"t be greather than coefficient of friction times collision impulse (Coulomb friction cone) var maxFric = friction * reactionImpulse; var jt = Functions.Clamp(-vt / kt, -maxFric, maxFric); if (!float.IsNaN(jt) && !float.IsInfinity(jt)) { ApplySurfaceImpulse( cross.Clone().MultiplyScalar(jt), tangent.Clone().MultiplyScalar(jt) ); } } if (scatterAngle < 0.0) { scatterAngle = HardScatter; } // if < 0 use global value scatterAngle *= _tableData.GlobalDifficulty; // apply difficulty weighting if (dot > 1.0 && scatterAngle > 1.0e-5) { // no scatter at low velocity var scatter = MathF.Random() * 2 - 1; // -1.0f..1.0f scatter *= (1.0f - scatter * scatter) * 2.59808f * scatterAngle; // shape quadratic distribution and scale var radSin = MathF.Sin(scatter); // Green's transform matrix... rotate angle delta var radCos = MathF.Cos(scatter); // rotational transform from current position to position at time t var vxt = Vel.X; var vyt = Vel.Y; Vel.X = vxt * radCos - vyt * radSin; // rotate to random scatter angle Vel.Y = vyt * radCos + vxt * radSin; } }
private Mesh GetMesh(float playfieldHeight, float meshHeight, int detailLevel, float bendradius, int acc = -1, bool createHitShape = false, float margin = 0f) { var mesh = new Mesh(); // i dont understand the calculation of splineaccuracy here /cupiii var accuracy = (int)(10.0f * 1.2f); if (acc != -1) // hit shapes and UI display have the same, static, precision { accuracy = acc; } var splineAccuracy = acc != -1 ? 4.0f * MathF.Pow(10.0f, (10.0f - PhysicsConstants.HitShapeDetailLevel) * (float)(1.0 / 1.5)) : -1.0f; SplineVertex sv = new SplineVertex(_data.DragPoints, (int)(_data.Thickness + 0.5), detailLevel, splineAccuracy, margin: margin, loop: false); var height = playfieldHeight + meshHeight; var standheight = _data.Standheight; // dont lat the Collider be higher than the visible mesh, just shift the top of the MWG. if (createHitShape) { standheight = standheight - _data.Height + height; } // one ring for each Splinevertex, two for the stands, and "bendradius" tomes two for the bend (should be enough) // todo: could be better, if accuracy was taken into account var numRingsInBend = (int)(bendradius + 1); var numRings = sv.VertexCount - 1 + numRingsInBend * 2 + 2; var numSegments = accuracy; var up = new Vertex3D(0f, 0f, 1f); var points = new Vertex3D[numRings]; // middlepoints of rings var tangents = new Vertex3D[numRings]; // pointing into the direction of the spline, even first and last var right = new Vertex3D[numRings]; // pointing right, looking into tangent direction var down = new Vertex3D[numRings]; // pointing down from tangent view var accLength = new float[numRings]; // accumulated length of the wire beginning at 0; // copy the data from the pline into the middle of the new variables for (int i = 0; i < sv.VertexCount - 1; i++) { points[i + numRingsInBend + 1] = new Vertex3D(sv.MiddlePoints[i].X, sv.MiddlePoints[i].Y, height); right[i + numRingsInBend + 1] = new Vertex3D(sv.RgvLocal[i].X - sv.MiddlePoints[i].X, sv.RgvLocal[i].Y - sv.MiddlePoints[i].Y, 0f); right[i + numRingsInBend + 1].Normalize(); tangents[i + numRingsInBend + 1] = Vertex3D.CrossProduct(right[i + numRingsInBend + 1], new Vertex3D(0f, 0f, 1f)); tangents[i + numRingsInBend + 1].Normalize(); } // first set up beginning of the stand points[0] = points[numRingsInBend + 1] + tangents[numRingsInBend + 1] * bendradius * -1 + up * standheight * -1f; tangents[0] = new Vertex3D(0f, 0f, 1f); right[0] = right[numRingsInBend + 1]; // set up the first point of the bend points[1] = points[numRingsInBend + 1] + tangents[numRingsInBend + 1] * bendradius * -1 + up * bendradius * -1f; tangents[1] = tangents[0]; right[1] = right[0]; // now bend from point 1 to numRingsInBend+1(-1) var diffXY = points[numRingsInBend + 1] - points[1]; diffXY.Z = 0; var diffZ = points[numRingsInBend + 1] - points[1]; diffZ.X = 0; diffZ.Y = 0; for (int i = 1; i < (numRingsInBend + 1); i++) { points[numRingsInBend - i + 1] = points[1] + diffXY - (float)System.Math.Sin(System.Math.PI / 2 / numRingsInBend * i) * diffXY + (float)System.Math.Cos(System.Math.PI / 2 / numRingsInBend * i) * diffZ; var tmp = tangents[numRingsInBend + 1]; tmp.Normalize(); tangents[numRingsInBend - i + 1] = tmp * (float)System.Math.Cos(System.Math.PI / 2 / numRingsInBend * i) + (float)System.Math.Sin(System.Math.PI / 2 / numRingsInBend * i) * up; right[numRingsInBend - i + 1] = right[0]; } // set up last point points[numRings - 1] = points[(numRings - 1) - numRingsInBend - 1] + tangents[numRings - 1 - numRingsInBend - 1] * bendradius + up * standheight * -1f; tangents[numRings - 1] = new Vertex3D(0f, 0f, -1f); right[numRings - 1] = right[(numRings - 1) - numRingsInBend - 1]; // and the point before points[numRings - 2] = points[(numRings - 1) - numRingsInBend - 1] + tangents[numRings - 1 - numRingsInBend - 1] * bendradius + up * bendradius * -1f; tangents[numRings - 2] = tangents[numRings - 1]; right[numRings - 2] = right[numRings - 1]; // now bend again diffXY = points[numRings - 1 - numRingsInBend - 1] - points[numRings - 2]; diffXY.Z = 0; diffZ = points[numRings - 1 - numRingsInBend - 1] - points[numRings - 2]; diffZ.X = 0; diffZ.Y = 0; for (int i = 1; i < (numRingsInBend + 1); i++) { points[numRings - 2 - numRingsInBend + i] = points[numRings - 2] + diffXY - (float)System.Math.Sin(System.Math.PI / 2 / numRingsInBend * i) * diffXY + (float)System.Math.Cos(System.Math.PI / 2 / numRingsInBend * i) * diffZ; var tmp = tangents[numRings - 1 - numRingsInBend - 1]; tmp.Normalize(); tangents[numRings - 2 - numRingsInBend + i] = tmp * (float)System.Math.Cos(System.Math.PI / 2 / numRingsInBend * i) + (float)System.Math.Sin(System.Math.PI / 2 / numRingsInBend * i) * up * -1; right[numRings - 2 - numRingsInBend + i] = right[numRings - 1]; } // calculate downvectors for (int i = 0; i < numRings; i++) { down[i] = Vertex3D.CrossProduct(right[i], tangents[i]); down[i].Normalize(); } // For UV calculation we need the whole length of the wire accLength[0] = 0.0f; for (int i = 1; i < numRings; i++) { accLength[i] = accLength[i - 1] + (points[i] - points[i - 1]).Length(); } var totalLength = accLength[numRings - 1]; var numVertices = numRings * numSegments; var numIndices = (numRings - 1) * numSegments * 6; mesh.Vertices = new Vertex3DNoTex2[numVertices]; mesh.Indices = new int[numIndices]; // precalculate the rings (positive X is left, positive Y is up) Starting at the bottom clockwise (X=0, Y=1) var ringsX = new float[numSegments]; var ringsY = new float[numSegments]; for (int i = 0; i < numSegments; i++) { ringsX[i] = -1.0f * (float)System.Math.Sin(System.Math.PI * 2 * i / numSegments) * _data.Thickness; ringsY[i] = -1.0f * (float)System.Math.Cos(System.Math.PI + System.Math.PI * 2 * i / numSegments) * _data.Thickness; } var verticesIndex = 0; var indicesIndex = 0; // calculate Vertices first for (int currentRing = 0; currentRing < numRings; currentRing++) { // calculate one ring for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { mesh.Vertices[verticesIndex++] = new Vertex3DNoTex2 { X = points[currentRing].X + right[currentRing].X * ringsX[currentSegment] + down[currentRing].X * ringsY[currentSegment], Y = points[currentRing].Y + right[currentRing].Y * ringsX[currentSegment] + down[currentRing].Y * ringsY[currentSegment], Z = points[currentRing].Z + right[currentRing].Z * ringsX[currentSegment] + down[currentRing].Z * ringsY[currentSegment], //normals seem to be somehow off, but are caculated again at the end of mesh creation. Nx = right[currentRing].X * ringsX[currentSegment] + down[currentRing].X * ringsY[currentSegment], Ny = right[currentRing].Y * ringsX[currentSegment] + down[currentRing].Y * ringsY[currentSegment], Nz = right[currentRing].Z * ringsX[currentSegment] + down[currentRing].Z * ringsY[currentSegment], Tu = accLength[currentRing] / totalLength, Tv = (float)currentSegment / ((float)numSegments - 1) }; } // could be integrated in above for loop, but better to read and will be optimized anyway by compiler if (currentRing > 0) { for (int currentSegment = 0; currentSegment < numSegments; currentSegment++) { var csp1 = currentSegment + 1; if (csp1 >= numSegments) { csp1 = 0; } mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + csp1; mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + currentSegment; mesh.Indices[indicesIndex++] = currentRing * numSegments + csp1; mesh.Indices[indicesIndex++] = (currentRing - 1) * numSegments + csp1; } } } Mesh.ComputeNormals(mesh.Vertices, numVertices, mesh.Indices, numIndices); var maxX = Constants.FloatMin; var minX = Constants.FloatMax; var maxY = Constants.FloatMin; var minY = Constants.FloatMax; var maxZ = Constants.FloatMin; var minZ = Constants.FloatMax; for (var i = 0; i < numVertices; i++) { MathF.Max(maxX, mesh.Vertices[i].X); MathF.Max(maxY, mesh.Vertices[i].Y); MathF.Max(maxZ, mesh.Vertices[i].Z); MathF.Min(minX, mesh.Vertices[i].X); MathF.Min(minY, mesh.Vertices[i].X); MathF.Min(minZ, mesh.Vertices[i].X); } _middlePoint.X = (maxX + minX) * 0.5f; _middlePoint.Y = (maxY + minY) * 0.5f; _middlePoint.Z = (maxZ + minZ) * 0.5f; return(mesh); }