voxelands/src/mapblock_mesh.cpp

800 lines
20 KiB
C++

/************************************************************************
* Minetest-c55
* Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
*
* mapblock_mesh.cpp
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2014 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* License updated from GPLv2 or later to GPLv3 or later by Lisa Milne
* for Voxelands.
************************************************************************/
#include "common.h"
#include "config.h"
#include "mapblock_mesh.h"
#include "light.h"
#include "mapblock.h"
#include "map.h"
#include "main.h" // For g_texturesource
#include "content_mapblock.h"
#include "content_nodemeta.h"
#include "profiler.h"
#include "mesh.h"
#include "base64.h"
#include "sound.h"
void MeshMakeData::fill(u32 daynight_ratio, MapBlock *block)
{
m_daynight_ratio = daynight_ratio;
if (!block)
return;
m_blockpos = block->getPos();
if (m_env)
m_vmanip.m_env = m_env;
m_blockpos_nodes = m_blockpos*MAP_BLOCKSIZE;
/*
Copy data
*/
// Allocate this block + neighbors
m_vmanip.clear();
m_vmanip.addArea(VoxelArea(m_blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE,
m_blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)));
{
//TimeTaker timer("copy central block data");
// 0ms
// Copy our data
block->copyTo(m_vmanip);
}
{
//TimeTaker timer("copy neighbor block data");
// 0ms
/*
Copy neighbors. This is lightning fast.
Copying only the borders would be *very* slow.
*/
// Get map
Map *map = block->getParent();
for(u16 i=0; i<6; i++)
{
const v3s16 &dir = g_6dirs[i];
v3s16 bp = m_blockpos + dir;
MapBlock *b = map->getBlockNoCreateNoEx(bp);
if(b)
b->copyTo(m_vmanip);
}
}
}
video::SColor blend_light(u32 data, u32 daylight_factor)
{
u8 type = (data>>24)&0xFF;
u8 a = 255;
u8 r = 0;
u8 g = 0;
u8 b = 0;
if (type < 2) {
u8 light = data&0xFF;
if (type == 1)
a = (data>>8)&0xFF;
u8 d = light&0x0F;
u8 n = (light>>4)&0x0F;
u8 l = ((daylight_factor * d + (1000-daylight_factor) * n) )/1000;
u8 max = LIGHT_MAX;
if (d == LIGHT_SUN)
max = LIGHT_SUN;
if (l > max)
l = max;
l = decode_light(l);
r = l;
g = l;
b = l;
if (l <= 80)
b = MYMAX(0, pow((float)l/80.0, 0.8)*80.0);
}else{
a = type;
r = (data>>16)&0xFF;
g = (data>>8)&0xFF;
b = data&0xFF;
}
return video::SColor(a,r,g,b);
}
std::string getGrassTile(u8 p2, std::string base, std::string overlay)
{
std::string tex = base+"^"+overlay;
if (p2) {
u8 p = p2&0xF0;
u8 g = p2&0x0F;
if (g < 15) {
std::string texture_name = base;
if (p == 0) {
float v = (float)(g+1)*0.03125;
texture_name += "^[blit:";
texture_name += ftos(0.5-v);
texture_name += ",";
texture_name += ftos(0.5-v);
texture_name += ",";
texture_name += ftos(0.5+v);
texture_name += ",";
texture_name += ftos(0.5+v);
texture_name += ",";
texture_name += overlay;
}else{
float v = (float)g*0.0625;
if ((p&(1<<7)) != 0) { // -Z
texture_name += "^[blit:0,";
texture_name += ftos(1.0-v);
texture_name += ",1,1,";
texture_name += overlay;
}
if ((p&(1<<6)) != 0) { // +Z
texture_name += "^[blit:0,0,1,";
texture_name += ftos(v);
texture_name += ",";
texture_name += overlay;
}
if ((p&(1<<5)) != 0) { // -X
texture_name += "^[blit:0,0,";
texture_name += ftos(v);
texture_name += ",1,";
texture_name += overlay;
}
if ((p&(1<<4)) != 0) { // +X
texture_name += "^[blit:";
texture_name += ftos(1.0-v);
texture_name += ",0,1,1,";
texture_name += overlay;
}
}
tex = texture_name;
}
}
return tex;
}
TileSpec getCrackTile(TileSpec spec, SelectedNode &select)
{
/*
apply crack to this node
*/
if (select.has_crack) {
/*
Get texture id, translate it to name, append stuff to
name, get texture id
*/
// Get original texture name
u32 orig_id = spec.texture.id;
std::string orig_name = g_texturesource->getTextureName(orig_id);
// Create new texture name
std::ostringstream os;
os<<orig_name<<"^[crack"<<select.crack;
// Get new texture
u32 new_id = g_texturesource->getTextureId(os.str());
spec.texture = g_texturesource->getTexture(new_id);
}
return spec;
}
/*
Gets node tile from any place relative to block.
Returns TILE_NODE if doesn't exist or should not be drawn.
*/
TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 face_dir, SelectedNode &select, NodeMetadata *meta)
{
TileSpec spec;
spec = mn.getTile(face_dir,false);
ContentFeatures *f = &content_features(mn);
if (meta) {
FaceText ft = mn.getFaceText(face_dir);
if (ft.m_hastext) {
std::string txt("");
switch (ft.m_type) {
case FTT_BOOKCONTENT:
txt = ((BookNodeMetadata*)meta)->getContent();
break;
case FTT_OWNER:
txt = meta->getOwner();
break;
case FTT_INVOWNER:
txt = meta->getInventoryOwner();
break;
default:
txt = meta->getText();
break;
}
if (txt != "") {
// Get original texture name
u32 orig_id = spec.texture.id;
std::string orig_name = g_texturesource->getTextureName(orig_id);
// Create new texture name
std::ostringstream os;
os<<orig_name<<"^[text:";
os<<ft.m_pos.UpperLeftCorner.X;
os<<",";
os<<ft.m_pos.UpperLeftCorner.Y;
os<<",";
os<<ft.m_pos.LowerRightCorner.X;
os<<",";
os<<ft.m_pos.LowerRightCorner.Y;
os<<",";
os<<base64_encode((const unsigned char*)txt.c_str(),txt.size());
// Get new texture
u32 new_id = g_texturesource->getTextureId(os.str());
spec.texture = g_texturesource->getTexture(new_id);
}
}
}
if (f->draw_type == CDT_PLANTLIKE && f->plantgrowth_on_trellis) {
if (!select.is_coloured && !select.has_crack) {
// Get original texture name
u32 orig_id = spec.texture.id;
std::string orig_name = g_texturesource->getTextureName(orig_id);
std::string texture_name("trellis.png");
if (f->param2_type != CPT_PLANTGROWTH || !mn.param2) {
texture_name += "^"+orig_name;
}else{
std::string bs("^[blit:0,");
bs += ftos(1.0-(0.0625*(float)mn.param2));
bs += ",1,1,";
// new name
texture_name += bs+orig_name;
}
// Get new texture
u32 new_id = g_texturesource->getTextureId(texture_name);
spec.texture = g_texturesource->getTexture(new_id);
}
}else if (f->draw_type == CDT_CUBELIKE && f->param2_type == CPT_PLANTGROWTH && face_dir.Y == 1) {
TileSpec bspec = spec;
spec = mn.getTile(v3s16(0,-1,0),false);
if (mn.param2) {
u8 p = mn.param2&0xF0;
u8 g = mn.param2&0x0F;
if (g < 15) {
u32 orig_id = bspec.texture.id;
std::string blit_name = g_texturesource->getTextureName(orig_id);
std::string orig_name = g_texturesource->getTextureName(spec.texture.id);
std::string texture_name = orig_name;
if (p == 0) {
float v = (float)(g+1)*0.03125;
texture_name += "^[blit:";
texture_name += ftos(0.5-v);
texture_name += ",";
texture_name += ftos(0.5-v);
texture_name += ",";
texture_name += ftos(0.5+v);
texture_name += ",";
texture_name += ftos(0.5+v);
texture_name += ",";
texture_name += blit_name;
}else{
float v = (float)g*0.0625;
if ((p&(1<<7)) != 0) { // -Z
texture_name += "^[blit:0,";
texture_name += ftos(1.0-v);
texture_name += ",1,1,";
texture_name += blit_name;
}
if ((p&(1<<6)) != 0) { // +Z
texture_name += "^[blit:0,0,1,";
texture_name += ftos(v);
texture_name += ",";
texture_name += blit_name;
}
if ((p&(1<<5)) != 0) { // -X
texture_name += "^[blit:0,0,";
texture_name += ftos(v);
texture_name += ",1,";
texture_name += blit_name;
}
if ((p&(1<<4)) != 0) { // +X
texture_name += "^[blit:";
texture_name += ftos(1.0-v);
texture_name += ",0,1,1,";
texture_name += blit_name;
}
}
// Get new texture
u32 new_id = g_texturesource->getTextureId(texture_name);
spec.texture = g_texturesource->getTexture(new_id);
}else{
spec = bspec;
}
}
}
std::string rot = mn.getTileRotationString(face_dir);
if (rot != "") {
// Get original texture name
u32 orig_id = spec.texture.id;
std::string orig_name = g_texturesource->getTextureName(orig_id);
// new name
std::string texture_name = orig_name + rot;
// Get new texture
u32 new_id = g_texturesource->getTextureId(texture_name);
spec.texture = g_texturesource->getTexture(new_id);
}
/*
apply crack to this node
*/
if (select.has_crack) {
/*
Get texture id, translate it to name, append stuff to
name, get texture id
*/
// Get original texture name
u32 orig_id = spec.texture.id;
std::string orig_name = g_texturesource->getTextureName(orig_id);
// Create new texture name
std::ostringstream os;
os<<orig_name<<"^[crack"<<select.crack;
// Get new texture
u32 new_id = g_texturesource->getTextureId(os.str());
spec.texture = g_texturesource->getTexture(new_id);
}
return spec;
}
/*
Gets node meta tile from any place relative to block.
Returns TILE_NODE if doesn't exist or should not be drawn.
*/
TileSpec getMetaTile(MapNode mn, v3s16 p, v3s16 face_dir, SelectedNode &select)
{
TileSpec spec;
spec = mn.getMetaTile(face_dir);
return spec;
}
static const v3s16 dirs8[8] = {
v3s16(0,0,0),
v3s16(0,0,1),
v3s16(0,1,0),
v3s16(0,1,1),
v3s16(1,0,0),
v3s16(1,1,0),
v3s16(1,0,1),
v3s16(1,1,1),
};
// Calculate lighting at the given corner of p
u8 getSmoothLight(v3s16 p, v3s16 corner, VoxelManipulator &vmanip)
{
float ambient_occlusion = 0;
float dl = 0;
float nl = 0;
u16 light_count = 0;
if (corner.X == 1)
p.X += 1;
if (corner.Y == 1)
p.Y += 1;
if (corner.Z == 1)
p.Z += 1;
for (u8 i = 0; i < 8; i++) {
MapNode n = vmanip.getNodeRO(p - dirs8[i]);
ContentFeatures &f = content_features(n);
if (f.param_type == CPT_LIGHT) {
dl += n.getLight(LIGHTBANK_DAY);
nl += n.getLight(LIGHTBANK_NIGHT);
light_count++;
if (f.light_source > 0)
ambient_occlusion -= 1.0;
} else if (f.draw_type == CDT_CUBELIKE || f.draw_type == CDT_DIRTLIKE) {
ambient_occlusion += 0.5;
} else if (n.getContent() != CONTENT_IGNORE) {
ambient_occlusion += 0.25;
}
}
if (light_count == 0)
return 0;
dl /= light_count;
nl /= light_count;
if (ambient_occlusion > 2.0) {
ambient_occlusion = (ambient_occlusion-2) * 0.4 + 1.0;
dl /= ambient_occlusion;
nl /= ambient_occlusion;
}
u8 idl;
u8 inl;
if (dl >= LIGHT_SUN) {
idl = LIGHT_SUN;
}else{
idl = ceilf(dl);
}
inl = ceilf(nl);
return ((inl<<4)&0xF0)|(idl&0x0F);;
}
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
m_mesh(NULL),
m_farmesh(NULL),
m_camera_offset(camera_offset)
{
generate(data,camera_offset,NULL);
}
MapBlockMesh::~MapBlockMesh()
{
m_mesh->drop();
m_mesh = NULL;
m_farmesh->drop();
m_farmesh = NULL;
if (!m_animation_data.empty())
m_animation_data.clear();
}
void MapBlockMesh::animate(float time)
{
if (!m_mesh)
return;
for (std::map<u32, AnimationData>::iterator it = m_animation_data.begin();
it != m_animation_data.end(); ++it) {
// Make sure we don't cause an overflow
if (it->first >= m_mesh->getMeshBufferCount())
return;
const TileSpec &tile = it->second.tile;
// Figure out current frame
int frame = 0;
if (time > 0.0)
frame = (int)(time * 1000 / tile.animation_frame_length_ms) % tile.animation_frame_count;
// If frame doesn't change, skip
if (frame == it->second.frame)
continue;
m_animation_data[it->first].frame = frame;
u16 mc = m_mesh->getMeshBufferCount();
if (mc <= it->first)
continue;
scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(it->first);
// Create new texture name from original
if (g_texturesource && frame >= 0) {
std::ostringstream os(std::ios::binary);
os << g_texturesource->getTextureName(tile.texture.id);
os << "^[verticalframe:" << (int)tile.animation_frame_count << ":" << frame;
// Set the texture
AtlasPointer ap = g_texturesource->getTexture(os.str());
buf->getMaterial().setTexture(0, ap.atlas);
}
}
}
void MapBlockMesh::generate(MeshMakeData *data, v3s16 camera_offset, JMutex *mutex)
{
DSTACK(__FUNCTION_NAME);
data->mesh_detail = config_get_int("client.graphics.mesh.lod");
data->texture_detail = config_get_int("client.graphics.texture.lod");
data->light_detail = config_get_int("client.graphics.light.lod");
m_pos = data->m_blockpos;
SelectedNode selected;
if (!m_animation_data.empty())
m_animation_data.clear();
for(s16 z=0; z<MAP_BLOCKSIZE; z++)
for(s16 y=0; y<MAP_BLOCKSIZE; y++)
for(s16 x=0; x<MAP_BLOCKSIZE; x++)
{
v3s16 p(x,y,z);
MapNode n = data->m_vmanip.getNodeNoEx(data->m_blockpos_nodes+p);
#if USE_AUDIO == 1
if (data->m_sounds != NULL) {
std::string snd = content_features(n).sound_ambient;
std::map<v3s16,MapBlockSound>::iterator i = data->m_sounds->find(p);
if (snd != "") {
bool add_sound = true;
if (i != data->m_sounds->end()) {
if (i->second.name == snd && sound_exists(i->second.id)) {
add_sound = false;
}else{
sound_stop_single(i->second.id);
}
}
if (add_sound && content_features(n).liquid_type != LIQUID_NONE) {
if (data->m_vmanip.getNodeRO(data->m_blockpos_nodes+p+v3s16(0,1,0)).getContent() != CONTENT_AIR) {
add_sound = false;
}else if (content_features(n).param2_type != CPT_LIQUID || n.param2 < 4 || n.param2 > 7) {
add_sound = false;
}else{
int adj = 0;
for (s16 x=-1; x<2; x++) {
for (s16 z=-1; z<2; z++) {
if (!x && !z)
continue;
content_t ac = data->m_vmanip.getNodeRO(data->m_blockpos_nodes+p+v3s16(x,0,z)).getContent();
if (
ac == content_features(n).liquid_alternative_flowing
|| ac == content_features(n).liquid_alternative_source
)
adj++;
}
}
if (adj > 3)
add_sound = false;
}
}
if (add_sound) {
v3f pf = intToFloat(p+data->m_blockpos_nodes,BS);
v3_t vp = {pf.X,pf.Y,pf.Z};
MapBlockSound bsnd;
bsnd.id = sound_play_effect((char*)snd.c_str(),1.0,1,&vp);
bsnd.name = snd;
if (bsnd.id > 0)
(*data->m_sounds)[p] = bsnd;
}
}else if (i != data->m_sounds->end()) {
sound_stop_single(i->second.id);
data->m_sounds->erase(i);
}
}
#endif
if (data->light_detail > 1 && !selected.is_coloured)
meshgen_preset_smooth_lights(data,p);
switch (content_features(n).draw_type) {
case CDT_AIRLIKE:
break;
case CDT_CUBELIKE:
meshgen_cubelike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_DIRTLIKE:
meshgen_dirtlike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_RAILLIKE:
meshgen_raillike(data,p,n,selected);
break;
case CDT_PLANTLIKE:
meshgen_plantlike(data,p,n,selected);
break;
case CDT_PLANTLIKE_FERN:
meshgen_plantlike_fern(data,p,n,selected);
break;
case CDT_CROPLIKE:
meshgen_croplike(data,p,n,selected);
break;
case CDT_LIQUID:
meshgen_liquid(data,p,n,selected);
break;
case CDT_LIQUID_SOURCE:
meshgen_liquid_source(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_NODEBOX:
meshgen_nodebox(data,p,n,selected,false);
break;
case CDT_GLASSLIKE:
meshgen_glasslike(data,p,n,selected);
break;
case CDT_TORCHLIKE:
meshgen_torchlike(data,p,n,selected);
break;
case CDT_FENCELIKE:
meshgen_fencelike(data,p,n,selected);
break;
case CDT_FIRELIKE:
meshgen_firelike(data,p,n,selected);
break;
case CDT_WALLLIKE:
meshgen_walllike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_ROOFLIKE:
meshgen_rooflike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_LEAFLIKE:
meshgen_leaflike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_NODEBOX_META:
meshgen_nodebox(data,p,n,selected,true);
break;
case CDT_WIRELIKE:
meshgen_wirelike(data,p,n,selected,false);
break;
case CDT_3DWIRELIKE:
meshgen_wirelike(data,p,n,selected,true);
break;
case CDT_STAIRLIKE:
meshgen_stairlike(data,p,n,selected);
break;
case CDT_SLABLIKE:
meshgen_slablike(data,p,n,selected);
break;
case CDT_TRUNKLIKE:
meshgen_trunklike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_FLAGLIKE:
meshgen_flaglike(data,p,n,selected);
break;
case CDT_MELONLIKE:
meshgen_melonlike(data,p,n,selected);
meshgen_farnode(data,p,n);
break;
case CDT_CAMPFIRELIKE:
meshgen_campfirelike(data,p,n,selected);
break;
case CDT_BUSHLIKE:
meshgen_bushlike(data,p,n,selected);
break;
default:;
}
}
scene::SMesh *mesh = new scene::SMesh();
scene::SMesh *fmesh = new scene::SMesh();
for (u32 i=0; i<data->m_meshdata.size(); i++) {
MeshData &d = data->m_meshdata[i];
// - Texture animation
if (d.tile.material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) {
// Add to MapBlockMesh in order to animate these tiles
AnimationData anim_data;
anim_data.tile = d.tile;
anim_data.frame = -1;
m_animation_data[i] = anim_data;
}
// Create meshbuffer
// This is a "Standard MeshBuffer",
// it's a typedeffed CMeshBuffer<video::S3DVertex>
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
// Set material
buf->Material = d.tile.getMaterial();
// Add to mesh
mesh->addMeshBuffer(buf);
// Mesh grabbed it
buf->drop();
buf->append(d.vertices.data(), d.vertices.size(), d.indices.data(), d.indices.size());
}
for (u32 i=0; i<data->m_fardata.size(); i++) {
MeshData &d = data->m_fardata[i];
// Create meshbuffer
// This is a "Standard MeshBuffer",
// it's a typedeffed CMeshBuffer<video::S3DVertex>
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
// Set material
buf->Material = d.tile.getMaterial();
// Add to mesh
fmesh->addMeshBuffer(buf);
// Mesh grabbed it
buf->drop();
buf->append(d.vertices.data(), d.vertices.size(), d.indices.data(), d.indices.size());
}
translateMesh(mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));
translateMesh(fmesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));
if (mutex != NULL)
mutex->Lock();
if (m_mesh != NULL)
m_mesh->drop();
if (m_farmesh != NULL)
m_farmesh->drop();
m_mesh = mesh;
m_farmesh = fmesh;
m_meshdata.swap(data->m_meshdata);
m_fardata.swap(data->m_fardata);
refresh(data->m_daynight_ratio);
m_mesh->recalculateBoundingBox();
if (mutex != NULL)
mutex->Unlock();
// Get frist frame of animation AFTER the mutex is unlocked
animate(0.0);
}
void MapBlockMesh::refresh(u32 daynight_ratio)
{
if (m_mesh == NULL)
return;
u16 mc = m_mesh->getMeshBufferCount();
for (u16 j=0; j<mc; j++) {
scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(j);
if (buf == 0)
continue;
u16 vc = buf->getVertexCount();
if (!vc)
continue;
video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
if (vertices == 0)
continue;
u32 *c = m_meshdata[j].colours.data();
for (u16 i=0; i<vc; i++) {
vertices[i].Color = blend_light(c[i],daynight_ratio);
}
}
mc = m_farmesh->getMeshBufferCount();
for (u16 j=0; j<mc; j++) {
scene::IMeshBuffer *buf = m_farmesh->getMeshBuffer(j);
if (buf == 0)
continue;
u16 vc = buf->getVertexCount();
if (!vc)
continue;
video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
if (vertices == 0)
continue;
for (u16 i=0; i<vc; i++) {
vertices[i].Color = blend_light(0x0F,daynight_ratio);
}
}
}
void MapBlockMesh::updateCameraOffset(v3s16 camera_offset)
{
if (camera_offset != m_camera_offset) {
translateMesh(m_mesh, intToFloat(m_camera_offset-camera_offset, BS));
translateMesh(m_farmesh, intToFloat(m_camera_offset-camera_offset, BS));
m_camera_offset = camera_offset;
}
}