Displacement map prototype (#26709)

Requires https://github.com/space-wizards/RobustToolbox/pull/5023

This uses the new engine features (above) to add a displacement map shader. This allows deforming a sprite based on another sprite.

Primary use case is automatically adapting human clothing sprites to different species, something we want to make species like Vox a reality.

A basic example of wiring this up with Vox has been added. The system is however incredibly simple and **will** need more work by a content developer to select and toggle displacement maps when appropriate. I am leaving that to somebody else. For example right now the displacement map is applied even if a species already has custom-fit sprites for a piece of clothing, such as the grey jumpsuit for Vox.

Basic Aseprite plugins to help with authoring displacement maps have also been made.
This commit is contained in:
Pieter-Jan Briers
2024-04-27 08:03:58 +02:00
committed by GitHub
parent b4212a08f4
commit 2f7d0dedbd
9 changed files with 303 additions and 1 deletions

View File

@@ -0,0 +1,78 @@
local sprite = app.editor.sprite
local cel = app.cel
if sprite.selection.isEmpty then
print("You need to select something sorry")
return
end
local diag = Dialog{
title = "Flip Displacement Map"
}
diag:check{
id = "horizontal",
label = "flip horizontal?"
}
diag:check{
id = "vertical",
label = "flip vertical?"
}
diag:button{
text = "ok",
focus = true,
onclick = function(ev)
local horizontal = diag.data["horizontal"]
local vertical = diag.data["vertical"]
local selection = sprite.selection
local image = cel.image:clone()
for x = 0, selection.bounds.width do
for y = 0, selection.bounds.height do
local xSel = x + selection.origin.x
local ySel = y + selection.origin.y
local xImg = xSel - cel.position.x
local yImg = ySel - cel.position.y
if xImg < 0 or xImg >= image.width or yImg < 0 or yImg >= image.height then
goto continue
end
local imgValue = image:getPixel(xImg, yImg)
local color = Color(imgValue)
if horizontal then
color.red = 128 + -(color.red - 128)
end
if vertical then
color.green = 128 + -(color.green - 128)
end
image:drawPixel(
xImg,
yImg,
app.pixelColor.rgba(color.red, color.green, color.blue, color.alpha))
::continue::
end
end
cel.image = image
diag:close()
end
}
diag:button{
text = "cancel",
onclick = function(ev)
diag:close()
end
}
diag:show()

View File

@@ -0,0 +1,135 @@
-- Displacement Map Visualizer
--
-- This script will create a little preview window that will test a displacement map.
--
-- TODO: Handling of sizes != 127 doesn't work properly and rounds differently from the real shader. Ah well.
local scale = 4
-- This script requires UI
if not app.isUIAvailable then
return
end
local getOffsetPixel = function(x, y, image, rect)
local posX = x - rect.x
local posY = y - rect.y
if posX < 0 or posX >= image.width or posY < 0 or posY >= image.height then
return image.spec.transparentColor
end
return image:getPixel(posX, posY)
end
local pixelValueToColor = function(sprite, value)
return Color(value)
end
local applyDisplacementMap = function(width, height, size, displacement, displacementRect, target, targetRect)
-- print(Color(displacement:getPixel(17, 15)).red)
local image = target:clone()
image:resize(width, height)
image:clear()
for x = 0, width - 1 do
for y = 0, height - 1 do
local value = getOffsetPixel(x, y, displacement, displacementRect)
local color = pixelValueToColor(sprite, value)
if color.alpha ~= 0 then
local offset_x = (color.red - 128) / 127 * size
local offset_y = (color.green - 128) / 127 * size
local colorValue = getOffsetPixel(x + offset_x, y + offset_y, target, targetRect)
image:drawPixel(x, y, colorValue)
end
end
end
return image
end
local dialog = nil
local sprite = app.editor.sprite
local spriteChanged = sprite.events:on("change",
function(ev)
dialog:repaint()
end)
local layers = {}
for i,layer in ipairs(sprite.layers) do
table.insert(layers, 1, layer.name)
end
local findLayer = function(sprite, name)
for i, layer in ipairs(sprite.layers) do
if layer.name == name then
return layer
end
end
return nil
end
dialog = Dialog{
title = "Displacement map preview",
onclose = function(ev)
sprite.events:off(spriteChanged)
end}
dialog:canvas{
id = "canvas",
width = sprite.width * scale,
height = sprite.height * scale,
onpaint = function(ev)
local context = ev.context
local layerDisplacement = findLayer(sprite, dialog.data["displacement-select"])
local layerTarget = findLayer(sprite, dialog.data["reference-select"])
-- print(layerDisplacement.name)
-- print(layerTarget.name)
local celDisplacement = layerDisplacement:cel(1)
local celTarget = layerTarget:cel(1)
local image = applyDisplacementMap(
sprite.width, sprite.height,
dialog.data["size"],
celDisplacement.image, celDisplacement.bounds,
celTarget.image, celTarget.bounds)
context:drawImage(image, 0, 0, image.width, image.height, 0, 0, image.width * scale, context.width, context.height)
end
}
dialog:combobox{
id = "displacement-select",
label = "displacement layer",
options = layers,
onchange = function(ev)
dialog:repaint()
end
}
dialog:combobox{
id = "reference-select",
label = "reference layer",
options = layers,
onchange = function(ev)
dialog:repaint()
end
}
dialog:slider{
id = "size",
label = "displacement size",
min = 1,
max = 127,
value = 127,
onchange = function(ev)
dialog:repaint()
end
}
dialog:show{wait = false}