Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ModernScenePresentation.hpp
Go to the documentation of this file.
1#pragma once
3#include "LogicalViewport.hpp"
4#include "World3DRenderer.hpp"
5#include <algorithm>
6#include <cmath>
7#include <limits>
8#include <span>
9
10namespace monopoly::engine
11{
13 const sequence::Matrix3D& world)
14 {
15 data::MeshBounds result;
16 result.minimum.fill(std::numeric_limits<float>::max());
17 result.maximum.fill(-std::numeric_limits<float>::max());
18 for(unsigned corner=0;corner<8;++corner)
19 {
20 std::array<float,3> p{};
21 for(unsigned axis=0;axis<3;++axis)p[axis]=(corner&(1U<<axis))?local.maximum[axis]:local.minimum[axis];
22 for(unsigned axis=0;axis<3;++axis)
23 {
24 const float value=p[0]*world.values[axis]+p[1]*world.values[4+axis]+p[2]*world.values[8+axis]+world.values[12+axis];
25 result.minimum[axis]=std::min(result.minimum[axis],value);result.maximum[axis]=std::max(result.maximum[axis],value);
26 }
27 }
28 return result;
29 }
30 // Unit preset directions are linearly blended by the retail controller.
31 // Opposing directions shorten that blend; normalization at projection time
32 // otherwise turns a low lateral travel into an extreme overhead closeup.
33 // Retreat only the rendered eye along its original ground-target ray.
35 float groundY) noexcept
36 {
37 if(!std::isfinite(groundY) || !std::isfinite(original.fieldOfView) ||
38 original.fieldOfView<=0 || original.fieldOfView>=3.14159265F ||
39 !std::isfinite(original.nearPlane) || original.nearPlane<=0 ||
40 !std::isfinite(original.farPlane) || original.farPlane<=original.nearPlane)return original;
41 for(unsigned axis=0;axis<3;++axis)
42 if(!std::isfinite(original.location[axis]) || !std::isfinite(original.forward[axis]) ||
43 !std::isfinite(original.up[axis]))return original;
44 if(original.forward[1]>=-0.00001F)return original;
45 const float magnitude=std::hypot(original.forward[0],original.forward[1],original.forward[2]);
46 // Float-encoded unit presets retain their exact original bytes.
47 if(!std::isfinite(magnitude) || magnitude<=0.000001F || magnitude>=1-0.00001F)return original;
48 const float parameter=(groundY-original.location[1])/original.forward[1];
49 if(!std::isfinite(parameter) || parameter<=0)return original;
50 const float retreat=parameter*(1/magnitude-1);
51 if(!std::isfinite(retreat))return original;
52 auto adjusted=original;
53 for(unsigned axis=0;axis<3;++axis)
54 {
55 adjusted.location[axis]-=original.forward[axis]*retreat;
56 if(!std::isfinite(adjusted.location[axis]))return original;
57 }
58 return adjusted;
59 }
60 // Caller supplies only loaded building bounds, in the camera's world frame.
61 // This value-only adjustment never feeds back into the retail camera controller.
63 std::span<const data::MeshBounds> buildings, float groundY,
64 float clearance, float influenceRadius) noexcept
65 {
66 if (!std::isfinite(groundY) || !std::isfinite(clearance) || clearance <= 0 ||
67 !std::isfinite(influenceRadius) || influenceRadius <= 0 ||
68 !std::isfinite(original.nearPlane) || original.nearPlane <= 0 ||
69 !std::isfinite(original.farPlane) || original.farPlane <= original.nearPlane ||
70 !std::isfinite(original.fieldOfView) || original.fieldOfView <= 0) return original;
71 for (unsigned axis=0; axis<3; ++axis)
72 if (!std::isfinite(original.location[axis]) || !std::isfinite(original.forward[axis]) ||
73 !std::isfinite(original.up[axis])) return original;
74 // Preserve the exact original board-plane aim. Horizontal/upward views
75 // have no forward board intersection and intentionally retain their camera.
76 if (original.forward[1] >= -0.00001F) return original;
77 const float distance=(groundY-original.location[1])/original.forward[1];
78 if (!std::isfinite(distance) || distance <= 0) return original;
79 float lift=0;
80 for (const auto& bounds : buildings)
81 {
82 bool valid=true;
83 for (unsigned axis=0; axis<3; ++axis)
84 valid=valid && std::isfinite(bounds.minimum[axis]) &&
85 std::isfinite(bounds.maximum[axis]) && bounds.minimum[axis] <= bounds.maximum[axis];
86 if (!valid || bounds.maximum[1] <= groundY) continue;
87 const float dx=std::max({bounds.minimum[0]-original.location[0],0.0F,
88 original.location[0]-bounds.maximum[0]});
89 const float dz=std::max({bounds.minimum[2]-original.location[2],0.0F,
90 original.location[2]-bounds.maximum[2]});
91 const float outside=std::hypot(dx,dz);
92 if (!std::isfinite(outside) || outside >= influenceRadius) continue;
93 const float t=1-outside/influenceRadius;
94 const float weight=t*t*(3-2*t);
95 const float needed=bounds.maximum[1]+std::max(clearance,original.nearPlane)-original.location[1];
96 if (!std::isfinite(needed)) return original;
97 lift=std::max(lift,std::max(0.0F,needed)*weight);
98 }
99 if (lift == 0) return original;
100 auto result=original;
101 result.location[1]+=lift;
102 std::array<float,3> aim{};
103 float lengthSquared=0;
104 for (unsigned axis=0; axis<3; ++axis)
105 {
106 aim[axis]=original.forward[axis]*distance;
107 if (axis==1) aim[axis]-=lift;
109 }
110 if (!std::isfinite(result.location[1]) || !std::isfinite(lengthSquared) ||
111 lengthSquared <= 0) return original;
112 const float length=std::sqrt(lengthSquared);
113 for (unsigned axis=0; axis<3; ++axis) result.forward[axis]=aim[axis]/length;
114 return result;
115 }
116 inline float modernBoardControlReservation(const World3DRect& viewport,
117 int pixelWidth, int pixelHeight, int controlsTop) noexcept
118 {
119 const auto ui=logicalviewport::makeTransform(pixelWidth,pixelHeight);
120 const auto world=logicalviewport::makeWorld3DTransform(pixelWidth,pixelHeight);
121 if(!ui.valid() || !world.valid() || viewport.empty())return 0;
122 const auto pixels=logicalviewport::logicalToPixelRect(world,
123 {double(viewport.left),double(viewport.top),double(viewport.right-viewport.left),
124 double(viewport.bottom-viewport.top)});
125 if(pixels.height<=0)return 0;
126 const double uiTop=ui.offsetY+ui.scale*controlsTop;
127 return float(std::clamp((pixels.y+pixels.height-uiTop)/pixels.height,0.0,0.4));
128 }
129 // Fit measured board bounds in the configured viewport while retaining authored roll.
131 const World3DCamera& original, float aspect, float bottomReservedFraction = 0) noexcept
132 {
133 auto dot=[](const auto& a,const auto& b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];};
134 if(!std::isfinite(aspect) || aspect<=0 || !std::isfinite(bottomReservedFraction) || !std::isfinite(original.nearPlane) ||
135 !std::isfinite(original.farPlane) || original.nearPlane<=0 ||
136 original.farPlane<=original.nearPlane)return original;
137 std::array<float,3> target{},forward=original.forward,up=original.up;
138 for(unsigned axis=0;axis<3;++axis)
139 {
140 if(!std::isfinite(bounds.minimum[axis]) || !std::isfinite(bounds.maximum[axis]) ||
141 bounds.maximum[axis]<bounds.minimum[axis] || !std::isfinite(original.location[axis]) ||
142 !std::isfinite(forward[axis]) || !std::isfinite(up[axis]))return original;
143 target[axis]=(bounds.minimum[axis]+bounds.maximum[axis])*.5F;
144 }
145 const float forwardLength=std::sqrt(dot(forward,forward));
146 if(!std::isfinite(forwardLength) || forwardLength<=.000001F)return original;
147 for(auto& value:forward)value/=forwardLength;
148 const float parallel=dot(up,forward);
149 for(unsigned axis=0;axis<3;++axis)up[axis]-=forward[axis]*parallel;
150 const float upLength=std::sqrt(dot(up,up));
151 if(!std::isfinite(upLength) || upLength<=.000001F)return original;
152 for(auto& value:up)value/=upLength;
153 const std::array<float,3> right{up[1]*forward[2]-up[2]*forward[1],
154 up[2]*forward[0]-up[0]*forward[2],up[0]*forward[1]-up[1]*forward[0]};
155 std::array<float,3> delta{};
156 for(unsigned axis=0;axis<3;++axis)delta[axis]=target[axis]-original.location[axis];
157 const float distance=dot(delta,forward);
158 if(!std::isfinite(distance) || distance<=original.nearPlane)return original;
159 auto camera=original;
160 float fittedTan=0;
161 for(unsigned iteration=0;iteration<8;++iteration)
162 {
163 for(unsigned axis=0;axis<3;++axis)camera.location[axis]=target[axis]-forward[axis]*distance;
164 float minX=std::numeric_limits<float>::max(),minY=minX,maxX=-minX,maxY=-minX;
165 for(unsigned corner=0;corner<8;++corner)
166 {
167 std::array<float,3> relative{};
168 for(unsigned axis=0;axis<3;++axis)relative[axis]=
169 ((corner&(1U<<axis))?bounds.maximum[axis]:bounds.minimum[axis])-camera.location[axis];
170 const float z=dot(relative,forward);
171 if(!std::isfinite(z) || z<=original.nearPlane || z>=original.farPlane)return original;
172 const float x=dot(relative,right)/z,y=dot(relative,up)/z;
173 minX=std::min(minX,x);maxX=std::max(maxX,x);minY=std::min(minY,y);maxY=std::max(maxY,y);
174 }
175 fittedTan=std::max(std::max(std::abs(minX),std::abs(maxX)),
176 std::max(std::abs(minY),std::abs(maxY))*aspect);
177 if(iteration<7)for(unsigned axis=0;axis<3;++axis)
178 target[axis]+=distance*((minX+maxX)*.5F*right[axis]+(minY+maxY)*.5F*up[axis]);
179 }
180 if(!std::isfinite(fittedTan) || fittedTan<=0)return original;
181 const float reserved=std::clamp(bottomReservedFraction,0.0F,0.4F);
182 const float horizontalTan=fittedTan*1.08F/(1-reserved);
183 camera.fieldOfView=2*std::atan(horizontalTan);
184 for(unsigned axis=0;axis<3;++axis)
185 camera.location[axis]-=up[axis]*distance*(horizontalTan/aspect)*reserved;
186 return camera;
187 }
188 // Presentation only: measured immutable geometry; no sequencer/game state.
190 float aspect, float elevationDegrees = 48.0F, float yawDegrees = 8.0F,
191 float bottomReservedFraction = 0.0F)
192 {
193 const float rad = 0.01745329252F;
194 const float e = elevationDegrees * rad, a = yawDegrees * rad;
195 const float span = std::max(bounds.maximum[0]-bounds.minimum[0],
196 bounds.maximum[2]-bounds.minimum[2]);
197 const float distance = std::max(1.0F, span * 2.65F);
198 std::array<float,3> forward{-std::cos(e)*std::sin(a),-std::sin(e),std::cos(e)*std::cos(a)};
199 std::array<float,3> right{std::cos(a),0,std::sin(a)};
200 std::array<float,3> up{-std::sin(e)*std::sin(a),std::cos(e),std::sin(e)*std::cos(a)};
201 auto dot=[](const auto& x,const auto& y){return x[0]*y[0]+x[1]*y[1]+x[2]*y[2];};
202 std::array<float,3> target{};
203 for(unsigned axis=0;axis<3;++axis) target[axis]=(bounds.minimum[axis]+bounds.maximum[axis])*.5F;
204 World3DCamera camera;
205 camera.forward=forward;
206 camera.up={0,1,0};
207 camera.nearPlane=std::max(.01F,span*.015F);camera.farPlane=distance+span*8;
208 float fittedTan=0;
209 // Correct the perspective silhouette's screen centroid, rather than
210 // fitting its world centroid and leaving a large empty band above it.
211 for(unsigned iteration=0;iteration<8;++iteration)
212 {
213 for(unsigned axis=0;axis<3;++axis)camera.location[axis]=target[axis]-forward[axis]*distance;
214 float minX=std::numeric_limits<float>::max(),minY=minX,maxX=-minX,maxY=-minX;
215 for(unsigned corner=0;corner<8;++corner)
216 {
217 std::array<float,3> rel{};
218 for(unsigned axis=0;axis<3;++axis)rel[axis]=((corner&(1U<<axis))?bounds.maximum[axis]:bounds.minimum[axis])-camera.location[axis];
219 const float z=dot(rel,forward),x=dot(rel,right)/z,y=dot(rel,up)/z;
220 minX=std::min(minX,x);maxX=std::max(maxX,x);minY=std::min(minY,y);maxY=std::max(maxY,y);
221 }
222 fittedTan=std::max(std::max(std::abs(minX),std::abs(maxX)),
223 std::max(std::abs(minY),std::abs(maxY))*aspect);
224 if(iteration<7)for(unsigned axis=0;axis<3;++axis)
225 target[axis]+=distance*((minX+maxX)*.5F*right[axis]+(minY+maxY)*.5F*up[axis]);
226 }
227 const float reserved=std::clamp(bottomReservedFraction,0.0F,0.4F);
228 const float horizontalTan=fittedTan*1.08F/(1.0F-reserved);
229 camera.fieldOfView=2*std::atan(horizontalTan);
230 // The retail controls occupy the bottom of the full 3D viewport.
231 // Shift the framed board above that overlay without changing UI or CNK.
232 for(unsigned axis=0;axis<3;++axis)
233 camera.location[axis]-=up[axis]*distance*(horizontalTan/aspect)*reserved;
234 return camera;
235 }
237 {
238 World3DLighting lighting;
239 lighting.ambient={.24F,.28F,.33F};
240 lighting.sun.enabled=true;lighting.sun.direction={.35F,-1,.25F};
241 lighting.sun.color={1.25F,1.16F,1.02F};
242 lighting.boardReflection.enabled=true;
243 lighting.boardReflection.direction={-.5F,-.4F,-.7F};
244 lighting.boardReflection.color={.13F,.19F,.25F};
245 return lighting;
246 }
247}
fs::path relative
std::atomic< float > right
unsigned index
int z
bool valid
std::uint16_t value
Definition Timers.cpp:16
data::MeshBounds presentationWorldBounds(const data::MeshBounds &local, const sequence::Matrix3D &world)
World3DLighting modernBoardPresentationLighting()
World3DCamera fitModernTopDownBoard(const data::MeshBounds &bounds, const World3DCamera &original, float aspect, float bottomReservedFraction=0) noexcept
World3DCamera modernBoardPresentationCamera(const data::MeshBounds &bounds, float aspect, float elevationDegrees=48.0F, float yawDegrees=8.0F, float bottomReservedFraction=0.0F)
World3DCamera avoidModernPresentationBuildings(const World3DCamera &original, std::span< const data::MeshBounds > buildings, float groundY, float clearance, float influenceRadius) noexcept
float modernBoardControlReservation(const World3DRect &viewport, int pixelWidth, int pixelHeight, int controlsTop) noexcept
World3DCamera preserveModernTravelFraming(const World3DCamera &original, float groundY) noexcept
Transform makeTransform(int targetWidth, int targetHeight) noexcept
PixelRect logicalToPixelRect(const Transform &transform, const LogicalRect &logicalRect) noexcept
Transform makeWorld3DTransform(int targetWidth, int targetHeight) noexcept
std::array< float, 3 > maximum
std::array< float, 3 > minimum
World3DDirectionalLight boardReflection
std::array< float, 16 > values