Ever since @John said it should be possible, I’ve wanted to do it: implement @Ignatz’s approach for pixel-perfect touch detection in Craft.
It works great, with one problem.
I have to render each object in silhouette, to a buffer, but once I make them render as silhouettes, I can’t get them back to their original look.
Can anyone help me get them back to their original look?
Project:
GetEntityByTouch.zip (35.6 KB)
Or if you prefer, code:
-- setup function
function setup()
--define assets for entities
assetList = {
asset.builtin.Blocky_Characters.ManAlternative,
asset.builtin.Blocky_Characters.Orc,
asset.builtin.Watercraft.watercraftPack_003_obj,
asset.builtin.Watercraft.watercraftPack_024_obj,
asset.builtin.CastleKit.metalGate_obj,
asset.builtin.CastleKit.siegeCatapult_obj
}
--setup scene
makeScene()
--register custom shader with the scene
craft.shader.add(customShaderUnlit)
--setup buffer to draw to for touch detection
makeTouchBuffer()
-- Create and position entities within the visible area
zDepth = 50
local lowerLeft, upperRight = craftCoordsForScreenCornersAtDepth(zDepth)
makeEntitiesInsideBounds(lowerLeft, upperRight, zDepth)
end
function draw()
drawThumbnail()
end
-- touch function to handle touch events
function touched(touch)
touches.touched(touch)
if touch.state == BEGAN then
renderToBuffer()
checkTouch(touch)
end
end
function drawThumbnail()
pushStyle()
spriteMode(CORNER)
-- Define the size of the thumbnail
local thumbnailSize = vec2(WIDTH/5, HEIGHT/5) -- Adjust as needed
-- Define the position of the thumbnail
local thumbnailPos = vec2(WIDTH - thumbnailSize.x - 20, 20)
-- Draw the thumbnail
sprite(touchBuffer, thumbnailPos.x, thumbnailPos.y, thumbnailSize.x, thumbnailSize.y)
popStyle()
end
-- Updated checkTouch function
function checkTouch(touch)
local r, g, b, a = touchBuffer:get(math.floor(touch.x), math.floor(touch.y))
for i, entity in ipairs(entities) do
local entityColor = entity.pickColor
if entityColor.r == r and entityColor.g == g and entityColor.b == b then
print("Touched entity: "..entity.number..", "..entity.modelName)
return
end
end
print("No entity touched")
end
function makeScene()
craft.scene.main = craft.scene()
local scene = craft.scene.main
scene.sky.material.sky = color(72, 235, 156)
scene.sky.material.horizon = color(30, 47, 66)
scene.sky.material.ground = color(52, 90, 75)
scene.camera:add(OrbitViewer,vec3(0,0, 0), 20, 0,1000)
end
function makeTouchBuffer()
touchBuffer = image(WIDTH, HEIGHT)
bufferBGColor = color(255, 0, 202)
uniqueColors[bufferBGColor] = true
end
function craftCoordsForScreenCornersAtDepth(zDepth)
-- Ensure the scene is updated and drawn at least once before calling screenToWorld
craft.scene.main:update(DeltaTime)
craft.scene.main:draw()
-- Get screen boundaries in Craft coordinates
local lowerLeft = craft.scene.main.camera:get(craft.camera):screenToWorld(vec3(0, 0, zDepth))
local upperRight = craft.scene.main.camera:get(craft.camera):screenToWorld(vec3(WIDTH, HEIGHT, zDepth))
return lowerLeft, upperRight
end
function makeEntitiesInsideBounds(lowerLeft, upperRight, zDepth)
entities = {}
local entityCounter = 0
for i, assetName in ipairs(assetList) do
for j = 1, 3 do
local entity = craft.scene.main:entity()
entity.model = craft.model(assetName)
entityCounter = entityCounter + 1
entity.number = entityCounter
entity.modelName = string.match(tostring(assetName), ".+/(.+)%.") or assetName -- Extracts the name between the last slash and the last period
-- Generate random position within the visible area
local x = math.random() * (upperRight.x - lowerLeft.x) + lowerLeft.x
local y = math.random() * (upperRight.y - lowerLeft.y) + lowerLeft.y
-- Assign position and random rotation to the entity
entity.position = vec3(x, y, zDepth)
entity.rotation = quat.eulerAngles(math.random(0, 360), math.random(0, 360), math.random(0, 360))
-- Give it a random color
entity.pickColor = getUniqueColor()
-- Create a small colored image for this entity
entity.colorImage = image(1, 1) -- Create a 1x1 pixel image
entity.colorImage:set(1, 1, entity.pickColor) -- Set the color of the pixel
-- Store entities for later use
table.insert(entities, entity)
end
end
end
-- Global table to store unique colors
uniqueColors = {}
-- Function to get a unique random color
function getUniqueColor()
while true do
local col = color(math.random(255), math.random(255), math.random(255))
local key = tostring(col)
if uniqueColors[key] == nil then
uniqueColors[key] = true
return col
end
end
end
function renderToBuffer()
setContext(touchBuffer)
craft.scene.main.sky.active = false
craft.scene.main.camera:get(craft.camera).clearColor = bufferBGColor
for i, entity in ipairs(entities) do
entity.originalMaterial = entity.material
if entity.originalMaterial then
entity.originalMap = entity.material.map
entity.originalOffsetRepeat = entity.material.offsetRepeat
end
entity.material = craft.material("Custom Unlit") -- Assign the shader by name
entity.material.map = entity.colorImage
end
craft.scene.main:update(DeltaTime)
craft.scene.main:draw()
-- Reset materials and maps to their original state
for i, entity in ipairs(entities) do
if entity.originalMaterial then
entity.material = entity.originalMaterial
entity.material.map = entity.originalMap
else
entity.material = craft.material(asset.builtin.Materials.Standard)
entity.material.map = readImage(asset.builtin.Blocky_Characters.OrcSkin)
end
end
craft.scene.main.sky.active = true
setContext()
end
-- Custom Shader
customShaderUnlit = {
name = "Custom Unlit",
options =
{
USE_COLOR = { true },
},
properties =
{
--unless diffuse is specifically added to a custom shader you won't be able to externally modify it on the material
map = {"texture2D", nil},
diffuse = {"vec3", vec3(1, 1, 1)}
},
pass =
{
base = "Surface",
blendMode = "disabled",
depthWrite = true,
depthFunc = "lessEqual",
renderQueue = "solid",
colorMask = {"rgba"},
cullFace = "back",
vertex =
[[
void vertex(inout Vertex v, out Input o)
{}
]],
surface =
[[
uniform sampler2D map;
uniform vec3 diffuse;
void surface(in Input IN, inout SurfaceOutput o)
{
o.diffuse = texture(map, IN.uv * 10.0).rgb * diffuse;
o.emissive = 1.0;
o.emission = vec3(0.0, 0.0, 0.0);
}
]]
}
}
customShaderPhysical = {
name = "Custom Physical",
options =
{
USE_COLOR = { true },
USE_LIGHTING = { true },
STANDARD = { true },
PHYSICAL = { true },
ENVMAP_TYPE_CUBE = { true },
ENVMAP_MODE_REFLECTION = { true },
USE_ENVMAP = { false, {"envMap"} },
},
properties =
{
envMap = { "cubeTexture", "nil" },
envMapIntensity = { "float", "0.75" },
refactionRatio = { "float", "0.5" },
diffuse = {"vec3", vec3(1, 1, 1)}
},
pass =
{
base = "Surface",
blendMode = "disabled",
depthWrite = true,
depthFunc = "lessEqual",
renderQueue = "solid",
colorMask = {"rgba"},
cullFace = "back",
vertex =
[[
void vertex(inout Vertex v, out Input o)
{
}
]],
surface =
[[
uniform vec3 diffuse;
void surface(in Input IN, inout SurfaceOutput o)
{
o.diffuse = vec3(1.0, 1.0, 1.0) * diffuse;
o.roughness = 0.32468;
o.metalness = 0.0;
o.emission = vec3(0.0, 0.0, 0.0);
o.emissive = 1.0;
o.opacity = 1.0;
o.occlusion = 1.0;
}
]]
}
}