xcam/init.lua

284 lines
6.9 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[
xcam Minetest mod that makes renderings of a scene using raycasting
Copyright © 2023 Nils Dagsson Moskopp (erlehmann)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Dieses Programm hat das Ziel, die Medienkompetenz der Leser zu
steigern. Gelegentlich packe ich sogar einen handfesten Buffer
Overflow oder eine Format String Vulnerability zwischen die anderen
Codezeilen und schreibe das auch nicht dran.
]]--
local create_photo_item = function( pixels )
local itemstack = ItemStack( "xcam:photo" )
local meta = itemstack:get_meta()
local image = tga_encoder.image( pixels )
image:encode()
local texture_string = "[png:" .. minetest.encode_base64( image.data )
meta:set_string(
"inventory_image",
texture_string
)
return itemstack
end
local clamp = function( x, min, max )
return math.max( math.min( x, max ), min )
end
local get_node_color = function( node )
local short_name = node.name:gmatch(":.+")():gsub(":", "")
local value = ( #short_name % ( string.byte( short_name ) - 65 ) ) * 8
return clamp( value, 0, 255 )
end
local get_light = function( position )
return minetest.get_node_light( position ) / 16
end
local get_fog = function( distance )
return clamp( math.pow ( distance / 64, 2 ), 0, 1 )
end
local shade = function( origin, direction, background_color )
local ray = minetest.raycast( origin, direction, false, true )
local color
for pointed_thing in ray do
if "node" == pointed_thing.type then
local position = pointed_thing.under
local node = minetest.get_node( position )
local base_color = get_node_color( node )
local light = get_light( pointed_thing.above )
local distance = vector.distance ( position, origin )
local fog = get_fog( distance )
local l_color = base_color * light
color = math.floor(
l_color * ( 1 - fog ) +
background_color * fog
)
break
end
end
return color
end
x_offset_by_sample = { 0, 1, 0, 1, 0, 1,-1,-1,-1 }
y_offset_by_sample = { 0, 0, 1, 1,-1,-1, 0, 1,-1 }
local create_photo_pixels = function( player_name )
local width = 128
local height = 128
local girl = minetest.get_player_by_name( player_name )
local eye = girl:get_pos() + girl:get_eye_offset() / 10
eye.y = eye.y + girl:get_properties().eye_height
local dir = girl:get_look_dir()
local gaze = vector.normalize( girl:get_look_dir() ) + eye * -1
local down = vector.new({ x=0, y=-1, z=0 }) * 4
local right = vector.normalize( gaze:cross( down ) ) * -4
local corner = gaze + ( right + down ) * width / 2
local bg = 127
local pixels = {}
for h = 1,height,1 do
local y = h - 1
pixels[h] = {}
for w = 1,width,1 do
local x = w - 1
local samples = 4 -- can be up to 9
local acc_color = 0
for sample = 1,samples,1 do
local x_offset = x_offset_by_sample[sample] * 0.2
local y_offset = y_offset_by_sample[sample] * 0.2
local lens = (
right * x_offset +
down * y_offset
) * 99
-- TODO: why does this point towards ( 0, 0, 0 )?
local direction = (
gaze +
right * ( width/2 - x + x_offset ) +
down * ( height/2 - y + y_offset )
)
local color = shade(
eye,
direction,
bg
)
if nil == color then -- probably the sky
color = 191 - math.floor ( y / 2 )
end
acc_color = acc_color + (
clamp(
color / samples,
0,
255 / samples
)
)
end
pixels[h][w] = { math.floor( acc_color ) }
end
end
return pixels
end
minetest.register_chatcommand(
"snap",
{
description = "Snap a photo.",
func = function( player_name )
local pixels = create_photo_pixels( player_name )
local photo_item = create_photo_item( pixels )
local girl = minetest.get_player_by_name( player_name )
girl:get_inventory():add_item("main", photo_item)
end
}
)
minetest.register_craftitem(
"xcam:photo",
{
description = "A photo.",
inventory_image = "blank.png",
groups = { not_in_creative_inventory = 1 },
on_place = function(itemstack, player, pointed_thing)
if "node" ~= pointed_thing.type then
return
end
local player_pos = player:get_pos()
if not player_pos then
return
end
local node_pos = pointed_thing.under
local direction = vector.normalize(
vector.subtract(
node_pos,
player_pos
)
)
local wallmounted = minetest.dir_to_wallmounted(
direction
)
-- TODO: implement photos on floor or ceiling
if wallmounted < 2 then
return
end
direction = minetest.wallmounted_to_dir(
wallmounted
)
local pos = vector.subtract(
node_pos,
vector.multiply(
direction,
1/2 + 1/256 -- avoid z-fighting
)
)
local itemstring = itemstack:to_string()
if pos and "" ~= itemstring then
local staticdata = {
_wallmounted = wallmounted,
_itemstring = itemstring,
}
local obj = minetest.add_entity(
pos,
"xcam:photo",
minetest.serialize(staticdata)
)
if obj then
-- TODO: creative mode
itemstack:take_item()
end
end
return itemstack
end
}
)
minetest.register_entity(
"xcam:photo",
{
visual = "upright_sprite",
visual_size = { x = 1, y = 1 },
physical = false,
collide_with_objects = false,
textures = { "blank.png" },
on_activate = function(self, staticdata)
if (
staticdata and
"" ~= staticdata
) then
local data = minetest.deserialize(staticdata)
if not data then
return
end
self._wallmounted = data._wallmounted
assert( self._wallmounted )
local min, max = -8/16, 8/16
local len = 1/64
local sbox
if 2 == self._wallmounted then
sbox = { -len, min, min, len, max, max }
elseif 3 == self._wallmounted then
sbox = { -len, min, min, len, max, max }
elseif 4 == self._wallmounted then
sbox = { min, min, -len, max, max, len }
elseif 5 == self._wallmounted then
sbox = { min, min, -len, max, max, len }
end
assert( sbox )
self._itemstring = data._itemstring
assert( self._itemstring )
local itemstack = ItemStack(self._itemstring)
local meta = itemstack:get_meta()
local texture = meta:get_string("inventory_image")
self.object:set_properties({
selectionbox = sbox,
textures = { texture },
})
local yaw = minetest.dir_to_yaw(
minetest.wallmounted_to_dir(
self._wallmounted
)
)
self.object:set_yaw(yaw)
end
end,
get_staticdata = function(self)
return minetest.serialize(
{
_wallmounted = self._wallmounted,
_itemstring = self._itemstring,
}
)
end,
on_punch = function(self)
-- TODO: implement protection
local pos = self.object:get_pos()
local itemstring = self._itemstring
if pos and itemstring then
minetest.add_item(
pos,
itemstring
)
self.object:remove()
end
end
}
)