Page 1 of 1

RayCast does not work if entity is inside another one?

Posted: Thu Jan 16, 2014 12:22 am
by MightyMAC
Hi guys,

is it an expected behaviour the RayCast recognises an entity that is inside another entity? For example if you have a box with a smaller sphere in it and you make a RayCast from the outside of the box in direction of the sphere, the box is not recognised by RayCast. If you put the box between the RayCast origin and the sphere, the box is picked. It's like RayCast is working with the backside of entities. Should it work this way or is that a bug?

Regards
MAC

Re: RayCast does not work if entity is inside another one?

Posted: Thu Jan 16, 2014 3:54 pm
by MightyMAC
I thought I described quite well what the problem is, but I have some code to show the problem in practice.

I modified my dijkstra code example a bit and made one of the walls bigger. Run the code and move the vertical wall left and right by using the cursor keys left/right. As you can see the waypoints that are inside the wall are picked thought the RayCast should pick the wall instead until you move the wall so that the waypoints are completely behind it, only then the wall is picked and not the waypoints on the left. Picked objects are visualized by a Line3D. I know the code is a bit overloaded for an example but it shows the problem.

Code: Select all

InitEngine3D()
InitSprite()
InitKeyboard()

Structure Str_PathConnection
  *Waypoint
  Weight.d
EndStructure

Structure Str_PathWaypoint
  Entity.l
EndStructure

Structure Str_Path
  *Waypoint
  *Predecessor
  Distance.d
  Visited.c
  List Connection.Str_PathConnection()
  Nr.l
EndStructure

Global NewList Waypoint.Str_PathWaypoint()
Global NewList Path.Str_Path()

#WallPick = 1 << 1
#WaypointPick = 1 << 2

OpenWindow(0,100,100,800,600,"Test")
OpenWindowedScreen(WindowID(0),0,0,800,600)

; Camera
CreateCamera(1,0,0,100,100)
MoveCamera(1,0,100,100,#PB_Relative)
RotateCamera(1,-50,0,0)

; Textures
CreateTexture(0,256,256)
StartDrawing(TextureOutput(0))
  Box(0,0,255,255,RGB(200,0,0))
StopDrawing()

CreateTexture(1,256,256)
StartDrawing(TextureOutput(1))
  Box(0,0,255,255,RGB(0,0,200))
StopDrawing()

CreateTexture(2,256,256)
StartDrawing(TextureOutput(2))
  Box(0,0,255,255,RGB(0,200,200))
StopDrawing()

CreateTexture(3,256,256)
StartDrawing(TextureOutput(3))
  Box(0,0,255,255,RGB(0,200,0))
StopDrawing()

; Materials
CreateMaterial(0,TextureID(0))
CreateMaterial(1,TextureID(1))
CreateMaterial(2,TextureID(2))
CreateMaterial(3,TextureID(3))

; Floor
CreatePlane(2,100,100,10,10,1,1)
CreateEntity(3,MeshID(2),MaterialID(0))

; Walls
CreateCube(4,10)
CreateEntity(5,MeshID(4),MaterialID(1),0,0,0,#WallPick)
ScaleEntity(5,5,2,0.3)
CreateEntity(8,MeshID(4),MaterialID(1),-10,0,0,#WallPick)
ScaleEntity(8,3,2,8);ScaleEntity(8,0.3,2,8)

; Light
CreateLight(6,RGB(255,255,255),0,100,0)
AmbientColor(RGB(25, 25, 25))

; Waypoint, start and aim mesh
Global WaypointMesh=CreateCylinder(#PB_Any,0.2,15)

Hero=CreateEntity(#PB_Any,MeshID(WaypointMesh),MaterialID(2),10,0,10,#WaypointPick)
ScaleEntity(Hero,3,1,3)

Aim=CreateEntity(#PB_Any,MeshID(WaypointMesh),MaterialID(3),0,0,-20,#WaypointPick)
ScaleEntity(Aim,3,1,3)

; This procedure creates a waypoint graph (Path()) with all predifined waypoints in it and the start and aim mesh
; and inits the dijkstra algorithm
; StartMesh = The mesh to walk the path
; AimMesh = A mesh that represents the aim position
; YOffset = An optional offset on the y-axis (use this to make sure the line of sight does not interfere with the ground)
Procedure Pathfinding_Init(StartMesh,AimMesh,YOffset.d=0)
  Protected *TempWaypoint.Str_Path
  Protected LineCount=0
  ClearList(Path())
  
  ; Destroy all former 3d lines
  For t=100 To 150
    If IsMesh(t)
      FreeMesh(t)
    EndIf
  Next
  
  ; Add Start and Aim to the waypoint graph
  AddElement(Path())
  Path()\Waypoint=StartMesh
  Path()\Predecessor=-1
  Path()\Distance=0
  Path()\Nr=0

  AddElement(Path())
  Path()\Waypoint=AimMesh
  Path()\Predecessor=-1
  Path()\Distance=Infinity()
  Path()\Nr=1
  
  ; Add all predifined waypoints to the graph
  Nr=2
  ForEach Waypoint()
    AddElement(Path())
    Path()\Waypoint=Waypoint()\Entity
    Path()\Predecessor=-1
    Path()\Distance=Infinity()
    Path()\Nr=Nr
    Nr+1
  Next

  ; Render the world to have an up-to-date world state for using RayCast()
  RenderWorld()

  ; Check all connections between waypoints
  ForEach Path()
;    ClearList(Path()\Connection())
    *TempWaypoint=@Path()
    PushListPosition(Path())
    ForEach Path()
      If @Path()<>*TempWaypoint
        ; Check line of sight with a ray cast
        Res=RayCast(EntityX(*TempWaypoint\Waypoint),EntityY(*TempWaypoint\Waypoint)+YOffset,EntityZ(*TempWaypoint\Waypoint),EntityX(Path()\Waypoint)-EntityX(*TempWaypoint\Waypoint),0,EntityZ(Path()\Waypoint)-EntityZ(*TempWaypoint\Waypoint),#WallPick|#WaypointPick)
        ; Claculate the distance between the waypoint to have a weighted connection
        DistWaypoint.d=Sqr(Pow(EntityX(*TempWaypoint\Waypoint)-EntityX(Path()\Waypoint),2.0) + Pow(EntityY(*TempWaypoint\Waypoint)-EntityY(Path()\Waypoint),2.0) + Pow(EntityZ(*TempWaypoint\Waypoint)-EntityZ(Path()\Waypoint),2.0))
;          Debug "Cast ("+StrD(EntityX(*TempWaypoint\Waypoint))+", "+StrD(EntityY(*TempWaypoint\Waypoint))+", "+StrD(EntityZ(*TempWaypoint\Waypoint))+") - ("+StrD(EntityX(Path()\Waypoint))+", "+StrD(EntityY(Path()\Waypoint))+", "+StrD(EntityZ(Path()\Waypoint))+")"
        If Res=Path()\Waypoint

          ; Add the found connection to the connection list of the currently tested waypoint
          AddElement(*TempWaypoint\Connection())
          *TempWaypoint\Connection()\Waypoint=@Path()\Waypoint
          *TempWaypoint\Connection()\Weight=DistWaypoint

          ; Also add the connection to the list of connections of the waypoint that was found if it is not already in the list
          Found=0
          ForEach Path()\Connection()
            If  Path()\Connection()\Waypoint=@*TempWaypoint\Waypoint
              Found=1
              Break
            EndIf
          Next
          If Found=0
            AddElement(Path()\Connection())
            Path()\Connection()\Waypoint=@*TempWaypoint\Waypoint
            Path()\Connection()\Weight=DistWaypoint
          EndIf
          
          ; Show the connection as a 3d line
          CreateLine3D(100+LineCount,EntityX(*TempWaypoint\Waypoint),EntityY(*TempWaypoint\Waypoint)+YOffset,EntityZ(*TempWaypoint\Waypoint),RGB(255,255,255),EntityX(Path()\Waypoint),EntityY(Path()\Waypoint)+YOffset,EntityZ(Path()\Waypoint),RGB(255,255,255))
          LineCount+1
        EndIf
      EndIf
    Next
    PopListPosition(Path())
  Next
EndProcedure

; This is the dijkstra algorithm
; The shortest path can be found after using this procedure by starting with the aim mesh in the Path() list and check all predecessors until the start mesh is found
; StartMesh = The mesh to walk the path
; AimMesh = A mesh that represents the aim position
Procedure Pathfinding_FindPath(StartMesh,AimMesh)
  ; Repeat until all graph entries are visited
  Repeat
    Searching=0
    Dist.d=Infinity()
    ; Find the waypoint with the lowest distance
    ForEach Path()
      If Path()\Visited=0 
        If Path()\Distance<Dist
          Searching=1
          *CurrentWaypoint.Str_Path=@Path()
          Dist=Path()\Distance
        EndIf
      EndIf
    Next
    If Searching=1
      *CurrentWaypoint\Visited=1
      ForEach *CurrentWaypoint\Connection()
        *NextWaypoint.Str_Path=*CurrentWaypoint\Connection()\Waypoint
        If *NextWaypoint\Visited=0
          If *CurrentWaypoint\Distance+*CurrentWaypoint\Connection()\Weight<*NextWaypoint\Distance
            *NextWaypoint\Distance=*CurrentWaypoint\Distance+*CurrentWaypoint\Connection()\Weight
            *NextWaypoint\Predecessor=*CurrentWaypoint\Waypoint
          EndIf
        EndIf
      Next
    EndIf
  Until Searching=0
EndProcedure

; Parses the Path() list from end to start and prints the steps in the debug window
Procedure Pathfinding_ShowPath(StartMesh,AimMesh)
  ForEach Path()
    If Path()\Waypoint=AimMesh
      *Temp.Str_Path=@Path()
      ; Check if path is found or not
      If Path()\Predecessor=-1
        Debug "There is no path!"
      Else
        ; Show path from aim to start (found by the predecessors)
        Repeat
          Debug "No: "+Str(*temp\Nr)+" Pos: "+StrD(EntityX(*Temp\Waypoint))+", "+StrD(EntityY(*Temp\Waypoint))+", "+StrD(EntityZ(*Temp\Waypoint))+" - Predecessor: "+Str(*Temp\Predecessor)
          If *Temp\Predecessor=-1
            ; The start is reached -> break loop
            Break
          EndIf
          ForEach Path()
            If Path()\Waypoint=*Temp\Predecessor
              *Temp=@Path()
              Break
            EndIf
          Next
        ForEver
        Break
      EndIf
    EndIf
  Next
  Debug "-----------------------------------"
EndProcedure

; Add a waypoint
Procedure Pathfinding_AddWaypoint(x.d,y.d,z.d)
  AddElement(Waypoint())
  Waypoint()\Entity=CreateEntity(#PB_Any,MeshID(WaypointMesh),MaterialID(1),x,y,z,#WaypointPick)
  ScaleEntity(Waypoint()\Entity,3,1,3)
EndProcedure

; Add some waypoints
Pathfinding_AddWaypoint(30,0,0)
Pathfinding_AddWaypoint(-40,0,5)
Pathfinding_AddWaypoint(-30,0,-30)

; Render world has to be called once, otherwise RayCast does not work
RenderWorld()

; Make a first pathfinding
Pathfinding_Init(Hero,Aim,4)
Pathfinding_FindPath(Hero,Aim)

; Create overlay sprite for text output
Overlay=CreateSprite(#PB_Any,800,600)
StartDrawing(SpriteOutput(Overlay))
  DrawingMode(#PB_2DDrawing_Transparent)
  DrawText(0,0,"Use cursor keys to move the walls.")
  DrawText(0,15,"Use space key to calculate shortest path.")
  DrawText(0,30,"Use D key to show distance table.")
  Txt.s="Light blue is the start mesh (No. 0)"
  DrawText(ScreenWidth()-TextWidth(Txt),0,Txt)
  Txt.s="Green is the aim mesh (No. 1)"
  DrawText(ScreenWidth()-TextWidth(Txt),15,Txt)
  ForEach Path()
    DrawText(CameraProjectionX(1,EntityX(Path()\Waypoint),EntityY(Path()\Waypoint),EntityZ(Path()\Waypoint)),CameraProjectionY(1,EntityX(Path()\Waypoint),EntityY(Path()\Waypoint),EntityZ(Path()\Waypoint)),"("+StrD(EntityX(Path()\Waypoint))+", "+StrD(EntityY(Path()\Waypoint))+", "+StrD(EntityZ(Path()\Waypoint))+") - No: "+Str(Path()\Nr))
  Next
StopDrawing()

; Main loop
Repeat
  ExamineKeyboard()

  If KeyboardPushed(#PB_Key_Left)
    MoveEntity(8,-1,0,0,#PB_Relative)
    Pathfinding_Init(Hero,Aim,4)
    Pathfinding_FindPath(Hero,Aim)
  EndIf
  If KeyboardPushed(#PB_Key_Right)
    MoveEntity(8,1,0,0,#PB_Relative)
    Pathfinding_Init(Hero,Aim,4)
    Pathfinding_FindPath(Hero,Aim)
  EndIf
  If KeyboardPushed(#PB_Key_Down)
    MoveEntity(5,0,0,1,#PB_Relative)
    Pathfinding_Init(Hero,Aim,4)
    Pathfinding_FindPath(Hero,Aim)
  EndIf
  If KeyboardPushed(#PB_Key_Up)
    MoveEntity(5,0,0,-1,#PB_Relative)
    Pathfinding_Init(Hero,Aim,4)
    Pathfinding_FindPath(Hero,Aim)
  EndIf
  If KeyboardReleased(#PB_Key_Space)
    Pathfinding_ShowPath(Hero,Aim)
  EndIf
  If KeyboardReleased(#PB_Key_D)
    Pathfinding_Init(Hero,Aim,4)
    Pathfinding_FindPath(Hero,Aim)
    ForEach Path()
      Debug "No: "+Str(Path()\Nr)+" - Distance to start mesh: "+StrD(Path()\Distance)
    Next
    Debug "-----------------------------------"
  EndIf
  
  RenderWorld()
  DisplayTransparentSprite(Overlay,0,0)
  FlipBuffers()
Until WindowEvent()=#PB_Event_CloseWindow


Re: RayCast does not work if entity is inside another one?

Posted: Sat Jan 18, 2014 11:00 pm
by MightyMAC
Hi guys,

I found out what the Problem is: RayCast does not recognise entities from inside of them. What I mean is that if you cast a ray from inside an entity to the outside it won't work, and as I use a bi-directional waypoint checking in my example the connection shown is from the waypoint inside the wall to the waypoint outside of the wall and not the other way. I found this out with another example I wrote:

Code: Select all

InitEngine3D()
InitSprite()

OpenWindow(0,100,100,800,600,"Test")
OpenWindowedScreen(WindowID(0),0,0,800,600)

; Camera
CreateCamera(1,0,0,100,100)
MoveCamera(1,0,100,100,#PB_Relative)
RotateCamera(1,-50,0,0)

; Textures
CreateTexture(0,256,256)
StartDrawing(TextureOutput(0))
  Box(0,0,255,255,RGB(255,255,255))
StopDrawing()

CreateMaterial(0,TextureID(0))
CreateMaterial(1,TextureID(0))
SetMaterialColor(0,#PB_Material_DiffuseColor,RGBA(255,0,0,100))
SetMaterialColor(1,#PB_Material_DiffuseColor,RGBA(255,0,0,100))

; Light
CreateLight(6,RGB(255,255,255),0,50,0)
AmbientColor(RGB(25, 25, 25))

Cube=CreateCube(#PB_Any,10)
Sphere=CreateCylinder(#PB_Any,2,20)
Plane=CreatePlane(#PB_Any,100,100,1,1,1,1)

CubeEnt=CreateEntity(#PB_Any,MeshID(Cube),MaterialID(0))
SphereEnt=CreateEntity(#PB_Any,MeshID(Sphere),MaterialID(1))
PlaneEnt=CreateEntity(#PB_Any,MeshID(Plane),#PB_Material_None)

MoveEntity(PlaneEnt,0,0,-20)
RotateEntity(PlaneEnt,90,0,0,#PB_Absolute)

Dir=1
Speed.f=0.2
x.f=0

Repeat
  
  If Dir=1
    x+Speed
  Else
    x-Speed
  EndIf
  
  If x>40 Or x<-10
    Dir=1-Dir
  EndIf
  
  MoveEntity(CubeEnt,x,0,0,#PB_Absolute)
  Res=RayCast(30,0,0,-1,0,0,-1)
  If Res>0
    If Material<>0
      SetMaterialColor(Material,#PB_Material_SelfIlluminationColor,RGB(0,0,0))
      FreeMaterial(Material)
    EndIf
    Material=FetchEntityMaterial(Res,#PB_Any)
    SetMaterialColor(Material,#PB_Material_SelfIlluminationColor,RGB(0,255,0))
  EndIf
  CreateLine3D(0,30,0,0,RGB(255,255,255),PickX(),PickY(),PickZ(),RGB(255,255,255))
  
  RenderWorld()
  FlipBuffers()
Until WindowEvent()=#PB_Event_CloseWindow
So another question rises in me: Is there a way to cast an entity from the inside? For example to detect the walls of a room?

EDIT: I tried with different material culling modes, but that has no effect.