# Vortex - Camera Picking and Project

#### MikeHart

Looks really great! Congratulations.
Hey @JaviCervera , great to see you here again. I hope life is treating you well.

I know I had asked that before. Is there any chance you could add Object or Camera picking aka raycasting to Vortex and 3d coords conversion to 2D coords. For some reason I can't get my head around it, I am simply to stupid.

I know there is no money to be made and no fame to get for doing this, just my endless gratitude. I would love to add your framework as an official module to CX but I know people would ask for these features. And of course, I would ask that myself. I would love to port my game CardRogues to Vortex, but I need picking/ray casting to see which object I have clicked on.

#### Amon

##### Member
@JaviCervera

Hi Javi. Can you let us know what the cost will be to have the features @MikeHart asked about in the previous post? I'm willing to fund having you implement those features.

As long as I don't need to mortgage my house to pay the fee then I'm positive we can come up with an agreement.

#### innerloop

##### New Member
CX Code Contributor
@MikeHart
@Amon

I can offer you temporary solution

add some utils to file math3d.cxs:
``````Function Vec3Sub:Void( result:Float[], l:Float[], r:Float[] )
result[0] = l[0] - r[0]
result[1] = l[1] - r[1]
result[2] = l[2] - r[2]
End

Function Vec3Cross:Void( result:Float[], l:Float[], r:Float[] )
result[0] = l[1] * r[2] - l[2] * r[1]
result[1] = l[2] * r[0] - l[0] * r[2]
result[2] = l[0] * r[1] - l[1] * r[0]
End

Function Vec3Dot:Float( l:Float[], r:Float[] )
Return l[0] * r[0] + l[1] * r[1] + l[2] * r[2]
End

Function Vec3Transform:Void( result:Float[], v:Float[], m:Float[] )
Local x:Float = v[0]
Local y:Float = v[1]
Local z:Float = v[2]
result[0] = x * m[0] + y * m[4] + z * m[ 8] + m[12]
result[1] = x * m[1] + y * m[5] + z * m[ 9] + m[13]
result[2] = x * m[2] + y * m[6] + z * m[10] + m[14]
End

Function Vec3TransformCoord:Void( result:Float[], v:Float[], m:Float[] )
Local x:Float = v[0]
Local y:Float = v[1]
Local z:Float = v[2]
Local t:Float = 1.0 / ( x * m[3] + y * m[7] + z * m[11] + m[15] )
result[0] = ( x * m[0] + y * m[4] + z * m[ 8] + m[12] ) * t
result[1] = ( x * m[1] + y * m[5] + z * m[ 9] + m[13] ) * t
result[2] = ( x * m[2] + y * m[6] + z * m[10] + m[14] ) * t
End

Function Vec3TransformNormal:Void( result:Float[], v:Float[], m:Float[] )
Local x:Float = v[0]
Local y:Float = v[1]
Local z:Float = v[2]
result[0] = x * m[0] + y * m[4] + z * m[ 8]
result[1] = x * m[1] + y * m[5] + z * m[ 9]
result[2] = x * m[2] + y * m[6] + z * m[10]
End

Function Vec3Normalize:Float( result:Float[], v:Float[] )
Local l:Float = Vec3Dot( v, v )
If l > 0.0 Then
l = Sqrt( l )
Local s:Float = 1.0 / l
result[0] = v[0] * s
result[1] = v[1] * s
result[2] = v[2] * s
End If
Return l
End

Function RayTriangleIntersection:Bool( result:Float[], origin:Float[], direction:Float[], point1:Float[], point2:Float[], point3:Float[] )
Local e1x := point2[0] - point1[0]
Local e1y := point2[1] - point1[1]
Local e1z := point2[2] - point1[2]
Local e2x := point3[0] - point1[0]
Local e2y := point3[1] - point1[1]
Local e2z := point3[2] - point1[2]
Local pvx := direction[1] * e2z - direction[2] * e2y
Local pvy := direction[2] * e2x - direction[0] * e2z
Local pvz := direction[0] * e2y - direction[1] * e2x
Local d := e1x * pvx + e1y * pvy + e1z * pvz
If d > -0.001 And d < 0.001 Then Return False
d = 1.0 / d
Local tvx := origin[0] - point1[0]
Local tvy := origin[1] - point1[1]
Local tvz := origin[2] - point1[2]
Local u := ( tvx * pvx + tvy * pvy + tvz * pvz ) * d
If u < 0.0 Or u > 1.0 Then Return False
Local qvx := tvy * e1z - tvz * e1y
Local qvy := tvz * e1x - tvx * e1z
Local qvz := tvx * e1y - tvy * e1x
Local v := ( direction[0] * qvx + direction[1] * qvy + direction[2] * qvz ) * d
If v < 0.0 Or u + v > 1.0 Then Return False
Local t := ( e2x * qvx + e2y * qvy + e2z * qvz ) * d
result[0] = t
result[1] = u
result[2] = v
Return True
End

Function RayAxisAlignedBoxIntersection:Bool( result:Float[], origin:Float[], direction:Float[], min:Float[], max:Float[] )
Local l1:Float = ( min[0] - origin[0] ) / direction[0]
Local l2:Float = ( max[0] - origin[0] ) / direction[0]
Local t1:Float = Min( l1, l2 )
Local t2:Float = Max( l1, l2 )
l1 = ( min[1] - origin[1] ) / direction[1]
l2 = ( max[1] - origin[1] ) / direction[1]
t1 = Max( Min( l1, l2 ), t1 )
t2 = Min( Max( l1, l2 ), t2 )
l1 = ( min[2] - origin[2] ) / direction[2]
l2 = ( max[2] - origin[2] ) / direction[2]
t1 = Max( Min( l1, l2 ), t1 )
t2 = Min( Max( l1, l2 ), t2 )
If t2 < 0.0 Or t2 < t1 Then Return False
result[0] = t1
result[1] = t2
Return True
End

Function RaySphereIntersection:Bool(result:Float[], origin:Float[], direction:Float[], sphere:Float[] )
Local rox := origin[0] - sphere[0]
Local roy := origin[1] - sphere[1]
Local roz := origin[2] - sphere[2]
Local a := direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]
Local b := 2.0 * ( rox * direction[0] + roy * direction[1] + roz * direction[2] )
Local c := ( rox * rox + roy * roy + roz * roz ) - sphere[3] * sphere[3]
Local d := b * b - 4.0 * a * c
If d < 0.0 Then Return False
d = Sqrt( d )
a = 0.5 / a
Local t1 := ( d - b ) * a
Local t2 := ( -b - d ) * a
If t1 < 0.0 Then t1 = t2
If t2 < 0.0 Then t2 = t1
t1 = Min( t1, t2 )
If t1 < 0.0 Then Return False
result[0] = t1
Return True
End``````

add this to class World inside file world.cxs:
``````Private

Global rtTraceInfo:TraceInfo = New TraceInfo
Global rtProjectionTransform:Float[16]
Global rtViewTransform:Float[16]
Global rtInverseViewTransform:Float[16]
Global rtNormPoint:Float[3]
Global rtLineDirection:Float[3]
Global mPickedEntity:Entity
Global mPickedSurface:Surface
Global mPickedTriangle:Int
Global mPickedDistance:Float
Global mPickedPoint:Float[3]
Global mPickedNormal:Float[3]

Public

Function PickedEntity:Entity()
Return mPickedEntity
End

Function PickedSurface:Surface()
Return mPickedSurface
End

Function PickedTriangle:Int()
Return mPickedTriangle
End

Function PickedDistance:Float()
Return mPickedDistance
End

Function PickedX:Float()
Return mPickedPoint[0]
End

Function PickedY:Float()
Return mPickedPoint[1]
End

Function PickedZ:Float()
Return mPickedPoint[2]
End

Function PickedNX:Float()
Return mPickedNormal[0]
End

Function PickedNY:Float()
Return mPickedNormal[1]
End

Function PickedNZ:Float()
Return mPickedNormal[2]
End

Function RayTrace:Bool( rayOrigin:Float[], rayDirection:Float[], maxDistance:Float = 0.0 )
mPickedEntity   = Null
mPickedSurface  = Null
mPickedTriangle = -1
mPickedDistance = 10000000000.0
Local minDistance:Float = 10000000000.0
For Local targetEntity:Entity = EachIn mEntities
If Not ( targetEntity.Visible And targetEntity.Pickable ) Then Continue
If Not targetEntity.RayTrace( rtTraceInfo, rayOrigin, rayDirection, maxDistance ) Then Continue
If rtTraceInfo.mDistance >= minDistance Then Continue
minDistance = rtTraceInfo.mDistance
mPickedEntity    = targetEntity
mPickedSurface   = rtTraceInfo.mSurface
mPickedTriangle  = rtTraceInfo.mTriangle
mPickedDistance  = rtTraceInfo.mDistance
mPickedPoint[0]  = rtTraceInfo.mPoint[0]
mPickedPoint[1]  = rtTraceInfo.mPoint[1]
mPickedPoint[2]  = rtTraceInfo.mPoint[2]
mPickedNormal[0] = rtTraceInfo.mNormal[0]
mPickedNormal[1] = rtTraceInfo.mNormal[1]
mPickedNormal[2] = rtTraceInfo.mNormal[2]
End
Return mPickedEntity <> Null
End

Function CameraPick:Bool( cameraEntity:Camera, screenX:Int, screenY:Int )
Mat4PerspectiveLH( cameraEntity.FovY, cameraEntity.AspectRatio, cameraEntity.Near, cameraEntity.Far, rtProjectionTransform )
rtNormPoint[0] = ( screenX * 2.0 / cameraEntity.ViewportWidth - 1.0  ) / rtProjectionTransform[0]
rtNormPoint[1] = ( 1.0 - screenY * 2.0 / cameraEntity.ViewportHeight ) / rtProjectionTransform[5]
rtNormPoint[2] = 1.0
Mat4ViewEuler( cameraEntity.WorldX, cameraEntity.WorldY, cameraEntity.WorldZ, cameraEntity.Pitch, cameraEntity.Yaw, cameraEntity.Roll, rtViewTransform )
Mat4Invert( rtViewTransform, rtInverseViewTransform )
Local rayOrigin:Float[3]
rayOrigin[0] = rtInverseViewTransform[12]
rayOrigin[1] = rtInverseViewTransform[13]
rayOrigin[2] = rtInverseViewTransform[14]
Local rayDirection:Float[3]
Vec3TransformNormal( rayDirection, rtNormPoint, rtInverseViewTransform )
Return RayTrace( rayOrigin, rayDirection )
End

Function LinePick:Bool( lineStart:Float[], lineEnd:Float[] )
Vec3Sub( rtLineDirection, lineEnd, lineStart )
Local lineLength:Float = Vec3Normalize( rtLineDirection, rtLineDirection )
Return RayTrace( lineStart, rtLineDirection, lineLength )
End``````

add this to class Model inside file model.cxs:
``````Private

Field rtModelBox:Float[6]
Field rtModelSphere:Float[4]
Field rtIntersectionInfo:Float[2]
Field rtModelTransform:Float[16]
Field rtInverseModelTransform:Float[16]
Field rtLocalRayOrigin:Float[3]
Field rtLocalRayDirection:Float[3]

Public

Method RayTrace:Bool( ti:Object, rayOrigin:Float[], rayDirection:Float[], maxDistance:Float = 0.0 )
Local traceInfo:TraceInfo = TraceInfo( ti )
rtModelBox[0] = BoxMinX
rtModelBox[1] = BoxMinY
rtModelBox[2] = BoxMinZ
rtModelBox[3] = BoxMaxX
rtModelBox[4] = BoxMaxY
rtModelBox[5] = BoxMaxZ
rtModelSphere[0] = ( rtModelBox[0] + rtModelBox[3] ) * 0.5
rtModelSphere[1] = ( rtModelBox[1] + rtModelBox[4] ) * 0.5
rtModelSphere[2] = ( rtModelBox[2] + rtModelBox[5] ) * 0.5
rtModelSphere[3] = Max( rtModelBox[5] - rtModelBox[2],
Max( rtModelBox[3] - rtModelBox[0],
rtModelBox[4] - rtModelBox[1] ) )
If Not RaySphereIntersection( rtIntersectionInfo, rayOrigin, rayDirection, rtModelSphere ) Then Return False
If maxDistance > 0.0 And rtIntersectionInfo[0] > maxDistance Then Return False
Mat4TransformEuler( WorldX, WorldY, WorldZ, Pitch, Yaw, Roll, ScaleX, ScaleY, ScaleZ, rtModelTransform )
Mat4Invert( rtModelTransform, rtInverseModelTransform )
Vec3TransformCoord( rtLocalRayOrigin, rayOrigin, rtInverseModelTransform )
Vec3TransformNormal( rtLocalRayDirection, rayDirection, rtInverseModelTransform )
If Not Mesh.RayTrace( traceInfo, rtLocalRayOrigin, rtLocalRayDirection ) Then Return False
If maxDistance > 0.0 And traceInfo.mDistance > maxDistance Then Return False
Vec3Transform( traceInfo.mPoint, traceInfo.mPoint, rtModelTransform )
Return True
End``````

add this to class Entity inside file entity.cxs:
``````Private

Field mPickable: Bool

Public

Method RayTrace:Bool( traceInfo:Object, rayOrigin:Float[], rayDirection:Float[], maxDistance:Float = 0.0 )
Return False
End

Method Pickable:Bool() Property
Return mPickable
End

Method Pickable:Void( state:Bool ) Property
mPickable = state
End``````

add this class to file mesh.cxs:
``````Class TraceInfo
Public
Field mSurface:Surface
Field mTriangle:Int
Field mDistance:Float
Field mPoint:Float[3]
Field mNormal:Float[3]
End``````

add this to class Mesh inside file mesh.cxs:
``````Private

Field rtVertexPosition0:Float[3]
Field rtVertexPosition1:Float[3]
Field rtVertexPosition2:Float[3]
Field rtTriangleEdge1:Float[3]
Field rtTriangleEdge2:Float[3]
Field rtTriangleCross:Float[3]
Field rtIntersectionInfo:Float[3]

Method rtGetTriangleData:Void( meshSurface:Surface, triangleIndex:Int )
Local vertexIndex0:Int = meshSurface.TriangleV0( triangleIndex )
Local vertexIndex1:Int = meshSurface.TriangleV1( triangleIndex )
Local vertexIndex2:Int = meshSurface.TriangleV2( triangleIndex )
rtVertexPosition0[0] = meshSurface.VertexX( vertexIndex0 )
rtVertexPosition0[1] = meshSurface.VertexY( vertexIndex0 )
rtVertexPosition0[2] = meshSurface.VertexZ( vertexIndex0 )
rtVertexPosition1[0] = meshSurface.VertexX( vertexIndex1 )
rtVertexPosition1[1] = meshSurface.VertexY( vertexIndex1 )
rtVertexPosition1[2] = meshSurface.VertexZ( vertexIndex1 )
rtVertexPosition2[0] = meshSurface.VertexX( vertexIndex2 )
rtVertexPosition2[1] = meshSurface.VertexY( vertexIndex2 )
rtVertexPosition2[2] = meshSurface.VertexZ( vertexIndex2 )
Vec3Sub( rtTriangleEdge1, rtVertexPosition1, rtVertexPosition0 )
Vec3Sub( rtTriangleEdge2, rtVertexPosition2, rtVertexPosition0 )
Vec3Cross( rtTriangleCross, rtTriangleEdge1, rtTriangleEdge2 )
End

Public

Method RayTrace:Bool( traceInfo:TraceInfo, rayOrigin:Float[], rayDirection:Float[] )
Local nearestSurface:Surface = Null
Local nearestTriangle:Int = -1
Local minDistance:Float = 10000000000.0
For Local surfaceIndex:Int = 0 Until NumSurfaces
Local meshSurface:Surface = Self.Surface( surfaceIndex )
For Local triangleIndex:Int = 0 Until meshSurface.NumTriangles
rtGetTriangleData( meshSurface, triangleIndex )
If Vec3Dot( rayDirection, rtTriangleCross ) >= 0.0 Then Continue
If Not RayTriangleIntersection( rtIntersectionInfo, rayOrigin, rayDirection, rtVertexPosition0, rtVertexPosition1, rtVertexPosition2 ) Then Continue
If rtIntersectionInfo[0] >= minDistance Then Continue
minDistance = rtIntersectionInfo[0]
nearestTriangle = triangleIndex
nearestSurface = meshSurface
End
End
If nearestSurface = Null Then Return False
traceInfo.mSurface  = nearestSurface
traceInfo.mTriangle = nearestTriangle
traceInfo.mDistance = minDistance
traceInfo.mPoint[0] = rayOrigin[0] + rayDirection[0] * minDistance
traceInfo.mPoint[1] = rayOrigin[1] + rayDirection[1] * minDistance
traceInfo.mPoint[2] = rayOrigin[2] + rayDirection[2] * minDistance
rtGetTriangleData( nearestSurface, nearestTriangle )
Vec3Normalize( traceInfo.mNormal, rtTriangleCross )
Return True
End``````

add this to class Camera in file camera.cxs:
``````Private

Field rtProjectionTransform:Float[16]
Field rtViewTransform:Float[16]
Field rtTempPoint:Float[3]
Field mProjectedPoint:Int[2]

Public

Method ProjectedX:Int() Property
Return mProjectedPoint[0]
End

Method ProjectedY:Int() Property
Return mProjectedPoint[0]
End

Method Project:Void( inputPoint:Float[] )
Mat4PerspectiveLH( FovY, AspectRatio, Near, Far, rtProjectionTransform )
Mat4ViewEuler( WorldX, WorldY, WorldZ, Pitch, Yaw, Roll, rtViewTransform )
Vec3TransformCoord( rtTempPoint, inputPoint, rtViewTransform )
Vec3TransformCoord( rtTempPoint, rtTempPoint, rtProjectionTransform )
mProjectedPoint[0] = ( rtTempPoint[0] + 1.0 ) * ( ViewportWidth  / 2 )
mProjectedPoint[1] = ( 1.0 - rtTempPoint[1] ) * ( ViewportHeight / 2 )
End``````

so, usage is simple:

usage:
``````Entity.Pickable = True' enable pick for entity

World.CameraPick( camera, x, y )' pick all entities (models) in world with specific camera and screen coords

World.LinePick( lineStart, lineEnd )' pick all entities (models) in world with specific line start and end coords

World.PickedEntity()' returns nearest picked entity

World.PickedSurface()' returns picked surface

World.PickedTriangle()' returns picked triangle

' returns picked point
World.PickedX()
World.PickedY()
World.PickedZ()

' returns picked normal
World.PickedNX()
World.PickedNY()
World.PickedNZ()

camera.Project( someWorldPoint )' project 3d world point on screen for specific camera

' returns projected point
camera.ProjectedX()
camera.ProjectedY()``````

Last edited:

#### MikeHart

Welcome to the forum @innerloop . What a surprise. May I ask why you mentioned a temporary solution ?

#### innerloop

##### New Member
CX Code Contributor
Welcome to the forum @innerloop . What a surprise. May I ask why you mentioned a temporary solution ?
Temporary till Javi will add this feature to the engine as official. This code was written on the knee, without much testing.

#### MikeHart

Question, if I want to cast a ray from a given Vec3 to another, how would I do it?

#### MikeHart

Temporary till Javi will add this feature to the engine as official. This code was written on the knee, without much testing.
I don't think Javi will do it.

#### innerloop

##### New Member
CX Code Contributor
Question, if I want to cast a ray from a given Vec3 to another, how would I do it?
two way
1) compute ray from line between two vec3 and use RayTrace but reject all picked entities with distance great then line length
2) write code of intersection line with box and triangle and another RayTrace with line-specific math (too complicated)

content deleted, see first post in this thread.

not optimal solution because all entities traced anyway

Last edited:

#### MikeHart

Thanks, this example for picking doesn't work quite right, depending on the cubes position and the cameras position.

Cerberus X:
``````Strict

#GLFW_WINDOW_TITLE="Vortex2 Camera Picking Test"
#GLFW_WINDOW_WIDTH=800
#GLFW_WINDOW_HEIGHT=600
#GLFW_WINDOW_RESIZABLE=True
#GLFW_WINDOW_SAMPLES=2

Import mojo.app
Import mojo.input
Import vortex
Import vortex.src.math3d
Import vortex.src.renderstate

Class TestApp Extends App

Method OnCreate:Int()
'Setup
SetUpdateRate(30)
SetSwapInterval(1)
Seed = Millisecs()

'Init vortex
If Not World.Init() Then EndApp()
Print "Vendor name: " + Graphics.VendorName()
Print "Renderer name: " + Graphics.RendererName()
Print "API version name: " + Graphics.APIVersionName()

'Create camera
mCam = New Camera()
mCam.BackgroundColor = \$FF0000FF
mCam.Position(0, 0, -2)
mCam.Rotate(0,0,0)

'Create model
For Local i := 1 To 15
Local mModel := New Model(New Mesh(Mesh.CreateCube()))
mModel.Pickable(True)
mModel.Position(Rnd(-4,4), Rnd(-4,4), Rnd(4,20))
mModel.Name("This is cube #"+i+"   ( "+mModel.X+" : "+mModel.Y+" : "+mModel.Z+" )")
mModel.Material.Color = Color.RGB(Rnd(0,255),Rnd(0,255),Rnd(0,255))
Print mModel.Name
Next

'Disable sun
World.SunColor(\$FFffffff)

Return False
End

Method OnUpdate:Int()
'End with escape key
#If TARGET<>"html5"
If KeyHit(KEY_ESCAPE) Then EndApp()
#End

'Update world
World.Update()

If KeyDown(KEY_LEFT)  mCam.Move(-0.05,    0,0)
If KeyDown(KEY_RIGHT) mCam.Move( 0.05,    0,0)
If KeyDown(KEY_UP)    mCam.Move(    0, 0.05,0)
If KeyDown(KEY_DOWN)  mCam.Move(    0,-0.05,0)
If KeyDown(KEY_PAGEUP)    mCam.Move( 0,   0, 0.05)
If KeyDown(KEY_PAGEDOWN)  mCam.Move( 0,   0,-0.05)
If KeyDown(KEY_SPACE)
mCam.Position( 0,   0, -2.0)
mCam.Rotate(0,0,0)
Endif
If KeyDown(KEY_A)  mCam.Turn( 0,   -0.5, 0.0)
If KeyDown(KEY_D)  mCam.Turn( 0,    0.5, 0.0)
If KeyDown(KEY_W)  mCam.Turn( -0.5,   0, 0.0)
If KeyDown(KEY_S)  mCam.Turn( 0.5,    0, 0.0)
If KeyDown(KEY_Q)  mCam.Turn(  0,   0, 0.5)
If KeyDown(KEY_E)  mCam.Turn( 0.0,    0, -0.5)

'If MouseHit(0) = True
If World.CameraPick(mCam, MouseX(), MouseY()) = True
mModelPicked = World.PickedEntity()
Else
mModelPicked = Null
Endif
'Endif
Return False
End

Method OnRender:Int()
'Render world
World.Render()

'Setup graphics for 2D
Graphics.Setup2D(0, 0, DeviceWidth(), DeviceHeight())

'Draw FPS
Local text:String = Graphics.FPS() + " FPS"
mFont.Draw(8, 8, text)
text = "Cam x/y/z = " + mCam.X + " : "+ mCam.Y + " : "+ mCam.Z
mFont.Draw(8, 25, text)
If mModelPicked <> Null
text = "Picked Model = " + mModelPicked.Name
mFont.Draw(8, 50, text)
Endif
Return False
End

Private
Field mFont				: Font
Field mCam				: Camera
Field mModel			: Model
Field mModelPicked      : Entity = Null
Field mPos:Float[3]
Field out:Int[]

End

Function Main:Int()
New TestApp
Return False
End``````

#### MikeHart

Ok, I added your changes. Still, when I move the camera with the cursor keys, it hardly detects anything.

But Camera.Project works fine so far.

Last edited:

#### innerloop

##### New Member
CX Code Contributor
Ok, I added your changes. Still, when I move the camera with the cursor keys, it hardly detects anything.

But Camera.Project works fine so far.
Fixed, see post with code, there is last fixed version.

Last edited:

#### MikeHart

@innerloop Awesome, I added your version from your first post and it works so far. Great job man.

#### MikeHart

I will move your post and our conversation to a new topic, so Martin topic is not trashed up. Thanks again!

#### innerloop

##### New Member
CX Code Contributor
Model.RayTrace
* add ray-vs-sphere (most generic collider) rejection instead of ray-vs-aabb => avoid false rejection in some cases
Mesh.RayTrace
*add triangle backface rejection => less amount of triangles checked => more performance

#### MikeHart

Model.RayTrace
* add ray-vs-sphere (most generic collider) rejection instead of ray-vs-aabb => avoid false rejection in some cases
Mesh.RayTrace
*add triangle backface rejection => less amount of triangles checked => more performance
Is that a suggestion or did you modify you post above?

#### innerloop

##### New Member
CX Code Contributor
* take out arrays from functions -> avoid runtime memory allocation -> more performance
* fix bad code for RaySphereIntersection
* fix wrong calculation of model bounding sphere
* add Camera.ProjectedX and Y (blitz3d style)
* code cleanup