aboutsummaryrefslogtreecommitdiff
path: root/src/itemdef.cpp
blob: 5fb1e4c470019df5a9921977d107da0973c22c6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Kahrl <kahrl@gmx.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "itemdef.h"

#include "nodedef.h"
#include "tool.h"
#include "inventory.h"
#ifndef SERVER
#include "client/mapblock_mesh.h"
#include "client/mesh.h"
#include "client/wieldmesh.h"
#include "client/tile.h"
#include "client/client.h"
#endif
#include "log.h"
#include "settings.h"
#include "util/serialize.h"
#include "util/container.h"
#include "util/thread.h"
#include <map>
#include <set>

/*
	ItemDefinition
*/
ItemDefinition::ItemDefinition()
{
	resetInitial();
}

ItemDefinition::ItemDefinition(const ItemDefinition &def)
{
	resetInitial();
	*this = def;
}

ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
{
	if(this == &def)
		return *this;

	reset();

	type = def.type;
	name = def.name;
	description = def.description;
	short_description = def.short_description;
	inventory_image = def.inventory_image;
	inventory_overlay = def.inventory_overlay;
	wield_image = def.wield_image;
	wield_overlay = def.wield_overlay;
	wield_scale = def.wield_scale;
	stack_max = def.stack_max;
	usable = def.usable;
	liquids_pointable = def.liquids_pointable;
	if(def.tool_capabilities)
	{
		tool_capabilities = new ToolCapabilities(
				*def.tool_capabilities);
	}
	groups = def.groups;
	node_placement_prediction = def.node_placement_prediction;
	sound_place = def.sound_place;
	sound_place_failed = def.sound_place_failed;
	range = def.range;
	palette_image = def.palette_image;
	color = def.color;
	return *this;
}

ItemDefinition::~ItemDefinition()
{
	reset();
}

void ItemDefinition::resetInitial()
{
	// Initialize pointers to NULL so reset() does not delete undefined pointers
	tool_capabilities = NULL;
	reset();
}

void ItemDefinition::reset()
{
	type = ITEM_NONE;
	name = "";
	description = "";
	short_description = "";
	inventory_image = "";
	inventory_overlay = "";
	wield_image = "";
	wield_overlay = "";
	palette_image = "";
	color = video::SColor(0xFFFFFFFF);
	wield_scale = v3f(1.0, 1.0, 1.0);
	stack_max = 99;
	usable = false;
	liquids_pointable = false;
	delete tool_capabilities;
	tool_capabilities = NULL;
	groups.clear();
	sound_place = SimpleSoundSpec();
	sound_place_failed = SimpleSoundSpec();
	range = -1;

	node_placement_prediction = "";
}

void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
{
	// protocol_version >= 37
	u8 version = 6;
	writeU8(os, version);
	writeU8(os, type);
	os << serializeString16(name);
	os << serializeString16(description);
	os << serializeString16(inventory_image);
	os << serializeString16(wield_image);
	writeV3F32(os, wield_scale);
	writeS16(os, stack_max);
	writeU8(os, usable);
	writeU8(os, liquids_pointable);

	std::string tool_capabilities_s;
	if (tool_capabilities) {
		std::ostringstream tmp_os(std::ios::binary);
		tool_capabilities->serialize(tmp_os, protocol_version);
		tool_capabilities_s = tmp_os.str();
	}
	os << serializeString16(tool_capabilities_s);

	writeU16(os, groups.size());
	for (const auto &group : groups) {
		os << serializeString16(group.first);
		writeS16(os, group.second);
	}

	os << serializeString16(node_placement_prediction);

	// Version from ContentFeatures::serialize to keep in sync
	sound_place.serialize(os, CONTENTFEATURES_VERSION);
	sound_place_failed.serialize(os, CONTENTFEATURES_VERSION);

	writeF32(os, range);
	os << serializeString16(palette_image);
	writeARGB8(os, color);
	os << serializeString16(inventory_overlay);
	os << serializeString16(wield_overlay);

	os << serializeString16(short_description);
}

void ItemDefinition::deSerialize(std::istream &is)
{
	// Reset everything
	reset();

	// Deserialize
	int version = readU8(is);
	if (version < 6)
		throw SerializationError("unsupported ItemDefinition version");

	type = (enum ItemType)readU8(is);
	name = deSerializeString16(is);
	description = deSerializeString16(is);
	inventory_image = deSerializeString16(is);
	wield_image = deSerializeString16(is);
	wield_scale = readV3F32(is);
	stack_max = readS16(is);
	usable = readU8(is);
	liquids_pointable = readU8(is);

	std::string tool_capabilities_s = deSerializeString16(is);
	if (!tool_capabilities_s.empty()) {
		std::istringstream tmp_is(tool_capabilities_s, std::ios::binary);
		tool_capabilities = new ToolCapabilities;
		tool_capabilities->deSerialize(tmp_is);
	}

	groups.clear();
	u32 groups_size = readU16(is);
	for(u32 i=0; i<groups_size; i++){
		std::string name = deSerializeString16(is);
		int value = readS16(is);
		groups[name] = value;
	}

	node_placement_prediction = deSerializeString16(is);

	// Version from ContentFeatures::serialize to keep in sync
	sound_place.deSerialize(is, CONTENTFEATURES_VERSION);
	sound_place_failed.deSerialize(is, CONTENTFEATURES_VERSION);

	range = readF32(is);
	palette_image = deSerializeString16(is);
	color = readARGB8(is);
	inventory_overlay = deSerializeString16(is);
	wield_overlay = deSerializeString16(is);

	// If you add anything here, insert it primarily inside the try-catch
	// block to not need to increase the version.
	try {
		short_description = deSerializeString16(is);
	} catch(SerializationError &e) {};
}


/*
	CItemDefManager
*/

// SUGG: Support chains of aliases?

class CItemDefManager: public IWritableItemDefManager
{
#ifndef SERVER
	struct ClientCached
	{
		video::ITexture *inventory_texture;
		ItemMesh wield_mesh;
		Palette *palette;

		ClientCached():
			inventory_texture(NULL),
			palette(NULL)
		{}
	};
#endif

public:
	CItemDefManager()
	{

#ifndef SERVER
		m_main_thread = std::this_thread::get_id();
#endif
		clear();
	}
	virtual ~CItemDefManager()
	{
#ifndef SERVER
		const std::vector<ClientCached*> &values = m_clientcached.getValues();
		for (ClientCached *cc : values) {
			if (cc->wield_mesh.mesh)
				cc->wield_mesh.mesh->drop();
			delete cc;
		}

#endif
		for (auto &item_definition : m_item_definitions) {
			delete item_definition.second;
		}
		m_item_definitions.clear();
	}
	virtual const ItemDefinition& get(const std::string &name_) const
	{
		// Convert name according to possible alias
		std::string name = getAlias(name_);
		// Get the definition
		auto i = m_item_definitions.find(name);
		if (i == m_item_definitions.cend())
			i = m_item_definitions.find("unknown");
		assert(i != m_item_definitions.cend());
		return *(i->second);
	}
	virtual const std::string &getAlias(const std::string &name) const
	{
		auto it = m_aliases.find(name);
		if (it != m_aliases.cend())
			return it->second;
		return name;
	}
	virtual void getAll(std::set<std::string> &result) const
	{
		result.clear();
		for (const auto &item_definition : m_item_definitions) {
			result.insert(item_definition.first);
		}

		for (const auto &alias : m_aliases) {
			result.insert(alias.first);
		}
	}
	virtual bool isKnown(const std::string &name_) const
	{
		// Convert name according to possible alias
		std::string name = getAlias(name_);
		// Get the definition
		return m_item_definitions.find(name) != m_item_definitions.cend();
	}
#ifndef SERVER
public:
	ClientCached* createClientCachedDirect(const std::string &name,
			Client *client) const
	{
		infostream<<"Lazily creating item texture and mesh for \""
				<<name<<"\""<<std::endl;

		// This is not thread-safe
		sanity_check(std::this_thread::get_id() == m_main_thread);

		// Skip if already in cache
		ClientCached *cc = NULL;
		m_clientcached.get(name, &cc);
		if(cc)
			return cc;

		ITextureSource *tsrc = client->getTextureSource();
		const ItemDefinition &def = get(name);

		// Create new ClientCached
		cc = new ClientCached();

		// Create an inventory texture
		cc->inventory_texture = NULL;
		if (!def.inventory_image.empty())
			cc->inventory_texture = tsrc->getTexture(def.inventory_image);

		ItemStack item = ItemStack();
		item.name = def.name;

		getItemMesh(client, item, &(cc->wield_mesh));

		cc->palette = tsrc->getPalette(def.palette_image);

		// Put in cache
		m_clientcached.set(name, cc);

		return cc;
	}
	ClientCached* getClientCached(const std::string &name,
			Client *client) const
	{
		ClientCached *cc = NULL;
		m_clientcached.get(name, &cc);
		if (cc)
			return cc;

		if (std::this_thread::get_id() == m_main_thread) {
			return createClientCachedDirect(name, client);
		}

		// We're gonna ask the result to be put into here
		static ResultQueue<std::string, ClientCached*, u8, u8> result_queue;

		// Throw a request in
		m_get_clientcached_queue.add(name, 0, 0, &result_queue);
		try {
			while(true) {
				// Wait result for a second
				GetResult<std::string, ClientCached*, u8, u8>
					result = result_queue.pop_front(1000);

				if (result.key == name) {
					return result.item;
				}
			}
		} catch(ItemNotFoundException &e) {
			errorstream << "Waiting for clientcached " << name
				<< " timed out." << std::endl;
			return &m_dummy_clientcached;
		}
	}
	// Get item inventory texture
	virtual video::ITexture* getInventoryTexture(const std::string &name,
			Client *client) const
	{
		ClientCached *cc = getClientCached(name, client);
		if(!cc)
			return NULL;
		return cc->inventory_texture;
	}
	// Get item wield mesh
	virtual ItemMesh* getWieldMesh(const std::string &name,
			Client *client) const
	{
		ClientCached *cc = getClientCached(name, client);
		if(!cc)
			return NULL;
		return &(cc->wield_mesh);
	}

	// Get item palette
	virtual Palette* getPalette(const std::string &name,
			Client *client) const
	{
		ClientCached *cc = getClientCached(name, client);
		if(!cc)
			return NULL;
		return cc->palette;
	}

	virtual video::SColor getItemstackColor(const ItemStack &stack,
		Client *client) const
	{
		// Look for direct color definition
		const std::string &colorstring = stack.metadata.getString("color", 0);
		video::SColor directcolor;
		if (!colorstring.empty() && parseColorString(colorstring, directcolor, true))
			return directcolor;
		// See if there is a palette
		Palette *palette = getPalette(stack.name, client);
		const std::string &index = stack.metadata.getString("palette_index", 0);
		if (palette && !index.empty())
			return (*palette)[mystoi(index, 0, 255)];
		// Fallback color
		return get(stack.name).color;
	}
#endif
	void applyTextureOverrides(const std::vector<TextureOverride> &overrides)
	{
		infostream << "ItemDefManager::applyTextureOverrides(): Applying "
			"overrides to textures" << std::endl;

		for (const TextureOverride& texture_override : overrides) {
			if (m_item_definitions.find(texture_override.id) == m_item_definitions.end()) {
				continue; // Ignore unknown item
			}

			ItemDefinition* itemdef = m_item_definitions[texture_override.id];

			if (texture_override.hasTarget(OverrideTarget::INVENTORY))
				itemdef->inventory_image = texture_override.texture;

			if (texture_override.hasTarget(OverrideTarget::WIELD))
				itemdef->wield_image = texture_override.texture;
		}
	}
	void clear()
	{
		for (auto &i : m_item_definitions)
		{
			delete i.second;
		}
		m_item_definitions.clear();
		m_aliases.clear();

		// Add the four builtin items:
		//   "" is the hand
		//   "unknown" is returned whenever an undefined item
		//     is accessed (is also the unknown node)
		//   "air" is the air node
		//   "ignore" is the ignore node

		ItemDefinition* hand_def = new ItemDefinition;
		hand_def->name = "";
		hand_def->wield_image = "wieldhand.png";
		hand_def->tool_capabilities = new ToolCapabilities;
		m_item_definitions.insert(std::make_pair("", hand_def));

		ItemDefinition* unknown_def = new ItemDefinition;
		unknown_def->type = ITEM_NODE;
		unknown_def->name = "unknown";
		m_item_definitions.insert(std::make_pair("unknown", unknown_def));

		ItemDefinition* air_def = new ItemDefinition;
		air_def->type = ITEM_NODE;
		air_def->name = "air";
		m_item_definitions.insert(std::make_pair("air", air_def));

		ItemDefinition* ignore_def = new ItemDefinition;
		ignore_def->type = ITEM_NODE;
		ignore_def->name = "ignore";
		m_item_definitions.insert(std::make_pair("ignore", ignore_def));
	}
	virtual void registerItem(const ItemDefinition &def)
	{
		TRACESTREAM(<< "ItemDefManager: registering " << def.name << std::endl);
		// Ensure that the "" item (the hand) always has ToolCapabilities
		if (def.name.empty())
			FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities");

		if(m_item_definitions.count(def.name) == 0)
			m_item_definitions[def.name] = new ItemDefinition(def);
		else
			*(m_item_definitions[def.name]) = def;

		// Remove conflicting alias if it exists
		bool alias_removed = (m_aliases.erase(def.name) != 0);
		if(alias_removed)
			infostream<<"ItemDefManager: erased alias "<<def.name
					<<" because item was defined"<<std::endl;
	}
	virtual void unregisterItem(const std::string &name)
	{
		verbosestream<<"ItemDefManager: unregistering \""<<name<<"\""<<std::endl;

		delete m_item_definitions[name];
		m_item_definitions.erase(name);
	}
	virtual void registerAlias(const std::string &name,
			const std::string &convert_to)
	{
		if (m_item_definitions.find(name) == m_item_definitions.end()) {
			TRACESTREAM(<< "ItemDefManager: setting alias " << name
				<< " -> " << convert_to << std::endl);
			m_aliases[name] = convert_to;
		}
	}
	void serialize(std::ostream &os, u16 protocol_version)
	{
		writeU8(os, 0); // version
		u16 count = m_item_definitions.size();
		writeU16(os, count);

		for (const auto &it : m_item_definitions) {
			ItemDefinition *def = it.second;
			// Serialize ItemDefinition and write wrapped in a string
			std::ostringstream tmp_os(std::ios::binary);
			def->serialize(tmp_os, protocol_version);
			os << serializeString16(tmp_os.str());
		}

		writeU16(os, m_aliases.size());

		for (const auto &it : m_aliases) {
			os << serializeString16(it.first);
			os << serializeString16(it.second);
		}
	}
	void deSerialize(std::istream &is)
	{
		// Clear everything
		clear();
		// Deserialize
		int version = readU8(is);
		if(version != 0)
			throw SerializationError("unsupported ItemDefManager version");
		u16 count = readU16(is);
		for(u16 i=0; i<count; i++)
		{
			// Deserialize a string and grab an ItemDefinition from it
			std::istringstream tmp_is(deSerializeString16(is), std::ios::binary);
			ItemDefinition def;
			def.deSerialize(tmp_is);
			// Register
			registerItem(def);
		}
		u16 num_aliases = readU16(is);
		for(u16 i=0; i<num_aliases; i++)
		{
			std::string name = deSerializeString16(is);
			std::string convert_to = deSerializeString16(is);
			registerAlias(name, convert_to);
		}
	}
	void processQueue(IGameDef *gamedef)
	{
#ifndef SERVER
		//NOTE this is only thread safe for ONE consumer thread!
		while(!m_get_clientcached_queue.empty())
		{
			GetRequest<std::string, ClientCached*, u8, u8>
					request = m_get_clientcached_queue.pop();

			m_get_clientcached_queue.pushResult(request,
					createClientCachedDirect(request.key, (Client *)gamedef));
		}
#endif
	}
private:
	// Key is name
	std::map<std::string, ItemDefinition*> m_item_definitions;
	// Aliases
	StringMap m_aliases;
#ifndef SERVER
	// The id of the thread that is allowed to use irrlicht directly
	std::thread::id m_main_thread;
	// A reference to this can be returned when nothing is found, to avoid NULLs
	mutable ClientCached m_dummy_clientcached;
	// Cached textures and meshes
	mutable MutexedMap<std::string, ClientCached*> m_clientcached;
	// Queued clientcached fetches (to be processed by the main thread)
	mutable RequestQueue<std::string, ClientCached*, u8, u8> m_get_clientcached_queue;
#endif
};

IWritableItemDefManager* createItemDefManager()
{
	return new CItemDefManager();
}
n> rg -= (day - night) / 23; // Emphase blue a bit in darker places // Each entry of this array represents a range of 8 blue levels static const u8 emphase_blue_when_dark[32] = { 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; b += emphase_blue_when_dark[irr::core::clamp(b, 0, 255) / 8]; b = irr::core::clamp(b, 0, 255); // Artificial light is yellow-ish static const u8 emphase_yellow_when_artificial[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 15, 15, 15 }; rg += emphase_yellow_when_artificial[night/16]; rg = irr::core::clamp(rg, 0, 255); result.setRed(rg); result.setGreen(rg); result.setBlue(b); } /* Mesh generation helpers */ /* vertex_dirs: v3s16[4] */ static void getNodeVertexDirs(v3s16 dir, v3s16 *vertex_dirs) { /* If looked from outside the node towards the face, the corners are: 0: bottom-right 1: bottom-left 2: top-left 3: top-right */ if(dir == v3s16(0,0,1)) { // If looking towards z+, this is the face that is behind // the center point, facing towards z+. vertex_dirs[0] = v3s16(-1,-1, 1); vertex_dirs[1] = v3s16( 1,-1, 1); vertex_dirs[2] = v3s16( 1, 1, 1); vertex_dirs[3] = v3s16(-1, 1, 1); } else if(dir == v3s16(0,0,-1)) { // faces towards Z- vertex_dirs[0] = v3s16( 1,-1,-1); vertex_dirs[1] = v3s16(-1,-1,-1); vertex_dirs[2] = v3s16(-1, 1,-1); vertex_dirs[3] = v3s16( 1, 1,-1); } else if(dir == v3s16(1,0,0)) { // faces towards X+ vertex_dirs[0] = v3s16( 1,-1, 1); vertex_dirs[1] = v3s16( 1,-1,-1); vertex_dirs[2] = v3s16( 1, 1,-1); vertex_dirs[3] = v3s16( 1, 1, 1); } else if(dir == v3s16(-1,0,0)) { // faces towards X- vertex_dirs[0] = v3s16(-1,-1,-1); vertex_dirs[1] = v3s16(-1,-1, 1); vertex_dirs[2] = v3s16(-1, 1, 1); vertex_dirs[3] = v3s16(-1, 1,-1); } else if(dir == v3s16(0,1,0)) { // faces towards Y+ (assume Z- as "down" in texture) vertex_dirs[0] = v3s16( 1, 1,-1); vertex_dirs[1] = v3s16(-1, 1,-1); vertex_dirs[2] = v3s16(-1, 1, 1); vertex_dirs[3] = v3s16( 1, 1, 1); } else if(dir == v3s16(0,-1,0)) { // faces towards Y- (assume Z+ as "down" in texture) vertex_dirs[0] = v3s16( 1,-1, 1); vertex_dirs[1] = v3s16(-1,-1, 1); vertex_dirs[2] = v3s16(-1,-1,-1); vertex_dirs[3] = v3s16( 1,-1,-1); } } struct FastFace { TileSpec tile; video::S3DVertex vertices[4]; // Precalculated vertices }; static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, v3f p, v3s16 dir, v3f scale, u8 light_source, std::vector<FastFace> &dest) { // Position is at the center of the cube. v3f pos = p * BS; float x0 = 0.0; float y0 = 0.0; float w = 1.0; float h = 1.0; v3f vertex_pos[4]; v3s16 vertex_dirs[4]; getNodeVertexDirs(dir, vertex_dirs); v3s16 t; u16 t1; switch (tile.rotation) { case 0: break; case 1: //R90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; t1=li0; li0=li3; li3=li2; li2=li1; li1=t1; break; case 2: //R180 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[2]; vertex_dirs[2] = t; t = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[3]; vertex_dirs[3] = t; t1 = li0; li0 = li2; li2 = t1; t1 = li1; li1 = li3; li3 = t1; break; case 3: //R270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; t1 = li0; li0 = li1; li1 = li2; li2 = li3; li3 = t1; break; case 4: //FXR90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; t1 = li0; li0 = li3; li3 = li2; li2 = li1; li1 = t1; y0 += h; h *= -1; break; case 5: //FXR270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; t1 = li0; li0 = li1; li1 = li2; li2 = li3; li3 = t1; y0 += h; h *= -1; break; case 6: //FYR90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; t1 = li0; li0 = li3; li3 = li2; li2 = li1; li1 = t1; x0 += w; w *= -1; break; case 7: //FYR270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; t1 = li0; li0 = li1; li1 = li2; li2 = li3; li3 = t1; x0 += w; w *= -1; break; case 8: //FX y0 += h; h *= -1; break; case 9: //FY x0 += w; w *= -1; break; default: break; } for(u16 i=0; i<4; i++) { vertex_pos[i] = v3f( BS/2*vertex_dirs[i].X, BS/2*vertex_dirs[i].Y, BS/2*vertex_dirs[i].Z ); } for(u16 i=0; i<4; i++) { vertex_pos[i].X *= scale.X; vertex_pos[i].Y *= scale.Y; vertex_pos[i].Z *= scale.Z; vertex_pos[i] += pos; } f32 abs_scale = 1.0; if (scale.X < 0.999 || scale.X > 1.001) abs_scale = scale.X; else if(scale.Y < 0.999 || scale.Y > 1.001) abs_scale = scale.Y; else if(scale.Z < 0.999 || scale.Z > 1.001) abs_scale = scale.Z; v3f normal(dir.X, dir.Y, dir.Z); u8 alpha = tile.alpha; dest.push_back(FastFace()); FastFace& face = *dest.rbegin(); face.vertices[0] = video::S3DVertex(vertex_pos[0], normal, MapBlock_LightColor(alpha, li0, light_source), core::vector2d<f32>(x0+w*abs_scale, y0+h)); face.vertices[1] = video::S3DVertex(vertex_pos[1], normal, MapBlock_LightColor(alpha, li1, light_source), core::vector2d<f32>(x0, y0+h)); face.vertices[2] = video::S3DVertex(vertex_pos[2], normal, MapBlock_LightColor(alpha, li2, light_source), core::vector2d<f32>(x0, y0)); face.vertices[3] = video::S3DVertex(vertex_pos[3], normal, MapBlock_LightColor(alpha, li3, light_source), core::vector2d<f32>(x0+w*abs_scale, y0)); face.tile = tile; } /* Nodes make a face if contents differ and solidness differs. Return value: 0: No face 1: Face uses m1's content 2: Face uses m2's content equivalent: Whether the blocks share the same face (eg. water and glass) TODO: Add 3: Both faces drawn with backface culling, remove equivalent */ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, INodeDefManager *ndef) { *equivalent = false; if(m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE) return 0; bool contents_differ = (m1 != m2); const ContentFeatures &f1 = ndef->get(m1); const ContentFeatures &f2 = ndef->get(m2); // Contents don't differ for different forms of same liquid if(f1.sameLiquid(f2)) contents_differ = false; u8 c1 = f1.solidness; u8 c2 = f2.solidness; bool solidness_differs = (c1 != c2); bool makes_face = contents_differ && solidness_differs; if(makes_face == false) return 0; if(c1 == 0) c1 = f1.visual_solidness; if(c2 == 0) c2 = f2.visual_solidness; if(c1 == c2){ *equivalent = true; // If same solidness, liquid takes precense if(f1.isLiquid()) return 1; if(f2.isLiquid()) return 2; } if(c1 > c2) return 1; else return 2; } /* Gets nth node tile (0 <= n <= 5). */ TileSpec getNodeTileN(MapNode mn, v3s16 p, u8 tileindex, MeshMakeData *data) { INodeDefManager *ndef = data->m_gamedef->ndef(); TileSpec spec = ndef->get(mn).tiles[tileindex]; // Apply temporary crack if (p == data->m_crack_pos_relative) spec.material_flags |= MATERIAL_FLAG_CRACK; return spec; } /* Gets node tile given a face direction. */ TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 dir, MeshMakeData *data) { INodeDefManager *ndef = data->m_gamedef->ndef(); // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), // (0,0,1), (0,0,-1) or (0,0,0) assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1); // Convert direction to single integer for table lookup // 0 = (0,0,0) // 1 = (1,0,0) // 2 = (0,1,0) // 3 = (0,0,1) // 4 = invalid, treat as (0,0,0) // 5 = (0,0,-1) // 6 = (0,-1,0) // 7 = (-1,0,0) u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7)*2; // Get rotation for things like chests u8 facedir = mn.getFaceDir(ndef); static const u16 dir_to_tile[24 * 16] = { // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 , 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 , 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 , 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 , 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 , 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 , 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 , 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 , 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 , 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 , 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 , 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 , 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 , 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 , 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 , 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 , 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 , 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2 }; u16 tile_index=facedir*16 + dir_i; TileSpec spec = getNodeTileN(mn, p, dir_to_tile[tile_index], data); spec.rotation=dir_to_tile[tile_index + 1]; spec.texture = data->m_gamedef->tsrc()->getTexture(spec.texture_id); return spec; } static void getTileInfo( // Input: MeshMakeData *data, const v3s16 &p, const v3s16 &face_dir, // Output: bool &makes_face, v3s16 &p_corrected, v3s16 &face_dir_corrected, u16 *lights, TileSpec &tile, u8 &light_source ) { VoxelManipulator &vmanip = data->m_vmanip; INodeDefManager *ndef = data->m_gamedef->ndef(); v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); // Don't even try to get n1 if n0 is already CONTENT_IGNORE if (n0.getContent() == CONTENT_IGNORE) { makes_face = false; return; } const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); if (n1.getContent() == CONTENT_IGNORE) { makes_face = false; return; } // This is hackish bool equivalent = false; u8 mf = face_contents(n0.getContent(), n1.getContent(), &equivalent, ndef); if(mf == 0) { makes_face = false; return; } makes_face = true; if(mf == 1) { tile = getNodeTile(n0, p, face_dir, data); p_corrected = p; face_dir_corrected = face_dir; light_source = ndef->get(n0).light_source; } else { tile = getNodeTile(n1, p + face_dir, -face_dir, data); p_corrected = p + face_dir; face_dir_corrected = -face_dir; light_source = ndef->get(n1).light_source; } // eg. water and glass if(equivalent) tile.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; if(data->m_smooth_lighting == false) { lights[0] = lights[1] = lights[2] = lights[3] = getFaceLight(n0, n1, face_dir, ndef); } else { v3s16 vertex_dirs[4]; getNodeVertexDirs(face_dir_corrected, vertex_dirs); for(u16 i=0; i<4; i++) { lights[i] = getSmoothLight( blockpos_nodes + p_corrected, vertex_dirs[i], data); } } return; } /* startpos: translate_dir: unit vector with only one of x, y or z face_dir: unit vector with only one of x, y or z */ static void updateFastFaceRow( MeshMakeData *data, v3s16 startpos, v3s16 translate_dir, v3f translate_dir_f, v3s16 face_dir, v3f face_dir_f, std::vector<FastFace> &dest) { v3s16 p = startpos; u16 continuous_tiles_count = 1; bool makes_face = false; v3s16 p_corrected; v3s16 face_dir_corrected; u16 lights[4] = {0,0,0,0}; TileSpec tile; u8 light_source = 0; getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, lights, tile, light_source); for(u16 j=0; j<MAP_BLOCKSIZE; j++) { // If tiling can be done, this is set to false in the next step bool next_is_different = true; v3s16 p_next; bool next_makes_face = false; v3s16 next_p_corrected; v3s16 next_face_dir_corrected; u16 next_lights[4] = {0,0,0,0}; TileSpec next_tile; u8 next_light_source = 0; // If at last position, there is nothing to compare to and // the face must be drawn anyway if(j != MAP_BLOCKSIZE - 1) { p_next = p + translate_dir; getTileInfo(data, p_next, face_dir, next_makes_face, next_p_corrected, next_face_dir_corrected, next_lights, next_tile, next_light_source); if(next_makes_face == makes_face && next_p_corrected == p_corrected + translate_dir && next_face_dir_corrected == face_dir_corrected && next_lights[0] == lights[0] && next_lights[1] == lights[1] && next_lights[2] == lights[2] && next_lights[3] == lights[3] && next_tile == tile && tile.rotation == 0 && next_light_source == light_source && (tile.material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) { next_is_different = false; continuous_tiles_count++; } else { /*if(makes_face){ g_profiler->add("Meshgen: diff: next_makes_face != makes_face", next_makes_face != makes_face ? 1 : 0); g_profiler->add("Meshgen: diff: n_p_corr != p_corr + t_dir", (next_p_corrected != p_corrected + translate_dir) ? 1 : 0); g_profiler->add("Meshgen: diff: next_f_dir_corr != f_dir_corr", next_face_dir_corrected != face_dir_corrected ? 1 : 0); g_profiler->add("Meshgen: diff: next_lights[] != lights[]", (next_lights[0] != lights[0] || next_lights[0] != lights[0] || next_lights[0] != lights[0] || next_lights[0] != lights[0]) ? 1 : 0); g_profiler->add("Meshgen: diff: !(next_tile == tile)", !(next_tile == tile) ? 1 : 0); }*/ } /*g_profiler->add("Meshgen: Total faces checked", 1); if(makes_face) g_profiler->add("Meshgen: Total makes_face checked", 1);*/ } else { /*if(makes_face) g_profiler->add("Meshgen: diff: last position", 1);*/ } if(next_is_different) { /* Create a face if there should be one */ if(makes_face) { // Floating point conversion of the position vector v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); // Center point of face (kind of) v3f sp = pf - ((f32)continuous_tiles_count / 2.0 - 0.5) * translate_dir_f; v3f scale(1,1,1); if(translate_dir.X != 0) { scale.X = continuous_tiles_count; } if(translate_dir.Y != 0) { scale.Y = continuous_tiles_count; } if(translate_dir.Z != 0) { scale.Z = continuous_tiles_count; } makeFastFace(tile, lights[0], lights[1], lights[2], lights[3], sp, face_dir_corrected, scale, light_source, dest); g_profiler->avg("Meshgen: faces drawn by tiling", 0); for(int i = 1; i < continuous_tiles_count; i++){ g_profiler->avg("Meshgen: faces drawn by tiling", 1); } } continuous_tiles_count = 1; } makes_face = next_makes_face; p_corrected = next_p_corrected; face_dir_corrected = next_face_dir_corrected; lights[0] = next_lights[0]; lights[1] = next_lights[1]; lights[2] = next_lights[2]; lights[3] = next_lights[3]; tile = next_tile; light_source = next_light_source; p = p_next; } } static void updateAllFastFaceRows(MeshMakeData *data, std::vector<FastFace> &dest) { /* Go through every y,z and get top(y+) faces in rows of x+ */ for(s16 y = 0; y < MAP_BLOCKSIZE; y++) { for(s16 z = 0; z < MAP_BLOCKSIZE; z++) { updateFastFaceRow(data, v3s16(0,y,z), v3s16(1,0,0), //dir v3f (1,0,0), v3s16(0,1,0), //face dir v3f (0,1,0), dest); } } /* Go through every x,y and get right(x+) faces in rows of z+ */ for(s16 x = 0; x < MAP_BLOCKSIZE; x++) { for(s16 y = 0; y < MAP_BLOCKSIZE; y++) { updateFastFaceRow(data, v3s16(x,y,0), v3s16(0,0,1), //dir v3f (0,0,1), v3s16(1,0,0), //face dir v3f (1,0,0), dest); } } /* Go through every y,z and get back(z+) faces in rows of x+ */ for(s16 z = 0; z < MAP_BLOCKSIZE; z++) { for(s16 y = 0; y < MAP_BLOCKSIZE; y++) { updateFastFaceRow(data, v3s16(0,y,z), v3s16(1,0,0), //dir v3f (1,0,0), v3s16(0,0,1), //face dir v3f (0,0,1), dest); } } } /* MapBlockMesh */ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_mesh(new scene::SMesh()), m_minimap_mapblock(NULL), m_gamedef(data->m_gamedef), m_driver(m_gamedef->tsrc()->getDevice()->getVideoDriver()), m_tsrc(m_gamedef->getTextureSource()), m_shdrsrc(m_gamedef->getShaderSource()), m_animation_force_timer(0), // force initial animation m_last_crack(-1), m_crack_materials(), m_last_daynight_ratio((u32) -1), m_daynight_diffs() { m_enable_shaders = data->m_use_shaders; m_use_tangent_vertices = data->m_use_tangent_vertices; m_enable_vbo = g_settings->getBool("enable_vbo"); if (g_settings->getBool("enable_minimap")) { m_minimap_mapblock = new MinimapMapblock; m_minimap_mapblock->getMinimapNodes( &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); } // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) //TimeTaker timer1("MapBlockMesh()"); std::vector<FastFace> fastfaces_new; fastfaces_new.reserve(512); /* We are including the faces of the trailing edges of the block. This means that when something changes, the caller must also update the meshes of the blocks at the leading edges. NOTE: This is the slowest part of this method. */ { // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) //TimeTaker timer2("updateAllFastFaceRows()"); updateAllFastFaceRows(data, fastfaces_new); } // End of slow part /* Convert FastFaces to MeshCollector */ MeshCollector collector(m_use_tangent_vertices); { // avg 0ms (100ms spikes when loading textures the first time) // (NOTE: probably outdated) //TimeTaker timer2("MeshCollector building"); for (u32 i = 0; i < fastfaces_new.size(); i++) { FastFace &f = fastfaces_new[i]; const u16 indices[] = {0,1,2,2,3,0}; const u16 indices_alternate[] = {0,1,3,2,3,1}; if(f.tile.texture == NULL) continue; const u16 *indices_p = indices; /* Revert triangles for nicer looking gradient if vertices 1 and 3 have same color or 0 and 2 have different color. getRed() is the day color. */ if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed() || f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed()) indices_p = indices_alternate; collector.append(f.tile, f.vertices, 4, indices_p, 6); } } /* Add special graphics: - torches - flowing water - fences - whatever */ mapblock_mesh_generate_special(data, collector); /* Convert MeshCollector to SMesh */ for(u32 i = 0; i < collector.prebuffers.size(); i++) { PreMeshBuffer &p = collector.prebuffers[i]; // Generate animation data // - Cracks if(p.tile.material_flags & MATERIAL_FLAG_CRACK) { // Find the texture name plus ^[crack:N: std::ostringstream os(std::ios::binary); os<<m_tsrc->getTextureName(p.tile.texture_id)<<"^[crack"; if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) os<<"o"; // use ^[cracko os<<":"<<(u32)p.tile.animation_frame_count<<":"; m_crack_materials.insert(std::make_pair(i, os.str())); // Replace tile texture with the cracked one p.tile.texture = m_tsrc->getTextureForMesh( os.str()+"0", &p.tile.texture_id); } // - Texture animation if(p.tile.material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) { // Add to MapBlockMesh in order to animate these tiles m_animation_tiles[i] = p.tile; m_animation_frames[i] = 0; if(g_settings->getBool("desynchronize_mapblock_texture_animation")){ // Get starting position from noise m_animation_frame_offsets[i] = 100000 * (2.0 + noise3d( data->m_blockpos.X, data->m_blockpos.Y, data->m_blockpos.Z, 0)); } else { // Play all synchronized m_animation_frame_offsets[i] = 0; } // Replace tile texture with the first animation frame FrameSpec animation_frame = p.tile.frames[0]; p.tile.texture = animation_frame.texture; } u32 vertex_count = m_use_tangent_vertices ? p.tangent_vertices.size() : p.vertices.size(); for (u32 j = 0; j < vertex_count; j++) { v3f *Normal; video::SColor *vc; if (m_use_tangent_vertices) { vc = &p.tangent_vertices[j].Color; Normal = &p.tangent_vertices[j].Normal; } else { vc = &p.vertices[j].Color; Normal = &p.vertices[j].Normal; } // Note applyFacesShading second parameter is precalculated sqrt // value for speed improvement // Skip it for lightsources and top faces. if (!vc->getBlue()) { if (Normal->Y < -0.5) { applyFacesShading(*vc, 0.447213); } else if (Normal->X > 0.5) { applyFacesShading(*vc, 0.670820); } else if (Normal->X < -0.5) { applyFacesShading(*vc, 0.670820); } else if (Normal->Z > 0.5) { applyFacesShading(*vc, 0.836660); } else if (Normal->Z < -0.5) { applyFacesShading(*vc, 0.836660); } } if (!m_enable_shaders) { // - Classic lighting (shaders handle this by themselves) // Set initial real color and store for later updates u8 day = vc->getRed(); u8 night = vc->getGreen(); finalColorBlend(*vc, day, night, 1000); if (day != night) { m_daynight_diffs[i][j] = std::make_pair(day, night); } } } // Create material video::SMaterial material; material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BACK_FACE_CULLING, true); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_FOG_ENABLE, true); material.setTexture(0, p.tile.texture); if (m_enable_shaders) { material.MaterialType = m_shdrsrc->getShaderInfo(p.tile.shader_id).material; p.tile.applyMaterialOptionsWithShaders(material); if (p.tile.normal_texture) { material.setTexture(1, p.tile.normal_texture); } material.setTexture(2, p.tile.flags_texture); } else { p.tile.applyMaterialOptions(material); } scene::SMesh *mesh = (scene::SMesh *)m_mesh; // Create meshbuffer, add to mesh if (m_use_tangent_vertices) { scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); // Set material buf->Material = material; // Add to mesh mesh->addMeshBuffer(buf); // Mesh grabbed it buf->drop(); buf->append(&p.tangent_vertices[0], p.tangent_vertices.size(), &p.indices[0], p.indices.size()); } else { scene::SMeshBuffer *buf = new scene::SMeshBuffer(); // Set material buf->Material = material; // Add to mesh mesh->addMeshBuffer(buf); // Mesh grabbed it buf->drop(); buf->append(&p.vertices[0], p.vertices.size(), &p.indices[0], p.indices.size()); } } /* Do some stuff to the mesh */ m_camera_offset = camera_offset; translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); if (m_use_tangent_vertices) { scene::IMeshManipulator* meshmanip = m_gamedef->getSceneManager()->getMeshManipulator(); meshmanip->recalculateTangents(m_mesh, true, false, false); } if (m_mesh) { #if 0 // Usually 1-700 faces and 1-7 materials std::cout<<"Updated MapBlock has "<<fastfaces_new.size()<<" faces " <<"and uses "<<m_mesh->getMeshBufferCount() <<" materials (meshbuffers)"<<std::endl; #endif // Use VBO for mesh (this just would set this for ever buffer) if (m_enable_vbo) { m_mesh->setHardwareMappingHint(scene::EHM_STATIC); } } //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl; // Check if animation is required for this mesh m_has_animation = !m_crack_materials.empty() || !m_daynight_diffs.empty() || !m_animation_tiles.empty(); } MapBlockMesh::~MapBlockMesh() { if (m_enable_vbo && m_mesh) { for (u32 i = 0; i < m_mesh->getMeshBufferCount(); i++) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i); m_driver->removeHardwareBuffer(buf); } } m_mesh->drop(); m_mesh = NULL; delete m_minimap_mapblock; } bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_ratio) { if(!m_has_animation) { m_animation_force_timer = 100000; return false; } m_animation_force_timer = myrand_range(5, 100); // Cracks if(crack != m_last_crack) { for(std::map<u32, std::string>::iterator i = m_crack_materials.begin(); i != m_crack_materials.end(); ++i) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); std::string basename = i->second; // Create new texture name from original std::ostringstream os; os<<basename<<crack; u32 new_texture_id = 0; video::ITexture *new_texture = m_tsrc->getTextureForMesh(os.str(), &new_texture_id); buf->getMaterial().setTexture(0, new_texture); // If the current material is also animated, // update animation info std::map<u32, TileSpec>::iterator anim_iter = m_animation_tiles.find(i->first); if(anim_iter != m_animation_tiles.end()){ TileSpec &tile = anim_iter->second; tile.texture = new_texture; tile.texture_id = new_texture_id; // force animation update m_animation_frames[i->first] = -1; } } m_last_crack = crack; } // Texture animation for(std::map<u32, TileSpec>::iterator i = m_animation_tiles.begin(); i != m_animation_tiles.end(); ++i) { const TileSpec &tile = i->second; // Figure out current frame int frameoffset = m_animation_frame_offsets[i->first]; int frame = (int)(time * 1000 / tile.animation_frame_length_ms + frameoffset) % tile.animation_frame_count; // If frame doesn't change, skip if(frame == m_animation_frames[i->first]) continue; m_animation_frames[i->first] = frame; scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); FrameSpec animation_frame = tile.frames[frame]; buf->getMaterial().setTexture(0, animation_frame.texture); if (m_enable_shaders) { if (animation_frame.normal_texture) { buf->getMaterial().setTexture(1, animation_frame.normal_texture); } buf->getMaterial().setTexture(2, animation_frame.flags_texture); } } // Day-night transition if(!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) { // Force reload mesh to VBO if (m_enable_vbo) { m_mesh->setDirty(); } for(std::map<u32, std::map<u32, std::pair<u8, u8> > >::iterator i = m_daynight_diffs.begin(); i != m_daynight_diffs.end(); ++i) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); for(std::map<u32, std::pair<u8, u8 > >::iterator j = i->second.begin(); j != i->second.end(); ++j) { u8 day = j->second.first; u8 night = j->second.second; finalColorBlend(vertices[j->first].Color, day, night, daynight_ratio); } } m_last_daynight_ratio = daynight_ratio; } return true; } void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) { if (camera_offset != m_camera_offset) { translateMesh(m_mesh, intToFloat(m_camera_offset-camera_offset, BS)); if (m_enable_vbo) { m_mesh->setDirty(); } m_camera_offset = camera_offset; } } /* MeshCollector */ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices) { if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl; return; } PreMeshBuffer *p = NULL; for (u32 i = 0; i < prebuffers.size(); i++) { PreMeshBuffer &pp = prebuffers[i]; if (pp.tile != tile) continue; if (pp.indices.size() + numIndices > 65535) continue; p = &pp; break; } if (p == NULL) { PreMeshBuffer pp; pp.tile = tile; prebuffers.push_back(pp); p = &prebuffers[prebuffers.size() - 1]; } u32 vertex_count; if (m_use_tangent_vertices) { vertex_count = p->tangent_vertices.size(); for (u32 i = 0; i < numVertices; i++) { video::S3DVertexTangents vert(vertices[i].Pos, vertices[i].Normal, vertices[i].Color, vertices[i].TCoords); p->tangent_vertices.push_back(vert); } } else { vertex_count = p->vertices.size(); for (u32 i = 0; i < numVertices; i++) { video::S3DVertex vert(vertices[i].Pos, vertices[i].Normal, vertices[i].Color, vertices[i].TCoords); p->vertices.push_back(vert); } } for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; p->indices.push_back(j); } } /* MeshCollector - for meshnodes and converted drawtypes. */ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, v3f pos, video::SColor c) { if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl; return; } PreMeshBuffer *p = NULL; for (u32 i = 0; i < prebuffers.size(); i++) { PreMeshBuffer &pp = prebuffers[i]; if(pp.tile != tile) continue; if(pp.indices.size() + numIndices > 65535) continue; p = &pp; break; } if (p == NULL) { PreMeshBuffer pp; pp.tile = tile; prebuffers.push_back(pp); p = &prebuffers[prebuffers.size() - 1]; } u32 vertex_count; if (m_use_tangent_vertices) { vertex_count = p->tangent_vertices.size(); for (u32 i = 0; i < numVertices; i++) { video::S3DVertexTangents vert(vertices[i].Pos + pos, vertices[i].Normal, c, vertices[i].TCoords); p->tangent_vertices.push_back(vert); } } else { vertex_count = p->vertices.size(); for (u32 i = 0; i < numVertices; i++) { video::S3DVertex vert(vertices[i].Pos + pos, vertices[i].Normal, c, vertices[i].TCoords); p->vertices.push_back(vert); } } for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; p->indices.push_back(j); } }