1 /**
2 Copyright: Copyright (c) 2016-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module railroad.rail.utils;
7 
8 import voxelman.math;
9 import voxelman.geometry;
10 import voxelman.world.block;
11 import voxelman.world.blockentity.blockentitydata;
12 import voxelman.world.blockentity.blockentityaccess;
13 import voxelman.world.blockentity.utils;
14 import voxelman.world.storage;
15 import voxelman.world.mesh.utils : FaceSide, oppFaceSides;
16 
17 enum RAIL_TILE_SIZE = 8;
18 immutable ivec3 railSizeVector = ivec3(RAIL_TILE_SIZE, 1, RAIL_TILE_SIZE);
19 immutable ivec4 railPickOffset = ivec4(RAIL_TILE_SIZE/2, 0, RAIL_TILE_SIZE/2, 0);
20 
21 ivec3 railTilePos(ivec3 bwp) {
22 	return ivec3(floor(cast(float)bwp.x / RAIL_TILE_SIZE) * RAIL_TILE_SIZE,
23 		bwp.y,
24 		floor(cast(float)bwp.z / RAIL_TILE_SIZE) * RAIL_TILE_SIZE);
25 }
26 
27 ivec3 calcBlockTilePos(ivec3 bwp)
28 {
29 	ivec3 tilePos = railTilePos(bwp);
30 	return bwp - tilePos;
31 }
32 
33 RailData getRailAt(RailPos railPos, ushort railEntityId,
34 	WorldAccess worldAccess, BlockEntityAccess entityAccess)
35 {
36 	auto bwp = railPos.toBlockWorldPos;
37 	bwp.vector += railPickOffset;
38 	auto blockId = worldAccess.getBlock(bwp);
39 
40 	if (isBlockEntity(blockId))
41 	{
42 		ushort blockIndex = blockEntityIndexFromBlockId(blockId);
43 		BlockEntityData entity = entityAccess.getBlockEntity(railPos.chunkPos, blockIndex);
44 
45 		if (entity.id == railEntityId)
46 			return RailData(entity);
47 	}
48 	return RailData();
49 }
50 
51 enum RailEditOp
52 {
53 	add,
54 	remove
55 }
56 
57 struct RailPos {
58 	this(svec4 vec) {
59 		vector = vec;
60 	}
61 	this(BlockWorldPos bwp)
62 	{
63 		vector = svec4(
64 			floor(cast(float)bwp.x / RAIL_TILE_SIZE),
65 			cast(float)bwp.y,
66 			floor(cast(float)bwp.z / RAIL_TILE_SIZE),
67 			bwp.w);
68 	}
69 	ChunkWorldPos chunkPos() const {
70 		return ChunkWorldPos(toBlockWorldPos());
71 	}
72 	BlockWorldPos toBlockWorldPos() const {
73 		return BlockWorldPos(
74 			vector.x * RAIL_TILE_SIZE,
75 			vector.y,
76 			vector.z * RAIL_TILE_SIZE,
77 			vector.w);
78 	}
79 	WorldBox toBlockBox() const
80 	{
81 		return WorldBox(toBlockWorldPos().xyz, railSizeVector, vector.w);
82 	}
83 	BlockWorldPos deletePos() const
84 	{
85 		auto bwp = toBlockWorldPos;
86 		bwp.vector += railPickOffset;
87 		return bwp;
88 	}
89 	svec4 vector;
90 	alias vector this;
91 
92 	ulong asUlong() const @property
93 	{
94 		ulong res = cast(ulong)vector.w<<48 |
95 				cast(ulong)(cast(ushort)vector.z)<<32 |
96 				cast(ulong)(cast(ushort)vector.y)<<16 |
97 				cast(ulong)(cast(ushort)vector.x);
98 		return res;
99 	}
100 
101 	RailPos posInDirection(FaceSide direction)
102 	{
103 		byte[3] offset = sideToOffset[direction];
104 		return RailPos(svec4(
105 			vector.x+offset[0],
106 			vector.y+offset[1],
107 			vector.z+offset[2],
108 			vector.w));
109 	}
110 }
111 
112 immutable byte[3][4] sideToOffset = [
113 	[ 0, 0,-1],
114 	[-1, 0, 0],
115 	[ 0, 0, 1],
116 	[ 1, 0, 0]];
117 
118 struct RailData
119 {
120 	ubyte data;
121 
122 	this(ubyte eData) {
123 		data = eData;
124 	}
125 
126 	this(RailSegment segment) {
127 		data = railSegmentData[segment];
128 	}
129 
130 	this(BlockEntityData beData) {
131 		data = cast(ubyte)(beData.entityData);
132 	}
133 
134 	bool isSlope() const {
135 		return (data & SLOPE_RAIL_BIT) != 0;
136 	}
137 
138 	bool hasSingleSegment() const {
139 		if ((data & SLOPE_RAIL_BIT) != 0)
140 		{
141 			return 1;
142 		}
143 		else
144 		{
145 			import core.bitop : popcnt;
146 			return popcnt(data) == 1;
147 		}
148 	}
149 
150 	bool empty() const {
151 		return data == 0;
152 	}
153 
154 	void addRail(RailData newRail)
155 	{
156 		if (newRail.isSlope || isSlope)
157 		{
158 			data = newRail.data;
159 		}
160 		else
161 		{
162 			data |= newRail.data;
163 		}
164 	}
165 
166 	void removeRail(RailData newRail)
167 	{
168 		if (data == newRail.data) // slope or multiple same rails
169 		{
170 			data = 0;
171 		}
172 		else
173 		{
174 			data &= ~cast(int)(newRail.data);
175 		}
176 	}
177 
178 	void editRail(RailData railData, RailEditOp editOp)
179 	{
180 		final switch(editOp)
181 		{
182 			case RailEditOp.add: addRail(railData); break; // combine rails
183 			case RailEditOp.remove: removeRail(railData); break; // remove rails
184 		}
185 	}
186 
187 	SegmentRange getSegments() const
188 	{
189 		return SegmentRange(data);
190 	}
191 
192 	// returns segments that connect to the side
193 	SegmentBuffer getSegmentsFromSide(FaceSide side)
194 	{
195 		SegmentBuffer result;
196 		foreach(RailSegment segment; getSegments)
197 		{
198 			if (segmentInfos[segment].sideConnections[side])
199 				result.put(segment);
200 		}
201 		return result;
202 	}
203 
204 	// returns segments that connect to the side
205 	// and create smooth curve with adjacent segment
206 	// adjacent segment is guaranteed to be connected to the side
207 	SegmentBuffer getSegmentsFromSideSmooth(FaceSide side, RailSegment adjacent)
208 	{
209 		assert(segmentInfos[adjacent].sideConnections[oppFaceSides[side]],
210 			"Adjacent segment must be connected to the side");
211 		SegmentBuffer result;
212 		foreach(RailSegment segment; getSegments)
213 		{
214 			if (segmentInfos[segment].sideConnections[side])
215 			{
216 				// TODO
217 				result.put(segment);
218 			}
219 		}
220 		return result;
221 	}
222 
223 	Solidity bottomSolidity(ivec3 blockTilePos) const
224 	{
225 		foreach(segment; getSegments)
226 		{
227 			if (isSegmentSolid(segment, blockTilePos))
228 				return Solidity.solid;
229 		}
230 
231 		return Solidity.transparent;
232 	}
233 
234 	WorldBox boundingBox(RailPos railPos) const
235 	{
236 		return boundingBox(railPos.toBlockWorldPos());
237 	}
238 
239 	WorldBox boundingBox(BlockWorldPos bwp) const
240 	{
241 		if (isSlope)
242 		{
243 			auto segment = data - SLOPE_RAIL_BIT + RailSegment.znegUp;
244 			ivec3 tilePos = railTilePos(bwp.xyz);
245 			ivec3 railPos = tilePos + railSegmentOffsets[segment];
246 			ivec3 railSize = railSegmentSizes[segment];
247 			return WorldBox(railPos, railSize, cast(ushort)(bwp.w));
248 		}
249 		else
250 		{
251 			ivec3 tilePos = railTilePos(bwp.xyz);
252 
253 			Box commonBox;
254 			ubyte flag = 1;
255 			foreach(segment; 0..6)
256 			{
257 				if (flag & data)
258 				{
259 					ivec3 segmentPos = railSegmentOffsets[segment];
260 					ivec3 segmentSize = railSegmentSizes[segment];
261 					Box box = Box(segmentPos, segmentSize);
262 					if (commonBox.empty)
263 						commonBox = box;
264 					else
265 						commonBox = calcCommonBox(commonBox, box);
266 				}
267 
268 				flag <<= 1;
269 			}
270 			commonBox.position += tilePos;
271 
272 			return WorldBox(commonBox, cast(ushort)(bwp.w));
273 		}
274 	}
275 }
276 
277 import voxelman.container.fixedbuffer;
278 alias SegmentBuffer = FixedBuffer!(RailSegment, 3);
279 
280 struct SegmentRange
281 {
282 	this(ubyte _data)
283 	{
284 		data = _data;
285 	}
286 
287 	private ubyte data;
288 
289 	int opApply(scope int delegate(size_t, RailSegment) del)
290 	{
291 		size_t index;
292 		int proxyDel(RailSegment segment) { return del(index++, segment); }
293 		return opApply(&proxyDel);
294 	}
295 
296 	int opApply(scope int delegate(RailSegment) del)
297 	{
298 		if ((data & SLOPE_RAIL_BIT) != 0)
299 		{
300 			ubyte segment = cast(ubyte)(data - SLOPE_RAIL_BIT + RailSegment.znegUp);
301 			if (auto ret = del(cast(RailSegment)segment))
302 				return ret;
303 		}
304 		else if (data != 0)
305 		{
306 			import core.bitop : bsf;
307 
308 			ubyte segment = cast(ubyte)bsf(data);
309 			ubyte flag = cast(ubyte)(1 << segment);
310 
311 			while(segment <= RailSegment.xposZneg)
312 			{
313 				if (flag & data)
314 					if (auto ret = del(cast(RailSegment)segment))
315 						return ret;
316 				flag <<= 1;
317 				++segment;
318 			}
319 		}
320 		return 0;
321 	}
322 }
323 
324 enum RailSegment
325 {
326 	zneg,
327 	xpos,
328 
329 	xnegZneg,
330 	xnegZpos,
331 	xposZpos,
332 	xposZneg,
333 
334 	znegUp,
335 	xnegUp,
336 	zposUp,
337 	xposUp,
338 
339 	//znegDown = zposUp,
340 	//zposDown = znegUp,
341 	//xposDown = xnegUp,
342 	//xnegDown = xposUp,
343 }
344 
345 enum SEGMENT_LENGTH_STRAIGHT = 8;
346 enum SEGMENT_LENGTH_DIAGONAL = 4*SQRT_2;
347 enum SEGMENT_LENGTH_SLOPE = sqrt(8.0*8.0 + 1*1);
348 
349 float[] segmentLengths = [
350 	SEGMENT_LENGTH_STRAIGHT,
351 	SEGMENT_LENGTH_STRAIGHT,
352 
353 	SEGMENT_LENGTH_DIAGONAL,
354 	SEGMENT_LENGTH_DIAGONAL,
355 	SEGMENT_LENGTH_DIAGONAL,
356 	SEGMENT_LENGTH_DIAGONAL,
357 
358 	SEGMENT_LENGTH_SLOPE,
359 	SEGMENT_LENGTH_SLOPE,
360 	SEGMENT_LENGTH_SLOPE,
361 	SEGMENT_LENGTH_SLOPE,
362 ];
363 
364 enum SLOPE_RAIL_BIT = 0b0100_0000;
365 ubyte[] railSegmentData =
366 [
367 	1,
368 	2,
369 
370 	4,
371 	8,
372 	16,
373 	32,
374 
375 	SLOPE_RAIL_BIT + 0,
376 	SLOPE_RAIL_BIT + 1,
377 	SLOPE_RAIL_BIT + 2,
378 	SLOPE_RAIL_BIT + 3];
379 
380 // slopeUpToSide[data & 0b11]
381 CubeSide[4] slopeUpToSide = [
382 	CubeSide.zneg,
383 	CubeSide.xneg,
384 	CubeSide.zpos,
385 	CubeSide.xpos];
386 
387 bool isSlopeUpSideBlock(RailData railData, ivec3 entityPos, out CubeSide sideToMesh)
388 {
389 	sideToMesh = slopeUpToSide[railData.data & 0b11];
390 	switch(sideToMesh)
391 	{
392 		case CubeSide.zneg: return entityPos.z == 0;
393 		case CubeSide.xneg: return entityPos.x == 0;
394 		case CubeSide.zpos: return entityPos.z == Z_RAIL_SIZE.z - 1;
395 		case CubeSide.xpos: return entityPos.x == X_RAIL_SIZE.x - 1;
396 		default: assert(false);
397 	}
398 }
399 
400 enum Z_RAIL_SIZE = ivec3(4, 1, 8);
401 enum X_RAIL_SIZE = ivec3(8, 1, 4);
402 enum DIAGONAL_RAIL_SIZE = ivec3(6, 1, 6);
403 
404 // [x, z]
405 ivec3[] railSegmentSizes = [
406 	Z_RAIL_SIZE, // zneg
407 	X_RAIL_SIZE, // xpos
408 
409 	DIAGONAL_RAIL_SIZE, // xnegZneg
410 	DIAGONAL_RAIL_SIZE, // xnegZpos
411 	DIAGONAL_RAIL_SIZE, // xposZpos
412 	DIAGONAL_RAIL_SIZE, // xposZneg
413 
414 	Z_RAIL_SIZE, // znegUp
415 	X_RAIL_SIZE, // xnegUp
416 	Z_RAIL_SIZE, // zposUp
417 	X_RAIL_SIZE, // xposUp
418 ];
419 
420 enum Z_RAIL_OFFSET = ivec3(2, 0, 0);
421 enum X_RAIL_OFFSET = ivec3(0, 0, 2);
422 
423 // [x, z]
424 ivec3[] railSegmentOffsets = [
425 	Z_RAIL_OFFSET, // zneg
426 	X_RAIL_OFFSET, // xpos
427 
428 	ivec3(0, 0, 0), // xnegZneg
429 	ivec3(0, 0, 2), // xnegZpos
430 	ivec3(2, 0, 2), // xposZpos
431 	ivec3(2, 0, 0), // xposZneg
432 
433 	Z_RAIL_OFFSET, // znegUp
434 	X_RAIL_OFFSET, // xnegUp
435 	Z_RAIL_OFFSET, // zposUp
436 	X_RAIL_OFFSET, // xposUp
437 ];
438 
439 
440 ubyte[] railSegmentMeshId = [0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
441 ubyte[] railSegmentMeshRotation = [0, 1, 0, 1, 2, 3, 0, 1, 2, 3];
442 
443 void rotateSegment(ref RailSegment segment)
444 {
445 	++segment;
446 	if (segment > RailSegment.max)
447 		segment = RailSegment.min;
448 }
449 
450 bool isSegmentSolid(RailSegment segment, ivec3 blockTilePos)
451 {
452 	import core.bitop : bt;
453 	auto bitmapId = bt(cast(size_t*)&railSegmentBottomSolidityIndex, segment);
454 	ubyte rotation = railSegmentMeshRotation[segment];
455 	// Size needs to be less by 1 for correct shift
456 	ivec3 rotatedPos = rotatePointShiftOriginCW!ivec3(blockTilePos, ivec3(7,0,7), rotation);
457 	// Invert bit order (63 - bit num) add bitnum id offset of 64
458 	auto tileIndex = 63 - (rotatedPos.x + rotatedPos.z * RAIL_TILE_SIZE) + 64 * bitmapId;
459 	return !!bt(cast(size_t*)&railBottomSolidityBitmaps, tileIndex);
460 }
461 
462 ushort railSegmentBottomSolidityIndex = 0b0000_1111_00;
463 
464 ulong[2] railBottomSolidityBitmaps = [
465 mixin("0b"~ // !bit order is right to left!
466 "00111100"~
467 "00111100"~
468 "00111100"~
469 "00111100"~
470 "00111100"~
471 "00111100"~
472 "00111100"~
473 "00111100"),
474 mixin("0b"~
475 "00111000"~
476 "01110000"~
477 "11100000"~
478 "11000000"~
479 "10000000"~
480 "00000000"~
481 "00000000"~
482 "00000000")];
483 
484 // Rail tile can have a number of these. I.e. straight, diagonal rails are segments.
485 struct SegmentInfo
486 {
487 	// Sides of rail tile that this segment connects to
488 	// 0 zneg, 1 xneg, 2 zpos, 3 xpos
489 	FaceSide[2] sides;
490 	bool[4] sideConnections;
491 	// 0 - first connection (item at sides[0]), 1 - second connection (item at sides[1]). Non-connected sides also use 0.
492 	// Only indicies stated in 'sides' are relevant
493 	ubyte[4] sideIndicies;
494 }
495 
496 // relative to 8x8 tile
497 vec3[] railTileConnectionPoints = [
498 	vec3(4, 0.5, 0),
499 	vec3(0, 0.5, 4),
500 	vec3(4, 0.5, 8),
501 	vec3(8, 0.5, 4),
502 ];
503 
504 SegmentInfo[10] segmentInfos = [
505 	{cast(FaceSide[2])[0, 2], [true,  false, true,  false], [0, 0, 1, 0]}, // zneg,
506 	{cast(FaceSide[2])[1, 3], [false, true,  false, true ], [0, 0, 0, 1]}, // xpos,
507 
508 	{cast(FaceSide[2])[1, 0], [true,  true,  false, false], [1, 0, 0, 0]}, // xnegZneg,
509 	{cast(FaceSide[2])[1, 2], [false, true,  true,  false], [0, 0, 1, 0]}, // xnegZpos,
510 	{cast(FaceSide[2])[3, 2], [false, false, true,  true ], [0, 0, 1, 0]}, // xposZpos,
511 	{cast(FaceSide[2])[3, 0], [true,  false, false, true ], [1, 0, 0, 0]}, // xposZneg,
512 
513 	{cast(FaceSide[2])[0, 2], [true,  false, false, false], [0, 0, 0, 0]}, // znegUp,
514 	{cast(FaceSide[2])[1, 3], [false, true,  false, false], [0, 0, 0, 0]}, // xnegUp,
515 	{cast(FaceSide[2])[2, 0], [false, false, true,  false], [0, 0, 0, 0]}, // zposUp,
516 	{cast(FaceSide[2])[3, 1], [false, false, false, true ], [0, 0, 0, 0]}, // xposUp,
517 ];
518 
519 struct SmoothConnections
520 {
521 	ushort data;
522 	bool isSmoothWith(RailSegment segment)
523 	{
524 		return true; // TODO
525 	}
526 }
527 
528 // Table which gives info on valid transitions between segments for wagons
529 SmoothConnections[] segmentSmoothConnectionTbl = [
530 ];
531 
532 
533 import voxelman.graphics;
534 
535 void railDebugHandler(ref BlockEntityDebugContext context)
536 {
537 	drawSolidityDebug(context.graphics.debugBatch, RailData(context.data), context.bwp);
538 }
539 
540 void drawSolidityDebug(ref Batch b, RailData data, BlockWorldPos bwp)
541 {
542 	ivec3 tilePos = railTilePos(bwp.xyz);
543 	ivec3 blockTilePos = bwp.xyz - tilePos;
544 
545 	foreach(segment; data.getSegments)
546 	{
547 		foreach(z; 0..RAIL_TILE_SIZE)
548 		foreach(x; 0..RAIL_TILE_SIZE)
549 		{
550 			auto blockPos = ivec3(x, 0, z);
551 			if (isSegmentSolid(segment, blockPos))
552 			{
553 				auto renderPos = tilePos + ivec3(x, segment, z);
554 				enum cursorOffset = vec3(0.01, 0.01, 0.01);
555 				b.putCube(vec3(renderPos) - cursorOffset + vec3(0.25,0.25,0.25),
556 					vec3(0.5,0.5,0.5) + cursorOffset, Colors.black, true);
557 			}
558 		}
559 	}
560 }
561 
562 // Diagonal rail utils
563 
564 enum RailOrientation
565 {
566 	x,
567 	xzOppSign, // xneg-zpos, xpos-zneg
568 	z,
569 	xzSameSign, //xneg-zneg, xpos-zpos
570 }
571 
572 enum DiagonalRailSide
573 {
574 	zpos,
575 	zneg
576 }
577 
578 ivec2 addDiagonalManhattan(ivec2 origin, int distance, RailOrientation orientation, DiagonalRailSide side)
579 {
580 	bool topSide = side == DiagonalRailSide.zneg;
581 	int odd = distance % 2 != 0;
582 
583 	switch(orientation)
584 	{
585 		case RailOrientation.xzSameSign:
586 			ivec2 oddIncrement = topSide ? ivec2(0, -1) : ivec2(1, 0);
587 			return origin + ivec2(1, -1) * cast(int)floor(distance/2.0f) + oddIncrement * odd;
588 
589 		case RailOrientation.xzOppSign:
590 			ivec2 oddIncrement = topSide ? ivec2(1, 0) : ivec2(0,  1);
591 			return origin + ivec2(1,  1) * cast(int)floor(distance/2.0f) + oddIncrement * odd;
592 
593 		default: assert(false);
594 	}
595 }
596