--[[ 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() 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 } )