@MikeHart
@Amon
I can offer you temporary solution
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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
[/CODE]
[CODE lang="cerberus" title="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[1]
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
[/CODE]
so, usage is simple:
[CODE lang="cerberus" title="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()
[/CODE]