Lua Voxel Manipulators: Create chapter
This commit is contained in:
parent
fb1df1f851
commit
b551eb1d2f
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
*.zip
|
||||||
|
|
||||||
# Created by https://www.gitignore.io/api/node,ruby,linux,jekyll
|
# Created by https://www.gitignore.io/api/node,ruby,linux,jekyll
|
||||||
|
|
||||||
### Jekyll ###
|
### Jekyll ###
|
||||||
|
@ -39,58 +39,62 @@
|
|||||||
num: 8
|
num: 8
|
||||||
link: chapters/node_metadata.html
|
link: chapters/node_metadata.html
|
||||||
|
|
||||||
|
- title: Lua Voxel Manipulators
|
||||||
|
num: 9
|
||||||
|
link: chapters/lvm.html
|
||||||
|
|
||||||
- hr: true
|
- hr: true
|
||||||
|
|
||||||
- title: Privileges
|
- title: Privileges
|
||||||
num: 9
|
num: 10
|
||||||
link: chapters/privileges.html
|
link: chapters/privileges.html
|
||||||
|
|
||||||
- title: Chat and Commands
|
- title: Chat and Commands
|
||||||
num: 10
|
num: 11
|
||||||
link: chapters/chat.html
|
link: chapters/chat.html
|
||||||
|
|
||||||
- title: Chat Command Builder
|
- title: Chat Command Builder
|
||||||
num: 11
|
num: 12
|
||||||
link: chapters/chat_complex.html
|
link: chapters/chat_complex.html
|
||||||
|
|
||||||
- title: Player Physics
|
- title: Player Physics
|
||||||
num: 12
|
num: 13
|
||||||
link: chapters/player_physics.html
|
link: chapters/player_physics.html
|
||||||
|
|
||||||
- title: Formspecs
|
- title: Formspecs
|
||||||
num: 13
|
num: 14
|
||||||
link: chapters/formspecs.html
|
link: chapters/formspecs.html
|
||||||
|
|
||||||
- title: HUD
|
- title: HUD
|
||||||
num: 14
|
num: 15
|
||||||
link: chapters/hud.html
|
link: chapters/hud.html
|
||||||
|
|
||||||
- title: "SFINV: Inventory Formspec"
|
- title: "SFINV: Inventory Formspec"
|
||||||
num: 15
|
num: 16
|
||||||
link: chapters/sfinv.html
|
link: chapters/sfinv.html
|
||||||
|
|
||||||
- hr: true
|
- hr: true
|
||||||
|
|
||||||
- title: ItemStacks
|
- title: ItemStacks
|
||||||
num: 16
|
num: 17
|
||||||
link: chapters/itemstacks.html
|
link: chapters/itemstacks.html
|
||||||
|
|
||||||
- title: Inventories
|
- title: Inventories
|
||||||
num: 17
|
num: 18
|
||||||
link: chapters/inventories.html
|
link: chapters/inventories.html
|
||||||
|
|
||||||
- hr: true
|
- hr: true
|
||||||
|
|
||||||
- title: Automatic Error Checking
|
- title: Automatic Error Checking
|
||||||
num: 18
|
num: 19
|
||||||
link: chapters/luacheck.html
|
link: chapters/luacheck.html
|
||||||
|
|
||||||
- title: Releasing a Mod
|
- title: Releasing a Mod
|
||||||
num: 19
|
num: 20
|
||||||
link: chapters/releasing.html
|
link: chapters/releasing.html
|
||||||
|
|
||||||
- title: Read More
|
- title: Read More
|
||||||
num: 20
|
num: 21
|
||||||
link: chapters/readmore.html
|
link: chapters/readmore.html
|
||||||
|
|
||||||
- hr: true
|
- hr: true
|
||||||
|
190
en/chapters/lvm.md
Normal file
190
en/chapters/lvm.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
---
|
||||||
|
title: Lua Voxel Manipulators
|
||||||
|
layout: default
|
||||||
|
root: ../../
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The functions outlined in the [Basic Map Operations](environment.html) chapter
|
||||||
|
are easy to use and convenient, but for large areas they are not efficient.
|
||||||
|
Every time you call `set_node` or `get_node` your mod needs to communicate with
|
||||||
|
the engine, which results in copying. Copying is slow, and will quickly kill the
|
||||||
|
performance of your game.
|
||||||
|
|
||||||
|
* [Concepts](#concepts)
|
||||||
|
* [Reading into the LVM](#reading-into-the-lvm)
|
||||||
|
* [Reading Nodes](#reading-nodes)
|
||||||
|
* [Writing Nodes](#writing-nodes)
|
||||||
|
* [Example](#example)
|
||||||
|
* [Your Turn](#your-turn)
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
Creating a Lua Voxel Manipulator allows you to load large areas of the map into
|
||||||
|
your mod's memory at once. You can then read and write to this data without
|
||||||
|
interacting with the engine at all or running any callbacks, which means that
|
||||||
|
these operations are very fast. Once done, you can then write the area back into
|
||||||
|
the engine and run any lighting calculations.
|
||||||
|
|
||||||
|
## Reading into the LVM
|
||||||
|
|
||||||
|
You can only load a cubic area into an LVM, so you need to work out the minimum
|
||||||
|
and maximum positions that you need to modify. Then you can create and read into
|
||||||
|
an LVM like so:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
local vm = minetest.get_voxel_manip()
|
||||||
|
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||||
|
local data = vm:get_data()
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
An LVM may not read exactly the area you tell it to, for performance reasons.
|
||||||
|
Instead it may read a larger area. The larger area is given by `emin` and `emax`,
|
||||||
|
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
|
||||||
|
it contains for you - whether that involves loading from memory, from disk, or
|
||||||
|
calling the map generator.
|
||||||
|
|
||||||
|
## Reading Nodes
|
||||||
|
|
||||||
|
You'll need to use `emin` and `emax` to work out where a node is in the data of
|
||||||
|
an LVM. There's a helper class called `VoxelArea` which handles the calculation
|
||||||
|
for you:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
local a = VoxelArea:new{
|
||||||
|
MinEdge = emin,
|
||||||
|
MaxEdge = emax
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Get node's index
|
||||||
|
local idx = a:index(x, y, z)
|
||||||
|
|
||||||
|
-- Read node
|
||||||
|
print(data[idx])
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
If you run that, you'll notice that `data[vi]` is an integer. This is because
|
||||||
|
the engine doesn't store nodes using their name string, as string comparision
|
||||||
|
is slow. Instead, the engine uses a content ID. You can find out the content
|
||||||
|
ID for a particular type of node like so:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
local c_stone = minetest.get_content_id("default:stone")
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
and then you can check whether a node is stone like so:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
local idx = a:index(x, y, z)
|
||||||
|
if data[idx] == c_stone then
|
||||||
|
print("is stone!")
|
||||||
|
end
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
It is recommended that you find out and store the content IDs of nodes types
|
||||||
|
uring load time, as the IDs of a node type will never change. Make sure to store
|
||||||
|
the IDs in a local for performance reasons.
|
||||||
|
|
||||||
|
Nodes in an LVM data are stored in reverse co-ordinate order, so you should
|
||||||
|
always iterate in the order of `z, y, x` like so:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
for z = min.z, max.z do
|
||||||
|
for y = min.y, max.y do
|
||||||
|
for x = min.x, max.x do
|
||||||
|
-- vi, voxel index, is a common variable name here
|
||||||
|
local vi = a:index(x, y, z)
|
||||||
|
if data[vi] == c_stone then
|
||||||
|
print("is stone!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
The reason for this touches computer architecture. Reading from RAM is rather
|
||||||
|
costly, so CPUs have multiple levels of caching. If the data a process requests
|
||||||
|
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
|
||||||
|
then a cache miss occurs so it'll fetch the data it needs from RAM. Any data
|
||||||
|
surrounding the requested data is also fetched and then replaces the data in the cache as
|
||||||
|
it's quite likely that the process will ask for data near there again. So a
|
||||||
|
good rule of optimisation is to iterate in a way that you read data one after
|
||||||
|
another, and avoid *memory thrashing*.
|
||||||
|
|
||||||
|
## Writing Nodes
|
||||||
|
|
||||||
|
First you need to set the new content ID in the data array:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
for z = min.z, max.z do
|
||||||
|
for y = min.y, max.y do
|
||||||
|
for x = min.x, max.x do
|
||||||
|
local vi = a:index(x, y, z)
|
||||||
|
if data[vi] == c_stone then
|
||||||
|
data[vi] = c_air
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
When you finished setting nodes in the LVM, you then need to upload the data
|
||||||
|
array to the engine:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
vm:set_data(data)
|
||||||
|
vm:write_to_map(data)
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
You then need to update lighting and other things:
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
vm:update_map()
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
and that's it!
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
{% highlight lua %}
|
||||||
|
-- Get content IDs during load time, and store into a local
|
||||||
|
local c_dirt = minetest.get_content_id("default:dirt")
|
||||||
|
local c_grass = minetest.get_content_id("default:dirt_with_grass")
|
||||||
|
|
||||||
|
local function grass_to_dirt(pos1, pos2)
|
||||||
|
-- Read data into LVM
|
||||||
|
local vm = minetest.get_voxel_manip()
|
||||||
|
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||||
|
local a = VoxelArea:new{
|
||||||
|
MinEdge = emin,
|
||||||
|
MaxEdge = emax
|
||||||
|
}
|
||||||
|
local data = vm:get_data()
|
||||||
|
|
||||||
|
-- Modify data
|
||||||
|
for z = min.z, max.z do
|
||||||
|
for y = min.y, max.y do
|
||||||
|
for x = min.x, max.x do
|
||||||
|
local vi = a:index(x, y, z)
|
||||||
|
if data[vi] == c_grass then
|
||||||
|
data[vi] = c_dirt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Write data
|
||||||
|
vm:set_data(data)
|
||||||
|
vm:write_to_map(data)
|
||||||
|
vm:update_map()
|
||||||
|
end
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
## Your Turn
|
||||||
|
|
||||||
|
* Create `replace_in_area(from, to, pos1, pos2)` which replaces all instances of
|
||||||
|
`from` with `to` in the area given, where from and to are node names.
|
||||||
|
* Make an LVM which causes mossy cobble to spread to nearby stone and cobble nodes.
|
||||||
|
Does your implementation cause mossy cobble to spread more than a distance of one each
|
||||||
|
time? How could you stop this?
|
Loading…
Reference in New Issue
Block a user