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
|
||||
|
||||
### Jekyll ###
|
||||
|
@ -39,58 +39,62 @@
|
||||
num: 8
|
||||
link: chapters/node_metadata.html
|
||||
|
||||
- title: Lua Voxel Manipulators
|
||||
num: 9
|
||||
link: chapters/lvm.html
|
||||
|
||||
- hr: true
|
||||
|
||||
- title: Privileges
|
||||
num: 9
|
||||
num: 10
|
||||
link: chapters/privileges.html
|
||||
|
||||
- title: Chat and Commands
|
||||
num: 10
|
||||
num: 11
|
||||
link: chapters/chat.html
|
||||
|
||||
- title: Chat Command Builder
|
||||
num: 11
|
||||
num: 12
|
||||
link: chapters/chat_complex.html
|
||||
|
||||
- title: Player Physics
|
||||
num: 12
|
||||
num: 13
|
||||
link: chapters/player_physics.html
|
||||
|
||||
- title: Formspecs
|
||||
num: 13
|
||||
num: 14
|
||||
link: chapters/formspecs.html
|
||||
|
||||
- title: HUD
|
||||
num: 14
|
||||
num: 15
|
||||
link: chapters/hud.html
|
||||
|
||||
- title: "SFINV: Inventory Formspec"
|
||||
num: 15
|
||||
num: 16
|
||||
link: chapters/sfinv.html
|
||||
|
||||
- hr: true
|
||||
|
||||
- title: ItemStacks
|
||||
num: 16
|
||||
num: 17
|
||||
link: chapters/itemstacks.html
|
||||
|
||||
- title: Inventories
|
||||
num: 17
|
||||
num: 18
|
||||
link: chapters/inventories.html
|
||||
|
||||
- hr: true
|
||||
|
||||
- title: Automatic Error Checking
|
||||
num: 18
|
||||
num: 19
|
||||
link: chapters/luacheck.html
|
||||
|
||||
- title: Releasing a Mod
|
||||
num: 19
|
||||
num: 20
|
||||
link: chapters/releasing.html
|
||||
|
||||
- title: Read More
|
||||
num: 20
|
||||
num: 21
|
||||
link: chapters/readmore.html
|
||||
|
||||
- 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