/// <summary> /// Adjusts the current min, max accordingly in case /// the new point lies outside the current box. /// </summary> /// <param name="Point"></param> public void ExtendByPoint(V3 Point) { if (Point.X < Min.X) Min.X = Point.X; if (Point.X > Max.X) Max.X = Point.X; if (Point.Y < Min.Y) Min.Y = Point.Y; if (Point.Y > Max.Y) Max.Y = Point.Y; if (Point.Z < Min.Z) Min.Z = Point.Z; if (Point.Z > Max.Z) Max.Z = Point.Z; }
/// <summary> /// Constructor using two initial points. /// </summary> /// <param name="P1"></param> /// <param name="P2"></param> public BoundingBox3D(V3 P1, V3 P2) { // these initial values make sure // any first point will replace them this.Min.X = Real.MaxValue; this.Min.Y = Real.MaxValue; this.Min.Z = Real.MaxValue; this.Max.X = Real.MinValue; this.Max.Y = Real.MinValue; this.Max.Z = Real.MinValue; ExtendByPoint(P1); ExtendByPoint(P2); }
/// <summary> /// Checks for intersection of 3D line and 3D triangle. /// </summary> /// <remarks> /// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm /// </remarks> /// <param name="S">Start</param> /// <param name="E">End</param> /// <param name="P1">Triangle P1</param> /// <param name="P2">Triangle P2</param> /// <param name="P3">Triangle P3</param> /// <param name="I">Intersection Point</param> /// <returns>True or False</returns> public static bool IntersectLineTriangle(ref V3 S, ref V3 E, ref V3 P1, ref V3 P2, ref V3 P3, ref V3 I) { const float EPS = 0.001f; V3 e1, e2, p, q, t, d; Real u, v, f, inv_det, det; // vectors d = E - S; // line vector S->E e1 = P2 - P1; // triangle edge P1->P2 e2 = P3 - P1; // triangle edge P1->P3 p = d.CrossProduct(e2); // used to calculate determinant and u,v parms // if determinant is near zero, ray lies in plane of triangle det = e1 * p; if (det < EPS && det > -EPS) { return(false); } inv_det = 1.0f / det; // calculate distance from P1 to line start t = S - P1; // calculate u parameter and test bound u = (t * p) * inv_det; if ((u < 0.0f) || (u > 1.0f)) { return(false); } // prepare to test v parameter q = t.CrossProduct(e1); // calculate v parameter and test bound v = (d * q) * inv_det; if ((v < 0.0f) || (u + v > 1.0f)) { return(false); } // note: we additionally check for < 1.0f // = LINE intersection, not ray f = (e2 * q) * inv_det; if ((f >= 0.0f) && (f <= 1.0f)) { I = S + d * f; return(true); } return(false); }
/// <summary> /// Checks if this object is visible from a position /// in a room. /// </summary> /// <param name="Position"></param> /// <param name="Room"></param> /// <returns></returns> public bool IsVisibleFrom(V3 Position, RooFile Room) { if (Room != null) { // roo format variants V3 rooStart = Position.Clone(); V3 rooEnd = Position3D.Clone(); // add offset for playerheight to not use the groundheight rooStart.Y += GeometryConstants.PLAYERHEIGHT; rooEnd.Y += GeometryConstants.PLAYERHEIGHT; // convert to ROO format rooStart.ConvertToROO(); rooEnd.ConvertToROO(); // verify the object is visible return Room.VerifySight(rooStart, rooEnd); } else return false; }
/// <summary> /// Checks if this wall blocks a /// move between Start and End /// </summary> /// <param name="Start">A 3D location</param> /// <param name="End">A 2D location</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns></returns> public bool IsBlocking(V3 Start, V2 End, Real PlayerHeight) { V2 Start2D = new V2(Start.X, Start.Z); // calculate the sides of the points (returns -1, 0 or 1) int startside = Start2D.GetSide(P1, P2); int endside = End.GetSide(P1, P2); // if points are not on same side // the infinite lines cross if (startside != endside) { // verify also the finite line segments cross V2 intersect; LineLineIntersectionType intersecttype = MathUtil.IntersectLineLine(Start2D, End, P1, P2, out intersect); if (intersecttype == LineLineIntersectionType.OneIntersection || intersecttype == LineLineIntersectionType.OneBoundaryPoint || intersecttype == LineLineIntersectionType.FullyCoincide || intersecttype == LineLineIntersectionType.PartiallyCoincide) { // verify the side we've crossed is flaggged as "nonpassable" // if so, we actually have a collision if ((startside < 0 && LeftSide != null && !LeftSide.Flags.IsPassable) || (startside > 0 && RightSide != null && !RightSide.Flags.IsPassable)) { return true; } // still check the stepheight from oldheight to new floor if passable // for too high steps Real endheight = 0.0f; Real diff; if (endside <= 0 && LeftSector != null) endheight = LeftSector.CalculateFloorHeight((int)End.X, (int)End.Y, true); else if (endside > 0 && RightSector != null) endheight = RightSector.CalculateFloorHeight((int)End.X, (int)End.Y, true); diff = endheight - Start.Y; // diff is bigger than max. step height, we have a collision if (diff > GeometryConstants.MAXSTEPHEIGHT) return true; // check the ceiling heights if (endside <= 0 && LeftSector != null) endheight = LeftSector.CalculateCeilingHeight((int)End.X, (int)End.Y); else if (endside > 0 && RightSector != null) endheight = RightSector.CalculateCeilingHeight((int)End.X, (int)End.Y); // diff is bigger than max. step height, we have a collision if (endheight < Start.Y + PlayerHeight) return true; } } return false; }
/// <summary> /// Checks if this wall blocks a /// move between Start and End /// </summary> /// <param name="Start">A 3D location</param> /// <param name="End">A 2D location</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns></returns> public bool IsBlockingMove(V3 Start, V2 End, Real PlayerHeight) { // get distance of end to finite line segment Real distEnd = End.MinSquaredDistanceToLineSegment(P1, P2); // end is far enough away, no block if (distEnd >= GeometryConstants.WALLMINDISTANCE2) return false; /*************************************************************************/ // end is too 'too' close to wall V2 start2D = new V2(Start.X, Start.Z); int startside = start2D.GetSide(P1, P2); int endside = End.GetSide(P1, P2); Real endheight; ushort bgfbmp; /*************************************************************************/ // allow moving away from wall if (startside == endside) { Real distStart = start2D.MinSquaredDistanceToLineSegment(P1, P2); if (distEnd > distStart) return false; } /*************************************************************************/ // prevent moving through non-passable side if ((startside < 0 && LeftSide != null && !LeftSide.Flags.IsPassable) || (startside > 0 && RightSide != null && !RightSide.Flags.IsPassable)) return true; /*************************************************************************/ // check step-height endheight = 0.0f; if (startside >= 0) { endheight = (LeftSector != null) ? LeftSector.CalculateFloorHeight(End.X, End.Y, true) : 0.0f; bgfbmp = (RightSide != null) ? RightSide.LowerTexture : (ushort)0; } else { endheight = (RightSector != null) ? RightSector.CalculateFloorHeight(End.X, End.Y, true) : 0.0f; bgfbmp = (LeftSide != null) ? LeftSide.LowerTexture : (ushort)0; } if (bgfbmp > 0 && (endheight - Start.Y > GeometryConstants.MAXSTEPHEIGHT)) return true; /*************************************************************************/ // check head collision with ceiling endheight = 0.0f; if (startside >= 0) { endheight = (LeftSector != null) ? LeftSector.CalculateCeilingHeight(End.X, End.Y) : 0.0f; bgfbmp = (RightSide != null) ? RightSide.UpperTexture : (ushort)0; } else { endheight = (RightSector != null) ? RightSector.CalculateCeilingHeight(End.X, End.Y) : 0.0f; bgfbmp = (LeftSide != null) ? LeftSide.UpperTexture : (ushort)0; } if (bgfbmp > 0 && (endheight < Start.Y + PlayerHeight)) return true; /*************************************************************************/ return false; }
/// <summary> /// Collisions with wall segments for user movements using the BSP tree. /// Therefore with logarithmic rather than linear costs. /// </summary> /// <remarks> /// The algorithm goes like this (starts at root) /// (1) Get the sides of both endpoints (start, end) using the splitter (node) /// (2) If both endpoints are on the same side, there is no collision with any wall in the splitter /// and only one of the two subtrees must be visited then. /// (3) If there is an intersection with the infinite splitter, check the finite wall segments /// in the splitter for intersection. If none found, split up the vector (start, end) at the intersection /// and recusively start again for both subtrees using the just created two chunks of start vector. /// </remarks> /// <param name="Node"></param> /// <param name="Start"></param> /// <param name="End"></param> /// <param name="PlayerHeight"></param> /// <returns></returns> protected RooWall VerifyMoveTree(RooBSPItem Node, V3 Start, V2 End, Real PlayerHeight) { if (Node == null || Node.Type != RooBSPItem.NodeType.Node) return null; /*************************************************************/ RooPartitionLine line = (RooPartitionLine)Node; int side1 = Math.Sign(line.A * Start.X + line.B * Start.Z + line.C); int side2 = Math.Sign(line.A * End.X + line.B * End.Y + line.C); /*************************************************************/ // both endpoints on the same side of splitter -> no intersection // climb down only one of the two subtrees of the node if (side1 == side2 && side1 != 0) { // left if (side1 < 0) return VerifyMoveTree(line.LeftChild, Start, End, PlayerHeight); // right else return VerifyMoveTree(line.RightChild, Start, End, PlayerHeight); } /*************************************************************/ // endpoints are on different sides or both on infinite line else { RooWall wall = line.Wall; V2 intersect; V2 start2D = new V2(Start.X, Start.Z); if (wall == null) return null; /*************************************************************/ // intersect with infinite splitter LineInfiniteLineIntersectionType typ = MathUtil.IntersectLineInfiniteLine(start2D, End, wall.P1, wall.P2, out intersect); /*************************************************************/ // see if intersection with infinite splitter is on a wall segment in plane while (wall != null) { if (wall.IsBlocking(Start, End, PlayerHeight)) return wall; // loop over next wall in same plane wall = wall.NextWallInPlane; } /*************************************************************/ // no finite wallsegment in splitter plane intersects: // if vector is fully in splitter, we're done if (side1 == 0 && side2 == 0) return null; // otherwise split up the vector in left and right half // and possible check both else if (side1 < 0 && side2 > 0) { RooWall wl = VerifyMoveTree(line.LeftChild, Start, intersect, PlayerHeight); if (wl != null) return wl; else return VerifyMoveTree(line.RightChild, new V3(intersect.X, Start.Y, intersect.Y), End, PlayerHeight); } else { RooWall wl = VerifyMoveTree(line.RightChild, Start, intersect, PlayerHeight); if (wl != null) return wl; else return VerifyMoveTree(line.LeftChild, new V3(intersect.X, Start.Y, intersect.Y), End, PlayerHeight); } } }
/// <summary> /// Checks a movement vector (Start->End) for wall collisions and /// returns a possibly adjusted movement vector to use instead. /// This might be a slide along a wall or a zero-vector for a full block. /// </summary> /// <param name="Start">Startpoint of movement (in ROO coords)</param> /// <param name="End">Endpoint of movement (in ROO coords)</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns>Same or adjusted delta vector between Start and end (in ROO coords)</returns> public V2 VerifyMove(V3 Start, V2 End, Real PlayerHeight) { V2 Start2D = new V2(Start.X, Start.Z); // the move, if everything OK with this move // this is the untouched return V2 diff = End - Start2D; // an extension to the delta for min. wall distance V2 ext = diff.Clone(); ext.ScaleToLength(GeometryConstants.WALLMINDISTANCE); // this holds a possible collision wall RooWall intersectWall = VerifyMoveTree(BSPTree[0], Start, End + ext, PlayerHeight); // if there was an intersection, try to slide along wall if (intersectWall != null) { V2 newend = intersectWall.SlideAlong(Start2D, End); diff = newend - Start2D; // an extension to the delta for min. wall distance ext = diff.Clone(); ext.ScaleToLength(GeometryConstants.WALLMINDISTANCE); // recheck slide for intersect with other walls // this is necessary in case you get into a corner // to not slide through other wall there if (VerifyMoveTree(BSPTree[0], Start, newend + ext, PlayerHeight) != null) { diff.X = 0; diff.Y = 0; } } return diff; }
/// <summary> /// /// </summary> /// <param name="Start"></param> /// <param name="End"></param> /// <param name="PlayerHeight"></param> /// <returns></returns> protected RooWall VerifyMoveByList(V3 Start, V2 End, Real PlayerHeight) { foreach (RooWall wall in Walls) if (wall.IsBlockingMove(Start, End, PlayerHeight)) return wall; return null; }
/// <summary> /// Checks a movement vector (Start->End) for wall collisions and /// returns a possibly adjusted movement vector to use instead. /// This might be a slide along a wall or a zero-vector for a full block. /// </summary> /// <param name="Start">Startpoint of movement. In FINENESS units (1:1024), Y is height.</param> /// <param name="End">Endpoint of movement. In FINENESS units (1:1024).</param> /// <param name="PlayerHeight">Height of the player for ceiling collisions</param> /// <returns>Same or adjusted delta vector between Start and end (in ROO coords)</returns> public V2 VerifyMove(V3 Start, V2 End, Real PlayerHeight) { V2 Start2D = new V2(Start.X, Start.Z); V2 newend = End; V2 rot; RooWall wall; const int MAXATTEMPTS = 8; const Real ANGLESTEP = GeometryConstants.QUARTERPERIOD / 8; /**************************************************************/ // first collision check wall = VerifyMoveByTree(BSPTree[0], Start, End, PlayerHeight); // no collision with untouched move if (wall == null) return End - Start2D; /**************************************************************/ // try to slide along collision wall newend = wall.SlideAlong(Start2D, End); wall = VerifyMoveByTree(BSPTree[0], Start, newend, PlayerHeight); // no collision with 'slide along' move if (wall == null) return newend - Start2D; /**************************************************************/ // try find another collision wall wall = VerifyMoveByTree(BSPTree[0], Start, End, PlayerHeight, wall); if (wall != null) { // try to slide along other collision wall newend = wall.SlideAlong(Start2D, End); wall = VerifyMoveByTree(BSPTree[0], Start, newend, PlayerHeight); // slide along other collision wall was ok if (wall == null) return newend - Start2D; } /**************************************************************/ // sliding on collision walls does not work, try rotate a bit for(int i = 0; i < MAXATTEMPTS; i++) { rot = End - Start2D; rot.Rotate(-ANGLESTEP * (Real)i); newend = Start2D + rot; wall = VerifyMoveByTree(BSPTree[0], Start, newend, PlayerHeight); // no collision if (wall == null) return newend - Start2D; rot = End - Start2D; rot.Rotate(ANGLESTEP * (Real)i); newend = Start2D + rot; wall = VerifyMoveByTree(BSPTree[0], Start, newend, PlayerHeight); // no collision if (wall == null) return newend - Start2D; } return new V2(0.0f, 0.0f); }
public override int ReadFrom(byte[] Buffer, int StartIndex = 0) { int cursor = StartIndex; cursor += base.ReadFrom(Buffer, StartIndex); ushort coordinateY = BitConverter.ToUInt16(Buffer, cursor); cursor += TypeSizes.SHORT; ushort coordinateX = BitConverter.ToUInt16(Buffer, cursor); cursor += TypeSizes.SHORT; position3D = new V3(coordinateX, 0.0f, coordinateY); angle = MathUtil.BinaryAngleToRadian(BitConverter.ToUInt16(Buffer, cursor)); cursor += TypeSizes.SHORT; if ((AnimationType)Buffer[cursor] == AnimationType.TRANSLATION) // check if there is a colortranslation or effect as 1. anim { motionFirstAnimationType = (AnimationType)Buffer[cursor]; cursor++; motionColorTranslation = Buffer[cursor]; // RoomObjectColorTranslation (1 byte) cursor++; } else if (((AnimationType)Buffer[cursor] == AnimationType.EFFECT)) { motionFirstAnimationType = (AnimationType)Buffer[cursor]; cursor++; motionEffect = Buffer[cursor]; // RoomObjectEffect (1 byte) cursor++; } if (flags.Drawing == ObjectFlags.DrawingType.SecondTrans) motionColorTranslation = ColorTransformation.FILTERWHITE90; motionAnimation = Animation.ExtractAnimation(Buffer, cursor); // Animation (n bytes) cursor += motionAnimation.ByteLength; motionAnimation.PropertyChanged += OnMotionAnimationPropertyChanged; byte subOverlaysCount = Buffer[cursor]; // RoomObjectSubOverlaysCount (1 byte) cursor++; motionSubOverlays.Clear(); for (byte i = 0; i < subOverlaysCount; i++) { SubOverlay obj = new SubOverlay(Buffer, cursor); cursor += obj.ByteLength; if (flags.Drawing == ObjectFlags.DrawingType.SecondTrans) obj.ColorTranslation = ColorTransformation.FILTERWHITE90; obj.PropertyChanged += OnMotionSubOverlayPropertyChanged; motionSubOverlays.Add(obj); } return cursor - StartIndex; }
/// <summary> /// Resets the object to default state/values /// </summary> /// <param name="RaiseChangedEvent"></param> public override void Clear(bool RaiseChangedEvent) { base.Clear(RaiseChangedEvent); if (RaiseChangedEvent) { Position3D = new V3(0, 0, 0); Angle = 0.0f; MotionFirstAnimationType = 0; MotionColorTranslation = 0; MotionEffect = 0; if (motionAnimation != null) motionAnimation.PropertyChanged -= OnMotionAnimationPropertyChanged; MotionAnimation = new AnimationNone(); MotionAnimation.PropertyChanged += OnMotionAnimationPropertyChanged; motionSubOverlays.Clear(); IsMoving = false; IsTarget = false; IsHighlighted = false; VerticalSpeed = 0.0f; } else { position3D = new V3(0, 0, 0); angle = 0.0f; motionFirstAnimationType = 0; motionColorTranslation = 0; motionEffect = 0; if (motionAnimation != null) motionAnimation.PropertyChanged -= OnMotionAnimationPropertyChanged; motionAnimation = new AnimationNone(); motionAnimation.PropertyChanged += OnMotionAnimationPropertyChanged; motionSubOverlays.Clear(); isMoving = false; isTarget = false; isHighlighted = false; verticalSpeed = 0.0f; } }
public RoomObject( uint ID, uint Count, uint OverlayFileRID, uint NameRID, uint Flags, ushort LightFlags, byte LightIntensity, ushort LightColor, AnimationType FirstAnimationType, byte ColorTranslation, byte Effect, Animation Animation, IEnumerable<SubOverlay> SubOverlays, V3 Position3D, ushort Angle, AnimationType MotionFirstAnimationType, byte MotionColorTranslation, byte MotionEffect, Animation MotionAnimation, IEnumerable<SubOverlay> MotionSubOverlays) : base( ID, Count, OverlayFileRID, NameRID, Flags, LightFlags, LightIntensity, LightColor, FirstAnimationType, ColorTranslation, Effect, Animation, SubOverlays) { // attach motionsuboverlays listener motionSubOverlays.ListChanged += OnMotionSubOverlaysListChanged; // coordinates & angle position3D = Position3D; angle = Angle; // roomobject stuff (like object) motionFirstAnimationType = MotionFirstAnimationType; motionColorTranslation = MotionColorTranslation; motionEffect = MotionEffect; motionAnimation = MotionAnimation; motionAnimation.PropertyChanged += OnMotionAnimationPropertyChanged; // special handling for secondtrans if (flags.Drawing == ObjectFlags.DrawingType.SecondTrans) { motionColorTranslation = ColorTransformation.FILTERWHITE90; foreach (SubOverlay subOv in MotionSubOverlays) subOv.ColorTranslation = ColorTransformation.FILTERWHITE90; } // motionsuboverlays motionSubOverlays.AddRange(MotionSubOverlays); }
public override unsafe void ReadFrom(ref byte* Buffer) { base.ReadFrom(ref Buffer); ushort coordinateY = *((ushort*)Buffer); Buffer += TypeSizes.SHORT; ushort coordinateX = *((ushort*)Buffer); Buffer += TypeSizes.SHORT; position3D = new V3(coordinateX, 0.0f, coordinateY); angle = MathUtil.BinaryAngleToRadian(*((ushort*)Buffer)); Buffer += TypeSizes.SHORT; if ((AnimationType)Buffer[0] == AnimationType.TRANSLATION) { motionFirstAnimationType = (AnimationType)Buffer[0]; Buffer++; motionColorTranslation = Buffer[0]; Buffer++; } else if (((AnimationType)Buffer[0] == AnimationType.EFFECT)) { motionFirstAnimationType = (AnimationType)Buffer[0]; Buffer++; motionEffect = Buffer[0]; Buffer++; } if (flags.Drawing == ObjectFlags.DrawingType.SecondTrans) motionColorTranslation = ColorTransformation.FILTERWHITE90; motionAnimation = Animation.ExtractAnimation(ref Buffer); motionAnimation.PropertyChanged += OnMotionAnimationPropertyChanged; byte subOverlaysCount = Buffer[0]; Buffer++; motionSubOverlays.Clear(); for (byte i = 0; i < subOverlaysCount; i++) { SubOverlay subov = new SubOverlay(ref Buffer); if (flags.Drawing == ObjectFlags.DrawingType.SecondTrans) subov.ColorTranslation = ColorTransformation.FILTERWHITE90; subov.PropertyChanged += OnMotionSubOverlayPropertyChanged; motionSubOverlays.Add(subov); } }
/// <summary> /// Typed equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public bool Equals(V3 obj) { return (obj.X == X && obj.Y == Y && obj.Z == Z); }
/// <summary> /// Returns the crossproduct of this /// and another V3 vector. /// </summary> /// <param name="v"></param> /// <returns></returns> public V3 CrossProduct(V3 v) { return new V3( Y * v.Z - Z * v.Y, Z * v.X - X * v.Z, X * v.Y - Y * v.X); }
/// <summary> /// Collisions with wall segments for user movements using the BSP tree. /// Therefore with logarithmic rather than linear costs. /// </summary> /// <param name="Node"></param> /// <param name="Start"></param> /// <param name="End"></param> /// <param name="PlayerHeight"></param> /// <param name="IgnoreWall"></param> /// <returns></returns> protected RooWall VerifyMoveByTree(RooBSPItem Node, V3 Start, V2 End, Real PlayerHeight, RooWall IgnoreWall = null) { if (Node == null || Node.Type != RooBSPItem.NodeType.Node) return null; /*************************************************************/ RooPartitionLine line = (RooPartitionLine)Node; RooWall wall = line.Wall; V2 start2D = new V2(Start.X, Start.Z); /*************************************************************/ // check node boundingbox if (!line.BoundingBox.IsInside(End, GeometryConstants.WALLMINDISTANCE) && !line.BoundingBox.IsInside(start2D, GeometryConstants.WALLMINDISTANCE)) return null; /*************************************************************/ // test walls of splitter while (wall != null) { if (wall != IgnoreWall && wall.IsBlockingMove(Start, End, PlayerHeight)) return wall; // loop over next wall in same plane wall = wall.NextWallInPlane; } /*************************************************************/ RooWall wl = VerifyMoveByTree(line.LeftChild, Start, End, PlayerHeight, IgnoreWall); if (wl != null) return wl; else return VerifyMoveByTree(line.RightChild, Start, End, PlayerHeight, IgnoreWall); }
/// <summary> /// Verifies if any wall blocks a ray from Start to End /// </summary> /// <param name="Start"></param> /// <param name="End"></param> /// <returns>True if OK, false if collision.</returns> public bool VerifySight(V3 Start, V3 End) { // look for blocking wall foreach (RooWall wall in Walls) if (wall.IsBlockingSight(Start, End)) return false; return true; }
/// <summary> /// Verifies Line of Sight from Start to End. /// Checking against Walls only (no sectors/ceilings). /// </summary> /// <param name="Start"></param> /// <param name="End"></param> /// <returns>True if OK, false if collision.</returns> public bool VerifySight(V3 Start, V3 End) { return VerifySightByTree(BSPTree[0], Start, End); //return VerifySightByList(Start, End); }
public void Calculate() { // texture orientation Real radangle = MathUtil.BinaryAngleToRadian((ushort)TextureAngle); Real texorientx = (Real)System.Math.Cos(radangle); Real texorienty = (Real)System.Math.Sin(radangle); Real texorientz = 0; TextureOrientation = new V3(texorientx, texorienty, texorientz); // generate other endpoints from plane normal, texture origin, and texture // orientation which determine the orientation of the texture's u v space // in the 3d world's x, y, z space // plane normal V3 planeNormal = new V3(A, B, C); // first point // calculate z of texture origin from x, y, and plane equation Real z = (-A * X0 - B * Y0 - D) / C; P0 = new V3(X0, Y0, z); // cross normal with texture orientation to get vector perpendicular to texture // orientation and normal = v axis direction V3 v2 = planeNormal.CrossProduct(TextureOrientation); v2.ScaleToLength(GeometryConstants.FINENESS); // cross normal with v axis direction vector to get vector perpendicular to v axis // and normal = u axis direction vector V3 v1 = v2.CrossProduct(planeNormal); v1.ScaleToLength(GeometryConstants.FINENESS); // add vectors to origin to get endpoints P1 = P0 + v1; P2 = P0 + v2; }
/// <summary> /// /// </summary> /// <param name="Node"></param> /// <param name="Start"></param> /// <param name="End"></param> /// <returns>True if OK, false if collision.</returns> protected bool VerifySightByTree(RooBSPItem Node, V3 Start, V3 End) { if (Node == null || Node.Type != RooBSPItem.NodeType.Node) return true; /*************************************************************/ RooPartitionLine line = (RooPartitionLine)Node; RooWall wall = line.Wall; V2 start2D = new V2(Start.X, Start.Z); V2 end2D = new V2(End.X, End.Z); /*************************************************************/ Real startDist = line.GetDistance(start2D); Real endDist = line.GetDistance(end2D); /*************************************************************/ // both endpoints on negative side if (startDist < 0.0f && endDist < 0.0f) return VerifySightByTree(line.LeftChild, Start, End); // both endpoints on positive side else if (startDist > 0.0f && endDist > 0.0f) return VerifySightByTree(line.RightChild, Start, End); // crosses infinite splitter or one or both points on splitter else { // test walls of splitter while (wall != null) { if (wall.IsBlockingSight(Start, End)) return false; // loop over next wall in same plane wall = wall.NextWallInPlane; } // must climb down both subtrees, go left first bool wl = VerifySightByTree(line.LeftChild, Start, End); // return collision if already found if (wl == false) return wl; // try other subtree otherwise else return VerifySightByTree(line.RightChild, Start, End); } }
/// <summary> /// Tries to resolve the source and target RoomObject /// references from RoomObjects parameter, also sets the start position. /// </summary> /// <param name="RoomObjects"></param> /// <param name="RaiseChangedEvent"></param> public void ResolveSourceTarget(IList<RoomObject> RoomObjects, bool RaiseChangedEvent) { if (RaiseChangedEvent) { foreach (RoomObject roomObject in RoomObjects) { if (roomObject.ID == source.ID) { SourceObject = roomObject; Position3D = SourceObject.Position3D.Clone(); } else if (roomObject.ID == target.ID) TargetObject = roomObject; } } else { foreach (RoomObject roomObject in RoomObjects) { if (roomObject.ID == source.ID) { sourceObject = roomObject; position3D = sourceObject.Position3D.Clone(); } else if (roomObject.ID == target.ID) targetObject = roomObject; } } }
/// <summary> /// Typed equals /// </summary> /// <param name="obj"></param> /// <returns></returns> public bool Equals(V3 obj) { return(obj.X == X && obj.Y == Y && obj.Z == Z); }
/// <summary> /// Checks if this wall blocks /// a view ray from Start to End /// </summary> /// <param name="Start"></param> /// <param name="End"></param> /// <returns>True if blocked, false if OK</returns> public bool IsBlockingSight(V3 Start, V3 End) { // 2D V2 Start2D = new V2(Start.X, Start.Z); V2 End2D = new V2(End.X, End.Z); // calculate the sides of the points (returns -1, 0 or 1) int startside = Start2D.GetSide(P1, P2); int endside = End2D.GetSide(P1, P2); // if points are not on same side // the infinite lines cross if (startside != endside) { // verify also the finite line segments cross V2 intersect; LineLineIntersectionType intersecttype = MathUtil.IntersectLineLine(Start2D, End2D, P1, P2, out intersect); if (intersecttype == LineLineIntersectionType.OneIntersection || intersecttype == LineLineIntersectionType.OneBoundaryPoint || intersecttype == LineLineIntersectionType.FullyCoincide || intersecttype == LineLineIntersectionType.PartiallyCoincide) { // the vector/ray between start and end V3 diff = End - Start; // solve 3d linear equation to get rayheight R2 // (height of possible intersection) // // ( R1 ) (P1) (Q1) // ( R2 ) = (P2) + lambda * (Q2) // ( R3 ) (P3) (Q3) // // using: // R = intersect // P = Start // Q = diff (Direction) // get lambda scale Real lambda = 1.0f; if (diff.X != 0) lambda = (intersect.X - Start.X) / diff.X; else if (diff.Z != 0) lambda = (intersect.Y - Start.Z) / diff.Z; // calculate the rayheight based on linear 3d equation Real rayheight = Start.Y + lambda * diff.Y; // compare height with wallheights // use average of both endpoints (in case its sloped) // do not care about the sides Real h3 = (z3 + zz3) / 2.0f; Real h2 = (z2 + zz2) / 2.0f; Real h1 = (z1 + zz1) / 2.0f; Real h0 = (z0 + zz0) / 2.0f; bool a, b; // test upper part a = (startside <= 0 && LeftSide != null && LeftSide.ResourceUpper != null && (LeftSide.Flags.IsNoLookThrough || !LeftSide.Flags.IsTransparent)); b = (startside >= 0 && RightSide != null && RightSide.ResourceUpper != null && (RightSide.Flags.IsNoLookThrough || !RightSide.Flags.IsTransparent)); if ((a || b) && rayheight < h3 && rayheight > h2) return true; // test middle part a = (startside <= 0 && LeftSide != null && LeftSide.ResourceMiddle != null && (LeftSide.Flags.IsNoLookThrough || !LeftSide.Flags.IsTransparent)); b = (startside >= 0 && RightSide != null && RightSide.ResourceMiddle != null && (RightSide.Flags.IsNoLookThrough || !RightSide.Flags.IsTransparent)); if ((a || b) && rayheight < h2 && rayheight > h1) return true; // test lower part (nolookthrough) a = (startside <= 0 && LeftSide != null && LeftSide.ResourceLower != null); b = (startside >= 0 && RightSide != null && RightSide.ResourceLower != null); if ((a || b) && rayheight < h1 && rayheight > h0) return true; } } return false; }
/// <summary> /// Updates the P, UV and Normal properties for either floor or ceiling. /// Fills in current 3D data for the subsector vertices. /// Note: Z component is the height here. /// </summary> /// <param name="IsFloor">Whether to create for floor or ceiling</param> /// <param name="Scale">Optional additional scale to apply.</param> public void UpdateVertexData(bool IsFloor, Real Scale = 1.0f) { const Real INV64 = 1.0f / (Real)(64 << 4); // from old code.. V3 normal; V3[] p = new V3[Vertices.Count]; V2[] uv = new V2[Vertices.Count]; Real left = 0; Real top = 0; Real oneOverC = 0.0f; RooSectorSlopeInfo slopeInfo = (IsFloor) ? Sector.SlopeInfoFloor : Sector.SlopeInfoCeiling; /*****************************************************************/ // find most top left vertex and get its coordinates if (slopeInfo != null) { left = (int)slopeInfo.X0; top = (int)slopeInfo.Y0; oneOverC = 1.0f / slopeInfo.C; } else { for (int i = 0; i < Vertices.Count; i++) { if (Vertices[i].X < left) left = Vertices[i].X; if (Vertices[i].Y < top) top = Vertices[i].Y; } } /*****************************************************************/ for (int count = 0; count < Vertices.Count; count++) { // 1: Fill in vertex coordinates if (slopeInfo != null) { p[count].X = Vertices[count].X; p[count].Y = Vertices[count].Y; p[count].Z = (-slopeInfo.A * p[count].X - slopeInfo.B * p[count].Y - slopeInfo.D) *oneOverC; } else { p[count].X = Vertices[count].X; p[count].Y = Vertices[count].Y; p[count].Z = (IsFloor) ? (Sector.FloorHeight * 16.0f) : (Sector.CeilingHeight * 16.0f); } // 2.1: UV with slope if (slopeInfo != null) { V3 intersectTop; V3 intersectLeft; V3 vectorU; V3 vectorV; V3 vector; Real distance; Real U, temp; // calc distance from top line (vector u) U = ((p[count].X - slopeInfo.P0.X) * (slopeInfo.P1.X - slopeInfo.P0.X)) + ((p[count].Z - slopeInfo.P0.Z) * (slopeInfo.P1.Z - slopeInfo.P0.Z)) + ((p[count].Y - slopeInfo.P0.Y) * (slopeInfo.P1.Y - slopeInfo.P0.Y)); temp = ((slopeInfo.P1.X - slopeInfo.P0.X) * (slopeInfo.P1.X - slopeInfo.P0.X)) + ((slopeInfo.P1.Z - slopeInfo.P0.Z) * (slopeInfo.P1.Z - slopeInfo.P0.Z)) + ((slopeInfo.P1.Y - slopeInfo.P0.Y) * (slopeInfo.P1.Y - slopeInfo.P0.Y)); if (temp == 0.0f) temp = 1.0f; U /= temp; intersectTop.X = slopeInfo.P0.X + U * (slopeInfo.P1.X - slopeInfo.P0.X); intersectTop.Z = slopeInfo.P0.Z + U * (slopeInfo.P1.Z - slopeInfo.P0.Z); intersectTop.Y = slopeInfo.P0.Y + U * (slopeInfo.P1.Y - slopeInfo.P0.Y); uv[count].X = (Real)System.Math.Sqrt( (p[count].X - intersectTop.X) * (p[count].X - intersectTop.X) + (p[count].Z - intersectTop.Z) * (p[count].Z - intersectTop.Z) + (p[count].Y - intersectTop.Y) * (p[count].Y - intersectTop.Y)); // calc distance from left line (vector v) U = ((p[count].X - slopeInfo.P0.X) * (slopeInfo.P2.X - slopeInfo.P0.X)) + ((p[count].Z - slopeInfo.P0.Z) * (slopeInfo.P2.Z - slopeInfo.P0.Z)) + ((p[count].Y - slopeInfo.P0.Y) * (slopeInfo.P2.Y - slopeInfo.P0.Y)); temp = ((slopeInfo.P2.X - slopeInfo.P0.X) * (slopeInfo.P2.X - slopeInfo.P0.X)) + ((slopeInfo.P2.Z - slopeInfo.P0.Z) * (slopeInfo.P2.Z - slopeInfo.P0.Z)) + ((slopeInfo.P2.Y - slopeInfo.P0.Y) * (slopeInfo.P2.Y - slopeInfo.P0.Y)); if (temp == 0.0f) temp = 1.0f; U /= temp; intersectLeft.X = slopeInfo.P0.X + U * (slopeInfo.P2.X - slopeInfo.P0.X); intersectLeft.Z = slopeInfo.P0.Z + U * (slopeInfo.P2.Z - slopeInfo.P0.Z); intersectLeft.Y = slopeInfo.P0.Y + U * (slopeInfo.P2.Y - slopeInfo.P0.Y); uv[count].Y = (Real)System.Math.Sqrt( (p[count].X - intersectLeft.X) * (p[count].X - intersectLeft.X) + (p[count].Z - intersectLeft.Z) * (p[count].Z - intersectLeft.Z) + (p[count].Y - intersectLeft.Y) * (p[count].Y - intersectLeft.Y)); uv[count].X += (Sector.TextureY << 4) / 2.0f; uv[count].Y += (Sector.TextureX << 4) / 2.0f; vectorU.X = slopeInfo.P1.X - slopeInfo.P0.X; vectorU.Z = slopeInfo.P1.Z - slopeInfo.P0.Z; vectorU.Y = slopeInfo.P1.Y - slopeInfo.P0.Y; distance = (Real)System.Math.Sqrt((vectorU.X * vectorU.X) + (vectorU.Y * vectorU.Y)); if (distance == 0.0f) distance = 1.0f; vectorU.X /= distance; vectorU.Z /= distance; vectorU.Y /= distance; vectorV.X = slopeInfo.P2.X - slopeInfo.P0.X; vectorV.Z = slopeInfo.P2.Z - slopeInfo.P0.Z; vectorV.Y = slopeInfo.P2.Y - slopeInfo.P0.Y; distance = (Real)System.Math.Sqrt((vectorV.X * vectorV.X) + (vectorV.Y * vectorV.Y)); if (distance == 0.0f) distance = 1.0f; vectorV.X /= distance; vectorV.Z /= distance; vectorV.Y /= distance; vector.X = p[count].X - slopeInfo.P0.X; vector.Y = p[count].Y - slopeInfo.P0.Y; distance = (Real)System.Math.Sqrt((vector.X * vector.X) + (vector.Y * vector.Y)); if (distance == 0) distance = 1.0f; vector.X /= distance; vector.Y /= distance; if (((vector.X * vectorU.X) + (vector.Y * vectorU.Y)) <= 0) uv[count].Y = -uv[count].Y; if (((vector.X * vectorV.X) + (vector.Y * vectorV.Y)) > 0) uv[count].X = -uv[count].X; } // 2.2: UV without slope else { uv[count].X = System.Math.Abs(Vertices[count].Y - top) - (Sector.TextureY << 4); uv[count].Y = System.Math.Abs(Vertices[count].X - left) - (Sector.TextureX << 4); } uv[count].X *= INV64; uv[count].Y *= INV64; // apply additional userscale p[count] *= Scale; } /*****************************************************************/ // Calculate the normal if (slopeInfo != null) { // if the sector is sloped, we get the normal from slopeinfo normal.X = slopeInfo.A; normal.Y = slopeInfo.B; normal.Z = slopeInfo.C; normal.Normalize(); } else if (IsFloor) { // default normal for non sloped floor normal.X = 0.0f; normal.Y = 0.0f; normal.Z = 1.0f; } else { // default normal for non sloped ceilings normal.X = 0.0f; normal.Y = 0.0f; normal.Z = -1.0f; } /*****************************************************************/ // save in class properties depending on floor/ceiling if (IsFloor) { FloorP = p; FloorUV = uv; FloorNormal = normal; } else { CeilingP = p; CeilingUV = uv; CeilingNormal = normal; } }