public LineSeg[] GenerateLineSegs(float height, Vertex2D tangent, IItem item) { if (_gateData.TwoWay) { return(new LineSeg[0]); } var halfLength = _gateData.Length * 0.5f; var angleMin = MathF.Min(_gateData.AngleMin, _gateData.AngleMax); // correct angle inversions var angleMax = MathF.Max(_gateData.AngleMin, _gateData.AngleMax); _gateData.AngleMin = angleMin; _gateData.AngleMax = angleMax; // oversize by the ball's radius to prevent the ball from clipping through var rgv = new[] { _gateData.Center.Clone().Add(tangent.Clone().MultiplyScalar(halfLength + PhysicsConstants.PhysSkin)), _gateData.Center.Clone().Sub(tangent.Clone().MultiplyScalar(halfLength + PhysicsConstants.PhysSkin)), }; var lineSeg = new LineSeg(rgv[0], rgv[1], height, height + 2.0f * PhysicsConstants.PhysSkin, ItemType.Gate, item); //!! = ball diameter lineSeg.SetElasticity(_gateData.Elasticity); lineSeg.SetFriction(_gateData.Friction); return(new[] { lineSeg }); }
public HitCircle[] GenerateBracketHits(float height, Vertex2D tangent, IItem item) { var halfLength = _gateData.Length * 0.5f; if (_gateData.ShowBracket) { return(new[] { new HitCircle(_gateData.Center.Clone().Add(tangent.Clone().MultiplyScalar(halfLength)), 0.01f, height, height + _gateData.Height, ItemType.Gate, item), new HitCircle(_gateData.Center.Clone().Sub(tangent.Clone().MultiplyScalar(halfLength)), 0.01f, height, height + _gateData.Height, ItemType.Gate, item) }); } return(new HitCircle[0]); }
private TriggerHitLineSeg GetLineSeg(Vertex2D pv1, Vertex2D pv2, float height, IItem item) { return(new TriggerHitLineSeg( pv1.Clone(), pv2.Clone(), height, height + MathF.Max(_data.HitHeight - 8.0f, 0f), // adjust for same hit height as circular item )); }
private TriggerHitLineSeg GetLineSeg(Vertex2D pv1, Vertex2D pv2, EventProxy events, float height) { return(new TriggerHitLineSeg( pv1.Clone(), pv2.Clone(), height, height + MathF.Max(_data.HitHeight - 8.0f, 0f) // adjust for same hit height as circular ) { Obj = events }); }
private List <HitObject> GenerateWallLineSeg(Vertex2D pv1, Vertex2D pv2, bool pv3Exists, float height1, float height2, float wallHeight, IItem item) { var hitObjects = new List <HitObject>(); //!! Hit-walls are still done via 2D line segments with only a single lower and upper border, so the wall will always reach below and above the actual ramp -between- two points of the ramp // Thus, subdivide until at some point the approximation error is 'subtle' enough so that one will usually not notice (i.e. dependent on ball size) if (height2 - height1 > 2.0 * PhysicsConstants.PhysSkin) //!! use ballsize { hitObjects.AddRange(GenerateWallLineSeg(pv1, pv1.Clone().Add(pv2).MultiplyScalar(0.5f), pv3Exists, height1, (height1 + height2) * 0.5f, wallHeight, item)); hitObjects.AddRange(GenerateWallLineSeg(pv1.Clone().Add(pv2).MultiplyScalar(0.5f), pv2, true, (height1 + height2) * 0.5f, height2, wallHeight, item)); } else { hitObjects.Add(new LineSeg(pv1, pv2, height1, height2 + wallHeight, ItemType.Ramp, item)); if (pv3Exists) { hitObjects.Add(GenerateJoint2D(pv1, height1, height2 + wallHeight, item)); } } return(hitObjects); }
public SpinnerHit(SpinnerData data, float height) : base(ItemType.Spinner) { var halfLength = data.Length * 0.5f; var radAngle = MathF.DegToRad(data.Rotation); var sn = MathF.Sin(radAngle); var cs = MathF.Cos(radAngle); var v1 = new Vertex2D( data.Center.X - cs * (halfLength + PhysicsConstants.PhysSkin), // through the edge of the data.Center.Y - sn * (halfLength + PhysicsConstants.PhysSkin) // spinner ); var v2 = new Vertex2D( data.Center.X + cs * (halfLength + PhysicsConstants.PhysSkin), // oversize by the ball radius data.Center.Y + sn * (halfLength + PhysicsConstants.PhysSkin) // this will prevent clipping ); LineSeg0 = new LineSeg(v1, v2, height, height + 2.0f * PhysicsConstants.PhysSkin, ItemType.Spinner); LineSeg1 = new LineSeg(v2.Clone(), v1.Clone(), height, height + 2.0f * PhysicsConstants.PhysSkin, ItemType.Spinner); }
public override float HitTest(Ball ball, float dTime, CollisionEvent coll, PlayerPhysics physics) { if (!IsEnabled) { return(-1.0f); } var bp2d = new Vertex2D(ball.State.Pos.X, ball.State.Pos.Y); var dist = bp2d.Clone().Sub(Xy); // relative ball position var dv = new Vertex2D(ball.Hit.Vel.X, ball.Hit.Vel.Y); var bcddsq = dist.LengthSq(); // ball center to line distance squared var bcdd = MathF.Sqrt(bcddsq); // distance ball to line if (bcdd <= 1.0e-6) { // no hit on exact center return(-1.0f); } var b = dist.Dot(dv); var bnv = b / bcdd; // ball normal velocity if (bnv > PhysicsConstants.ContactVel) { // clearly receding from radius return(-1.0f); } var bnd = bcdd - ball.Data.Radius; // ball distance to line var a = dv.LengthSq(); float hitTime; var isContact = false; if (bnd < PhysicsConstants.PhysTouch) { // already in collision distance? if (MathF.Abs(bnv) <= PhysicsConstants.ContactVel) { isContact = true; hitTime = 0f; } else { // estimate based on distance and speed along distance hitTime = -bnd / bnv; } } else { if (a < 1.0e-8) { // no hit - ball not moving relative to object return(-1.0f); } var sol = Functions.SolveQuadraticEq(a, 2.0f * b, bcddsq - ball.Data.Radius * ball.Data.Radius); if (sol == null) { return(-1.0f); } var time1 = sol.Item1; var time2 = sol.Item2; // find smallest non-negative solution hitTime = time1 * time2 < 0 ? MathF.Max(time1, time2) : MathF.Min(time1, time2); } if (float.IsNaN(hitTime) || float.IsInfinity(hitTime) || hitTime < 0 || hitTime > dTime) { // contact out of physics frame return(-1.0f); } var hitZ = ball.State.Pos.Z + hitTime * ball.Hit.Vel.Z; // ball z position at hit time if (hitZ < HitBBox.ZLow || hitZ > HitBBox.ZHigh) { // check z coordinate return(-1.0f); } var hitX = ball.State.Pos.X + hitTime * ball.Hit.Vel.X; // ball x position at hit time var hitY = ball.State.Pos.Y + hitTime * ball.Hit.Vel.Y; // ball y position at hit time var norm = new Vertex2D(hitX - Xy.X, hitY - Xy.Y).Normalize(); coll.HitNormal.Set(norm.X, norm.Y, 0.0f); coll.IsContact = isContact; if (isContact) { coll.HitOrgNormalVelocity = bnv; } coll.HitDistance = bnd; // actual contact distance //coll.M_hitRigid = true; return(hitTime); }
internal RampVertex GetRampVertex(Table.Table table, float accuracy, bool incWidth) { var result = new RampVertex(); // vvertex are the 2D vertices forming the central curve of the ramp as seen from above var vertex = GetCentralCurve(table, accuracy); var numVertices = vertex.Length; result.VertexCount = numVertices; result.PointHeights = new float[numVertices]; result.Cross = new bool[numVertices]; result.PointRatios = new float[numVertices]; result.MiddlePoints = new Vertex2D[numVertices]; result.RgvLocal = new Vertex2D[_data.RampType != RampType.RampTypeFlat ? (numVertices + 1) * 2 : numVertices * 2]; // Compute an approximation to the length of the central curve // by adding up the lengths of the line segments. var totalLength = 0f; var bottomHeight = _data.HeightBottom + table.TableHeight; var topHeight = _data.HeightTop + table.TableHeight; for (var i = 0; i < numVertices - 1; i++) { var v1 = vertex[i]; var v2 = vertex[i + 1]; var dx = v1.X - v2.X; var dy = v1.Y - v2.Y; var length = MathF.Sqrt(dx * dx + dy * dy); totalLength += length; } var currentLength = 0f; for (var i = 0; i < numVertices; i++) { // clamp next and prev as ramps do not loop var prev = vertex[i > 0 ? i - 1 : i]; var next = vertex[i < numVertices - 1 ? i + 1 : i]; var middle = vertex[i]; result.Cross[i] = middle.IsControlPoint; var normal = new Vertex2D(); // Get normal at this point // Notice that these values equal the ones in the line // equation and could probably be substituted by them. var v1Normal = new Vertex2D(prev.Y - middle.Y, middle.X - prev.X); // vector vmiddle-vprev rotated RIGHT var v2Normal = new Vertex2D(middle.Y - next.Y, next.X - middle.X); // vector vnext-vmiddle rotated RIGHT // special handling for beginning and end of the ramp, as ramps do not loop if (i == numVertices - 1) { v1Normal.Normalize(); normal = v1Normal; } else if (i == 0) { v2Normal.Normalize(); normal = v2Normal; } else { v1Normal.Normalize(); v2Normal.Normalize(); if (MathF.Abs(v1Normal.X - v2Normal.X) < 0.0001 && MathF.Abs(v1Normal.Y - v2Normal.Y) < 0.0001) { // Two parallel segments normal = v1Normal; } else { // Find intersection of the two edges meeting this points, but // shift those lines outwards along their normals // First line var a = prev.Y - middle.Y; var b = middle.X - prev.X; // Shift line along the normal var c = -(a * (prev.X - v1Normal.X) + b * (prev.Y - v1Normal.Y)); // Second line var d = next.Y - middle.Y; var e = middle.X - next.X; // Shift line along the normal var f = -(d * (next.X - v2Normal.X) + e * (next.Y - v2Normal.Y)); var det = a * e - b * d; var invDet = det != 0.0 ? 1.0f / det : 0.0f; var intersectX = (b * f - e * c) * invDet; var intersectY = (c * d - a * f) * invDet; normal.X = middle.X - intersectX; normal.Y = middle.Y - intersectY; } } // Update current length along the ramp. var dx = prev.X - middle.X; var dy = prev.Y - middle.Y; var length = MathF.Sqrt(dx * dx + dy * dy); currentLength += length; var percentage = currentLength / totalLength; var currentWidth = percentage * (_data.WidthTop - _data.WidthBottom) + _data.WidthBottom; result.PointHeights[i] = middle.Z + percentage * (topHeight - bottomHeight) + bottomHeight; AssignHeightToControlPoint(vertex[i], middle.Z + percentage * (topHeight - bottomHeight) + bottomHeight); result.PointRatios[i] = 1.0f - percentage; // only change the width if we want to create vertices for rendering or for the editor // the collision engine uses flat type ramps if (IsHabitrail() && _data.RampType != RampType.RampType1Wire) { currentWidth = _data.WireDistanceX; if (incWidth) { currentWidth += 20.0f; } } else if (_data.RampType == RampType.RampType1Wire) { currentWidth = _data.WireDiameter; } result.MiddlePoints[i] = new Vertex2D(middle.X, middle.Y).Add(normal); result.RgvLocal[i] = new Vertex2D(middle.X, middle.Y).Add(normal.Clone().MultiplyScalar(currentWidth * 0.5f)); result.RgvLocal[numVertices * 2 - i - 1] = new Vertex2D(middle.X, middle.Y).Sub(normal.Clone().MultiplyScalar(currentWidth * 0.5f)); } return(result); }