+ Initial import
This commit is contained in:
commit
c84edc1345
|
@ -0,0 +1,291 @@
|
|||
--[[
|
||||
|
||||
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(
|
||||
"xcam:texture",
|
||||
texture_string
|
||||
)
|
||||
meta:set_string(
|
||||
"inventory_image",
|
||||
"${xcam:texture}"
|
||||
)
|
||||
meta:set_string(
|
||||
"wield_image",
|
||||
"${xcam:texture}"
|
||||
)
|
||||
|
||||
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("xcam:texture")
|
||||
|
||||
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
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue