Has anyone successfully used the craft physics raycast in 3D with craft rigidbodies? I didn’t manage to get it to work.
By the way, I remember seeing somewhere an undocumented command to draw a 3D line-can someone remind me about that.
Has anyone successfully used the craft physics raycast in 3D with craft rigidbodies? I didn’t manage to get it to work.
By the way, I remember seeing somewhere an undocumented command to draw a 3D line-can someone remind me about that.
@piinthesky Heres something I have. Use the sliders to move the raycast line around. The sphere is at (10,18,40). When you hit the sphere with the raycast line, the word hit will show below the center point. You can rotate the image to get a better view of the ray cast line and the sphere. You can change the size of the center circle and the raycast direction.
viewer.mode=STANDARD
function setup()
assert(OrbitViewer, "Please include Cameras as a dependency")
parameter.integer("x",0,60,0)
parameter.integer("y",0,60,0)
parameter.integer("z",0,60,0)
fill(255)
scene = craft.scene()
scene.sun.rotation=quat.eulerAngles(20,45,-30)
v=scene.camera:add(OrbitViewer,vec3(0,0,0),100,0,1000)
skyMaterial=scene.sky.material
skyMaterial.sky=color(158, 202, 223, 255)
skyMaterial.horizon=color(98, 166, 114, 255)
createSphere(vec3(10,18,40),2)
end
function draw()
update(DeltaTime)
scene:draw()
ellipse(WIDTH/2,HEIGHT/2,4) -- center point
scene.debug:line(vec3(0,0,0),vec3(x,y,z),color(255)) -- 3d line
ta=scene.physics:raycast(vec3(0,0,0),vec3(x,y,z),100)
text("(10, 18, 40)",WIDTH/2,HEIGHT/2-50)
if ta~=nil then
text("hit",WIDTH/2,HEIGHT/2-20)
end
end
function update(dt)
scene:update(dt)
end
function createSphere(p,size)
local sphere=scene:entity()
local s1=sphere:add(craft.rigidbody,STATIC)
sphere.position=vec3(p.x,p.y,p.z)
sphere.model = craft.model.icosphere(size,2)
sphere:add(craft.shape.sphere,size)
sphere.material = craft.material(asset.builtin.Materials.Specular)
sphere.material.map = readImage(asset.builtin.Blocks.Brick_Red)
end
Exactly what i was trying to do! Thanks @dave1707
@John I was hoping to write a 3D ray tracing program based on the physics:raycast command.
I see that the raycast works nicely when a ray is external to a rigid body and hits its surface.
Unfortunately a raycast starting inside a rigid body does not register a hit on its surface when it exits the rigid body.
Would it be possible to add an option to raycast so that the hit definition includes this ‘internal’ hit case?
If you’re after the x,y,z coords of where the raycast passes thru the center sphere, I think this will give you the info as x1,y1,z1. This assumes the center sphere is at 0,0,0. If it’s not the calculation gets a little more complicated. I think you just need to add the offset coordinates.
viewer.mode=STANDARD
function setup()
fill(255)
parameter.integer("x",0,60,20) -- x,y,z raycast direction
parameter.integer("y",0,60,20)
parameter.integer("z",0,60,20)
parameter.integer("radius",1,50,10) -- radius of center sphere
end
function draw()
background(0)
dist=math.sqrt(x^2+y^2+z^2)
x1=x*radius/dist
y1=y*radius/dist
z1=z*radius/dist
text("dist "..dist,WIDTH/2,HEIGHT/2)
text("x1 "..x1,WIDTH/2,HEIGHT/2-50) -- coord on center sphere
text("y1 "..y1,WIDTH/2,HEIGHT/2-80) -- where raycast passes through
text("z1 "..z1,WIDTH/2,HEIGHT/2-110)
end
Thanks @dave1707, actually the raycast gives the hit point on the rigid body and the direction of the normal at that point-so ok for that.
I want to be able to handle the case the raycast originates within a body (of any shape) and find its exit point. This would allow me to refract the light ray through the body (I would apply the Snells law at the interfaces). It must be the case that the existing behind the scenes code for the raycast could be easily modified to handle this. Otherwise, I would have loop through all the triangles in the mesh and calculate any intersection point. Sill hoping @John can help me here!
By the way @dave1707 do you have any code that can create a mesh for an arbitrary lens? I.e. different radii of curvature for each side of the lens I.e. intersection region of two spheres of different radii.
@piinthesky I had a look into it and couldn’t find any info on how to get Bullet physics (the engine we use) to work for raycasts inside objects. Do your rays begin outside an object and you just want to find the exit point after refraction? Technically you could move the ray origin forward after the first hit and fire it backward after setting a collision mask for that one object. That way you could find the exit point for that one object in the refraction direction
@John, thanks for investigating.
I would like to be to draw the paths of light rays as they pass through an arbitrary 3D object. I would assign a particular refractive index to each object.
One example could be a sphere…in this case I generate a raycast with an origin outside the sphere, then find its intersection with the sphere and its normal (Daves code does that already). Then generate another raycast from that intersection point but with the 3D angle changed to take into account the differences in refractive index so that it passes through the glass sphere until it hits somewhere on the other side of the sphere. Then I would repeat the process to draw the full path of the light ray through all the objects along its path.
Yes, that is a good idea. I calculate a point along the refracted ray that is well outside the object. Then from that point I fire a ray ‘backwards’ until it hits the object-that would be the exit point. Great I will try that!
@piinthesky Instead of a mesh, maybe it would be easier to calculate the tangent to a point on a sphere where the ray hits. Then calculate the angle the next ray takes.
@John, @dave1707, I implemented the backward raycast-all works well.
I notice if I scale the entity, the rendered image changes but the ray trace does not match. So I guess the raycast interacts with the rigid body and not the mesh of the model. As there is a limited choice of rigidbodies I may abandon the raycast and do it manually by testing for an intersection of the ray vector with each triangle of the model?
-- Raycast, 26/8/23
viewer.mode=STANDARD
function setup()
assert(OrbitViewer, "Please include Cameras as a dependency")
parameter.number("x",-1,1,0)
parameter.number("y",-1,1,1)
parameter.number("z",-1,1,0)
fill(255)
scene = craft.scene()
scene.sun.rotation=quat.eulerAngles(20,45,-30)
v=scene.camera:add(OrbitViewer,vec3(0,0,0),100,0,1000)
skyMaterial=scene.sky.material
skyMaterial.sky=color(158, 202, 223, 255)
skyMaterial.horizon=color(98, 166, 114, 255)
n1=1 --index of vacuum
n2=1.4 --index of sphere
mySphere=createSphere(vec3(0,18,0),10,true)
-- mySphere.scale=vec3(0.75,0.5,0.75)
org=createSphere(vec3(0,0,0),1,false) --origin
end
function draw()
update(DeltaTime)
scene:draw()
--draw ray
scene.debug:line(vec3(0,0,0),vec3(x,y,z)*100,color(255)) -- 3d line
--raycast from origin
ta=scene.physics:raycast(vec3(0,0,0),vec3(x,y,z),100)
if ta~=nil then
ent=ta.entity
entryPt=ta.point
entryNor=ta.normal
frac=ta.fraction
index=ta.triangleIndex
--draw entry normal
norStart=entryPt-entryNor
norEnd=entryPt+entryNor
scene.debug:line(norStart,norEnd,color(255))
--make internal ray
i=entryPt:normalize()
t=refractedRay(i,-entryNor,n1,n2)
--backwards raycast from outside
ta=scene.physics:raycast(entryPt+t*100, -t, 100)
exitPt=ta.point
exitNor=ta.normal
--draw exit normal
norStart=exitPt-exitNor
norEnd=exitPt+exitNor
scene.debug:line(norStart,norEnd,color(255))
--draw internal ray
scene.debug:line(entryPt,exitPt,color(241, 44, 80))
--get exit ray
i=(exitPt-entryPt):normalize()
t=refractedRay(i,exitNor,n2,n1)
--draw exit ray
scene.debug:line(exitPt,exitPt+t*100, color(241, 44, 80))
end
end
function update(dt)
scene:update(dt)
end
function createSphere(p,size,rigid)
local sphere=scene:entity()
if rigid then
local s1=sphere:add(craft.rigidbody,STATIC)
sphere:add(craft.shape.sphere,size)
end
sphere.position=vec3(p.x,p.y,p.z)
sphere.model = craft.model.icosphere(size,2)
sphere.material = craft.material(asset.builtin.Materials.Specular)
-- sphere.material.map = readImage(asset.builtin.Blocks.Brick_Red)
sphere.material.blendMode=NORMAL
sphere.material.opacity=0.3
return sphere
end
function refractedRay(i,n,n1,n2)
ni=n:dot(i)
mu=n1/n2
return math.sqrt(1-mu*mu*(1-ni*ni))*n + mu*(i-ni*n)
end
Nice demo. I made changes to my copy to make it easier to see the rays.
@John, shouldn’t scale also scale the rigidbody?
@piinthesky Scale should work on rigidbodies however I noticed your using a non-uniform scale on a sphere, which is not supported in Bullet since it can only handle regular spheres and not ellipsoids
Ok, I confirm it works when it is a uniform scale.
Using craft.shape.model I was able to implement the ray tracing for an arbitrary model. See video below
@piinthesky Nice video, very interesting. Can you share the code, I’m curious about the calculations. Also, I’m not sure about the code for uniform scale. I can get sprites to scale, but not Craft models, so I’m not doing something right.
Here is the current code. It uses the pseudoMesh class to make the cylinders for the rays.
Raycast2.zip (13.7 KB)
I will be extending this to handle multiple objects, reflections, filters, chromatic dispersion, etc.
Will be using it for work, but maybe also a puzzle game based on bouncing the light around.
Thanks for the code. It’s nice to try the different primitive objects and to slow the movement down to get a better view of the rays.
@John @dave1707 as suggested, I have tried to implement the collision mask on the raycast, but I did not succeed to make it work. Have you ever succeeded? Do you have an example code?
Here’s my original code again. The parameter values are set for a raycast hit with the text hit displayed in the center of the screen. In the raycast line of code, the last value (1) is the raycast group. In the createSphere function I have a mask of 3 set. The text hit will show. If you change the mask to 4, it won’t display hit. Mask and Group work with bit values or they should. So any bit that’s on in mask that has a corresponding bit in group that’s on should cause a hit.
viewer.mode=STANDARD
function setup()
assert(OrbitViewer, "Please include Cameras as a dependency")
parameter.integer("x",0,60,10)
parameter.integer("y",0,60,18)
parameter.integer("z",0,60,40)
fill(255)
scene = craft.scene()
scene.sun.rotation=quat.eulerAngles(20,45,-30)
v=scene.camera:add(OrbitViewer,vec3(0,0,0),100,0,1000)
skyMaterial=scene.sky.material
skyMaterial.sky=color(158, 202, 223, 255)
skyMaterial.horizon=color(98, 166, 114, 255)
createSphere(vec3(10,18,40),2)
end
function draw()
update(DeltaTime)
scene:draw()
ellipse(WIDTH/2,HEIGHT/2,4) -- center point
scene.debug:line(vec3(0,0,0),vec3(x,y,z),color(255)) -- 3d line
-- mask group = 1, bit 1 on
ta=scene.physics:raycast(vec3(0,0,0),vec3(x,y,z),100,1)
text("(10, 18, 40)",WIDTH/2,HEIGHT/2-50)
if ta~=nil then
text("hit",WIDTH/2,HEIGHT/2-20)
end
end
function update(dt)
scene:update(dt)
end
function createSphere(p,size)
local sphere=scene:entity()
local s1=sphere:add(craft.rigidbody,STATIC)
s1.mask=3
sphere.position=vec3(p.x,p.y,p.z)
sphere.model = craft.model.icosphere(size,2)
sphere:add(craft.shape.sphere,size)
sphere.material = craft.material(asset.builtin.Materials.Specular)
sphere.material.map = readImage(asset.builtin.Blocks.Brick_Red)
end
thanks @dave1707 that works now- i was confused with the groups and masks.