1 /**
2 Copyright: Copyright (c) 2017-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module railroad.wagon.wagontool;
7 
8 import dlib.geometry.plane;
9 import voxelman.log;
10 import voxelman.math;
11 
12 import voxelman.blockentity.blockentityman;
13 
14 import voxelman.edit.plugin;
15 import voxelman.edit.tools.itool;
16 
17 import voxelman.graphics.plugin;
18 import voxelman.net.plugin;
19 
20 import voxelman.world.blockentity;
21 import voxelman.world.clientworld;
22 import voxelman.world.storage;
23 import voxelman.worldinteraction.plugin;
24 
25 import railroad.rail.utils;
26 import railroad.wagon.packets;
27 import railroad.wagon.wagon;
28 import voxelman.world.mesh.utils : FaceSide, oppFaceSides;
29 
30 final class WagonTool : ITool
31 {
32 	ClientWorld clientWorld;
33 	BlockEntityManager blockEntityManager;
34 	NetClientPlugin connection;
35 	WorldInteractionPlugin worldInteraction;
36 	GraphicsPlugin graphics;
37 
38 	// need to check for emptiness
39 	RailData data;
40 	RailPos railPos;
41 	bool placeAllowed;
42 	vec3 preciseHitPos;
43 
44 	float wagonAxisDistance = 8;
45 	float wagonRotation = 0;
46 
47 	this(ClientWorld clientWorld, BlockEntityManager blockEntityManager,
48 		NetClientPlugin connection, WorldInteractionPlugin worldInteraction,
49 		GraphicsPlugin graphics)
50 	{
51 		this.clientWorld = clientWorld;
52 		this.blockEntityManager = blockEntityManager;
53 		this.connection = connection;
54 		this.worldInteraction = worldInteraction;
55 		this.graphics = graphics;
56 		name = "entity.wagon_tool";
57 	}
58 
59 	override void onUpdate()
60 	{
61 		auto plane = Plane(vec3(0,1,0), worldInteraction.hitPosition.y);
62 		vec3 camPos = worldInteraction.cameraPos;
63 		plane.intersectsLine(camPos, camPos+worldInteraction.cameraTraget, preciseHitPos);
64 		preciseHitPos.y = worldInteraction.hitPosition.y;
65 		graphics.debugText.putfln("hit %s", worldInteraction.hitPosition);
66 		graphics.debugText.putfln("precise hit %s", preciseHitPos);
67 
68 		railPos = RailPos(worldInteraction.blockPos);
69 		data = railAt(railPos);
70 	}
71 
72 	private RailData railAt(RailPos pos)
73 	{
74 		ushort targetEntityId = blockEntityManager.getId("rail");
75 		return getRailAt(pos, targetEntityId, clientWorld.worldAccess, clientWorld.entityAccess);
76 	}
77 
78 	// returns q the closest point to p on the line segment from a to b
79 	vec3 project_point_to_line_segment(vec3 a, vec3 b, vec3 p)
80 	{
81 		// vector from a to b
82 		vec3 AB = b - a;
83 
84 		// squared distance from a to b
85 		float AB_squared = dot(AB, AB);
86 
87 		if(AB_squared == 0)
88 		{
89 			// a and b are the same point
90 			return a;
91 		}
92 		else
93 		{
94 			// vector from a to p
95 			vec3 Ap = p - a;
96 
97 			// from http://stackoverflow.com/questions/849211/
98 			// Consider the line extending the segment, parameterized as a + t (b - a)
99 			// We find projection of point p onto the line.
100 			// It falls where t = [(p-a) . (b-a)] / |b-a|^2
101 			float t = dot(Ap, AB) / AB_squared;
102 
103 			if (t < 0.0f)
104 			{
105 				// "Before" a on the line, just return a
106 				return a;
107 			}
108 			else if (t > 1.0f)
109 			{
110 				// "After" b on the line, just return b
111 				return b;
112 			}
113 			else
114 			{
115 				// projection lines "inbetween" a and b on the line
116 				return a + t * AB;
117 			}
118 		}
119 	}
120 
121 	float point_time_on_line_segment(vec3 a, vec3 b, vec3 p)
122 	{
123 		vec3 AB = b - a;
124 		float AB_squared = dot(AB, AB);
125 		if(AB_squared == 0) return 0;
126 		else return dot(p - a, AB) / AB_squared;
127 	}
128 
129 	vec3 pointSize = vec3(0.5f,0.5f,0.5f);
130 	vec3 pointOffset = vec3(0.25f,0.25f,0.25f);
131 
132 	// Assumes non-empty rail
133 	// Virtual wagon is placed at user's cursor position and has 'wagonRotation' rotation.
134 	// The goal is to find positions of wagon's ends to be placed.
135 	// Algorithm:
136 	// 1. For hovered rail tile find closest segment
137 	// 2. For best candidate, for each end calc:
138 	//    - position and rail (vec3, RailPos, RailSegment, side of tile and segment end index)
139 	//    - error (diff between end's position and virtual wagon position)
140 	// 3. Iteratively try to build wagon placement closest to virtual wagon
141 	// 3.1. If we hit the end of segment when placing one of the ends,
142 	//      look into attached segments, replacing current selected segment with closer one
143 	//      Repeat until we can place two ends
144 	vec3[2] findWagonPlacement()
145 	{
146 		assert(!data.empty);
147 
148 		vec3 railWorldPos = vec3(railPos.toBlockWorldPos.xyz);
149 		// wagon center
150 		vec3 wagonC = vec3(preciseHitPos);
151 
152 		vec3 vertOff = vec3(0,1,0);
153 		vec3 wagonVector = vec3(cos(wagonRotation), 0, sin(wagonRotation));
154 		vec3 wagonA = vertOff + wagonC - wagonVector * wagonAxisDistance/2;
155 		vec3 wagonB = vertOff + wagonC + wagonVector * wagonAxisDistance/2;
156 		vec3[2] wagonPoints = [wagonA-vertOff, wagonB-vertOff];
157 		graphics.debugText.putfln(" wagA %s wagB %s", wagonPoints[0], wagonPoints[1]);
158 
159 		// Shadow wagon
160 		graphics.debugBatch.putLine(wagonA, wagonB, Color4ub(0,0,0,64));
161 		graphics.debugBatch.putCube(wagonA-pointOffset, pointSize, Color4ub(0,0,0,64), true);
162 		graphics.debugBatch.putCube(wagonB-pointOffset, pointSize, Color4ub(0,0,0,64), true);
163 		graphics.debugText.putfln("wagon rotation %.1f", radtodeg(wagonRotation));
164 
165 		vec3[2] resultPos;
166 		vec3[2] resultInnerPos;
167 		RailPos[2] resultRail = [railPos, railPos];
168 		RailSegment[2] resultSegment;
169 		FaceSide[2] resultSides;
170 		size_t[2] resultOuterIndex;
171 		bool[2] canExtend = [true, true];
172 
173 		// errors say how far is calculated position to the given one
174 		float segmentError = float.infinity;
175 		float[2] errors;
176 
177 		// Find best central segment
178 		foreach(i, segment; data.getSegments)
179 		{
180 			FaceSide[2] sides = segmentInfos[segment].sides;
181 			// Set first approximation
182 			vec3 railA = railWorldPos + railTileConnectionPoints[sides[0]]-vec3(0f,0.5f,0f);
183 			vec3 railB = railWorldPos + railTileConnectionPoints[sides[1]]-vec3(0f,0.5f,0f);
184 			//graphics.debugText.putfln(" railA %s railB %s", railA, railB);
185 
186 			float errorAA = distancesqr(railA, wagonPoints[0]);
187 			float errorBB = distancesqr(railB, wagonPoints[1]);
188 
189 			float errorAB = distancesqr(railA, wagonPoints[1]);
190 			float errorBA = distancesqr(railB, wagonPoints[0]);
191 
192 			float[2] newErrors;
193 			float newError;
194 
195 			float error0 = errorAA + errorBB;
196 			float error1 = errorAB + errorBA;
197 
198 			if (error0 < error1)
199 			{
200 				newError = error0;
201 				newErrors = [errorAA, errorBB];
202 			}
203 			else
204 			{
205 				newError = error1;
206 				newErrors = [errorAB, errorBA];
207 			}
208 
209 			//graphics.debugText.putfln(" AA %s AB %s BB %s BA %s", errorAA, errorAB, errorBB, errorBA);
210 			//graphics.debugText.putfln(" error of %s is %.1f", segment, newError);
211 
212 			void setSegment(int idx0, int idx1)
213 			{
214 				resultPos[idx0] = railA;
215 				resultPos[idx1] = railB;
216 				resultInnerPos[idx0] = railB;
217 				resultInnerPos[idx1] = railA;
218 				resultSides[idx0] = sides[0];
219 				resultSides[idx1] = sides[1];
220 				errors[idx0] = newErrors[0];
221 				errors[idx1] = newErrors[1];
222 				segmentError = newError;
223 				resultOuterIndex[idx0] = 0;
224 				resultOuterIndex[idx1] = 1;
225 				resultSegment = [segment, segment];
226 			}
227 
228 			if (newError < segmentError)
229 			{
230 				if (error0 < error1) setSegment(0, 1);
231 				else setSegment(1, 0);
232 			}
233 		}
234 
235 		immutable float wagonLenSqr = wagonAxisDistance * wagonAxisDistance;
236 
237 		// Returns true if extension was successfull
238 		// Returns false otherwise (i.e. no rail in given direction)
239 		bool extendRails(size_t nextIdx)
240 		{
241 			RailPos adjacentPos = resultRail[nextIdx].posInDirection(resultSides[nextIdx]);
242 			RailData adjacentData = railAt(adjacentPos);
243 			FaceSide connectedViaSide = oppFaceSides[resultSides[nextIdx]];
244 			vec3 adjRailWorldPos = vec3(adjacentPos.toBlockWorldPos.xyz);
245 			graphics.debugText.putfln(" - extend %s in %s at %s", nextIdx, resultSides[nextIdx], adjacentPos);
246 
247 			float pointError = float.infinity;
248 
249 			bool success = false;
250 
251 			// TODO smooth connection checking
252 			foreach(adjSegment; adjacentData.getSegments)
253 			{
254 				graphics.debugText.putfln("   - segm side %s conn %s", adjSegment, segmentInfos[adjSegment].sideConnections[connectedViaSide]);
255 				if (segmentInfos[adjSegment].sideConnections[connectedViaSide])
256 				{
257 					size_t innerIndex = segmentInfos[adjSegment].sideIndicies[connectedViaSide];
258 					size_t outerIndex = 1 - innerIndex;
259 					FaceSide[2] sides = segmentInfos[adjSegment].sides;
260 					vec3 railInnerEnd = adjRailWorldPos + railTileConnectionPoints[sides[innerIndex]]-vec3(0f,0.5f,0f);
261 					vec3 railOuterEnd = adjRailWorldPos + railTileConnectionPoints[sides[outerIndex]]-vec3(0f,0.5f,0f);
262 					vec3 closestPoint = project_point_to_line_segment(railInnerEnd, railOuterEnd, wagonPoints[nextIdx]);
263 					float endError = distancesqr(closestPoint, wagonPoints[nextIdx]);
264 
265 					void setPoint()
266 					{
267 						// set attributes of outer end of the rail
268 						resultOuterIndex[nextIdx] = outerIndex;
269 						graphics.debugText.putfln("   - set side %s", outerIndex);
270 						resultInnerPos[nextIdx] = resultPos[nextIdx];
271 						resultPos[nextIdx] = railOuterEnd;
272 						resultRail[nextIdx] = adjacentPos;
273 						resultSides[nextIdx] = sides[outerIndex];
274 						segmentError = errors[1-nextIdx] + endError;
275 						errors[nextIdx] = endError;
276 						pointError = endError;
277 						resultSegment[nextIdx] = adjSegment;
278 						success = true;
279 					}
280 
281 					if (endError < pointError) setPoint();
282 				}
283 			}
284 
285 			graphics.debugText.putfln(" - success %s", success);
286 
287 			canExtend[nextIdx] = success;
288 
289 			return success;
290 		}
291 
292 		// Reports true when wagon position is found
293 		// Otherwise a new segment needs to be attached to one of the ends
294 		bool tryPlaceWagon()
295 		{
296 			// Can potentially place
297 			if (wagonLenSqr <= distancesqr(resultPos[0], resultPos[1]))
298 			{
299 				graphics.debugText.putfln(" - place success %s <= %s", wagonLenSqr, distancesqr(resultPos[0], resultPos[1]));
300 
301 				vec3 inner0 = resultInnerPos[0];
302 				vec3 outer0 = resultPos[0];
303 				vec3 inner1 = resultInnerPos[1];
304 				vec3 outer1 = resultPos[1];
305 
306 				if (resultOuterIndex[0] == 0) swap(inner0, outer0);
307 				if (resultOuterIndex[1] == 0) swap(inner1, outer1);
308 
309 				graphics.debugBatch.putCube(vertOff*1.5f + outer0-pointOffset, pointSize, Color4ub(0,100,100,255), true);
310 				graphics.debugBatch.putCube(vertOff*1.5f + inner0-pointOffset, pointSize, Color4ub(0,200,200,255), true);
311 				graphics.debugBatch.putCube(vertOff*2.5f + inner1-pointOffset, pointSize, Color4ub(100,100,0,255), true);
312 				graphics.debugBatch.putCube(vertOff*2.5f + outer1-pointOffset, pointSize, Color4ub(200,200,0,255), true);
313 
314 				float pos0t = equation2(inner0.xz, outer0.xz, preciseHitPos.xz, wagonAxisDistance/2.0);
315 				vec3 pos0 = lerp(inner0, outer0, pos0t);
316 
317 				graphics.debugText.putfln("lerp 0 %s ext %s", pos0t, canExtend[0]);
318 				if ((pos0t < 0 || pos0t > 1) && canExtend[0])
319 				{
320 					extendRails(0);
321 					return false;
322 				}
323 
324 				float pos1t = equation2(inner1.xz, outer1.xz, pos0.xz, wagonAxisDistance);
325 				vec3 pos1 = lerp(inner1, outer1, pos1t);
326 
327 				graphics.debugText.putfln("lerp 1 %s ext %s", pos1t, canExtend[1]);
328 				if ((pos1t < 0 || pos1t > 1) && canExtend[1])
329 				{
330 					extendRails(1);
331 					return false;
332 				}
333 
334 				resultPos = [pos0, pos1];
335 				graphics.debugText.putfln("lerp 0 %s 1 %s", pos0t, pos1t);
336 				graphics.debugText.putfln("final pos %s", resultPos);
337 				return true;
338 			}
339 			else
340 			{
341 				// Extend points to adjacent segments as nesessary
342 				size_t nextIdx;
343 				// fail, need more space
344 				// extend in direction closer to wagon center
345 				graphics.debugText.putfln(" - place fail");
346 				if (distancesqr(wagonC, resultPos[0]) < distancesqr(wagonC, resultPos[1]))
347 				{
348 					if (!extendRails(0)) extendRails(1);
349 				}
350 				else
351 				{
352 					if (!extendRails(1)) extendRails(0);
353 				}
354 				return false;
355 			}
356 		}
357 
358 		size_t iters;
359 		while(!tryPlaceWagon()) {
360 			if (iters++ > 10) break;
361 		}
362 
363 		// Best first segment
364 		graphics.debugBatch.putLine(vertOff + resultPos[0], vertOff + resultPos[1], Color4ub(0,255,0,255));
365 		graphics.debugBatch.putCube(vertOff + resultPos[0]-pointOffset, pointSize, Color4ub(255,0,0,255), true);
366 		graphics.debugBatch.putCube(vertOff + resultPos[1]-pointOffset, pointSize, Color4ub(0,255,0,255), true);
367 		return resultPos;
368 	}
369 
370 	override void onRender(GraphicsPlugin renderer)
371 	{
372 		void drawRailsAt(vec3 railWorldPos, RailSegment segment, Color4ub color)
373 		{
374 			FaceSide[2] sides = segmentInfos[segment].sides;
375 			vec3 pos0 = railWorldPos + railTileConnectionPoints[sides[0]];
376 			vec3 pos1 = railWorldPos + railTileConnectionPoints[sides[1]];
377 			renderer.debugBatch.putLine(pos0, pos1, color);
378 		}
379 
380 		void drawSegments()
381 		{
382 			vec3 railWorldPos = vec3(railPos.toBlockWorldPos.xyz);
383 
384 			// Segments of hovered rail tile
385 			foreach(i, segment; data.getSegments)
386 			{
387 				enum layerDist = 0.25f;
388 				drawRailsAt(railWorldPos + vec3(0, i*layerDist, 0), segment, colorsArray[i+2]);
389 
390 				FaceSide[2] sides = segmentInfos[segment].sides;
391 
392 				foreach(side; sides)
393 				{
394 					RailPos adjacentPos = railPos.posInDirection(side);
395 					RailData adjacentData = railAt(adjacentPos);
396 
397 					// side with which adjacent segment connects to main segment
398 					FaceSide connectedViaSide = oppFaceSides[side];
399 
400 					// Process all segments that connect to main segment
401 					foreach(adjSegment; adjacentData.getSegments)
402 					if (segmentInfos[adjSegment].sideConnections[connectedViaSide])
403 					{
404 						vec3 adjRailWorldPos = vec3(adjacentPos.toBlockWorldPos.xyz);
405 						drawRailsAt(adjRailWorldPos + vec3(0, i*layerDist, 0), adjSegment, colorsArray[i+2]);
406 					}
407 				}
408 			}
409 		}
410 
411 		if (!data.empty)
412 		{
413 			graphics.debugText.putfln("hover %s", railPos);
414 			vec3[2] ends = findWagonPlacement();
415 			float angle = -atan2(ends[1].z - ends[0].z, ends[1].x - ends[0].x);
416 			auto quat = rotationQuaternion(vec3(0,1,0), angle);
417 			vec3 wagonCenter = (ends[0] + ends[1]) * 0.5f + vec3(0,1,0);
418 			enum wagonWidth = 3.0f;
419 			enum wagonHeight = 4.0f;
420 			float wagonLength = wagonAxisDistance + 4.0f;
421 
422 			vec3 wagonSize = vec3(wagonLength, wagonHeight, wagonWidth);
423 			vec3 centerOffset = -vec3(wagonSize.x*0.5f, 0, wagonSize.z*0.5f);
424 
425 			import voxelman.geometry.cubeutils;
426 			putFilledBlock(renderer.debugBatch.triBuffer, wagonCenter, wagonSize, -vec3(0.5,0.5,0.5), quat, Color4ub(128, 128, 128, 255));
427 
428 			//drawSegments();
429 		}
430 	}
431 
432 	override void onSecondaryActionRelease()
433 	{
434 		if (!data.empty) connection.send(CreateWagonPacket(railPos));
435 	}
436 
437 	override void onRotateAction() {
438 		wagonRotation += 2*PI / 8;
439 		wagonRotation %= 2*PI;
440 	}
441 }
442 
443 float equation2(vec2 p1, vec2 p2, vec2 circlePos, float radius)
444 {
445 	return equation(p1 - circlePos, p2 - circlePos, radius);
446 }
447 
448 // for a circle at 0,0. calculate its intersection with a line segment
449 // described with p1, p2 and return a position along the line as [0; 1]
450 float equation(vec2 p1, vec2 p2, float radius)
451 {
452 	float dx = (p2.x - p1.x);
453 	float dy = (p2.y - p1.y);
454 	float a = dx*dx + dy*dy;
455 	float b = 2*(dy*p1.y + dx*p1.x);
456 	float c = p1.x*p1.x + p1.y*p1.y - radius*radius;
457 	float d = b*b - 4*a*c;
458 	float sqrt_d = sqrt(d);
459 	float t1 = (-b + sqrt_d) / (2*a);
460 	float t2 = (-b - sqrt_d) / (2*a);
461 	if (std_abs(t2 - 0.5f) < std_abs(t1 - 0.5f)) return t2;
462 	return t1;
463 	//if (t1 < 0 || t1 > 1) return t2;
464 	//return t1;
465 }
466 
467 // t position on rail, R length between wagon points
468 // x₁y₁, x₂y₂ rail ends
469 // (1) x² + y² = R²
470 // (2) x = x₁ + t(x₂-x₁)
471 // (3) y = y₁ + t(y₂-y₁)
472 // We substitute (2) and (3) into (1) and get quadratic equation
473 // t²((x₂-x₁)² + (y₂-y₁)²) + t2((y₂-y₁)y₂ + (x₁-x₁)x₂) + x₁ + y₁ - R² = 0
474 // solve for t
475 
476 // a = (x2 - x1)^2 + (y2 - y1)^2;
477 // b = 2 * ((y2 - y1)*y1 + (x2 - x1)*x1);
478 // c = x1^2 + y1^2 - R^2;
479 // d = b*b - 4*a*c;