Branch data Line data Source code
1 : : #pragma once
2 : :
3 : : /*
4 : : MIT License
5 : :
6 : : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
7 : :
8 : : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : of this software and associated documentation files (the "Software"), to deal
10 : : in the Software without restriction, including without limitation the rights
11 : : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : copies of the Software, and to permit persons to whom the Software is
13 : : furnished to do so, subject to the following conditions:
14 : :
15 : : The above copyright notice and this permission notice shall be included in all
16 : : copies or substantial portions of the Software.
17 : :
18 : : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : : SOFTWARE.
25 : : */
26 : :
27 : : // ezGL is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
28 : :
29 : : #include <map>
30 : : #include <string>
31 : : #include <memory>
32 : : #include "../ezScreen.hpp"
33 : :
34 : : namespace ez {
35 : : namespace gl {
36 : :
37 : : /* Base Canvas Vertex shader :
38 : : #version 430
39 : : layout(location = 0) in vec2 aPosition; // -1..+1 (quad fullscreen)
40 : :
41 : : uniform vec2 uScale; // NDC scale
42 : : uniform vec2 uOffset; // NDC offset (centre du quad)
43 : : uniform vec4 uColor;
44 : :
45 : : out vec4 vertColor;
46 : :
47 : : void main() {
48 : : gl_Position = vec4(aPosition * uScale + uOffset, 0.0, 1.0);
49 : : vertColor = uColor;
50 : : }
51 : : */
52 : :
53 : : /* Base Canvas Fragment shader :
54 : : #version 430
55 : :
56 : : layout(location = 0) out vec4 fragColor;
57 : :
58 : : in vec4 vertColor;
59 : :
60 : : void main(void) {
61 : : fragColor = vertColor;
62 : : }
63 : : */
64 : :
65 : : class Canvas {
66 : : public:
67 : : struct MouseDatas {
68 : : ez::fvec2 displayRect;
69 : : ez::fvec2 mousePos;
70 : : float mouseWheel{};
71 : : bool isPanButtonDown{};
72 : : };
73 : : struct Transform {
74 : : ez::fvec2 origin;
75 : : float scale{1.0f}; // px / unit� monde
76 : : float invScale{1.0f};
77 : : };
78 : : struct UniformTransform {
79 : : ez::fvec2 uScale{1.0f};
80 : : ez::fvec2 uOffset;
81 : : };
82 : :
83 : :
84 : : private:
85 : : bool m_isRenderingActive{true};
86 : : ez::fvec4 m_clearColor;
87 : :
88 : : ez::fvec4 m_displayRect;
89 : : ez::gl::FBOPipeLinePtr mp_fboPipeline;
90 : : ez::fvec2 m_fboSize;
91 : :
92 : : MouseDatas m_mouseDatas;
93 : : Transform m_transform;
94 : :
95 : : // Uniforms NDC pour le shader (uScale/uOffset)
96 : : UniformTransform m_uniformTransform;
97 : :
98 : : // Souris (interne)
99 : : ez::fvec2 m_lastMousePos;
100 : : bool m_isPanning{false};
101 : :
102 : : public:
103 : 0 : bool init(const ez::ivec4& vDisplayRect) {
104 : 0 : bool ret = true;
105 : 0 : m_displayRect = vDisplayRect;
106 : 0 : mp_fboPipeline = ez::gl::FBOPipeLine::create(vDisplayRect.z, vDisplayRect.w, 1, false, false);
107 : 0 : ret &= (mp_fboPipeline != nullptr);
108 : 0 : m_fboSize = m_displayRect.zw();
109 : 0 : return ret;
110 : 0 : }
111 : 0 : void unit() {}
112 : 0 : void startFrame(const MouseDatas& vMouseDatas) {
113 : 0 : if (m_mouseActions(vMouseDatas)) {
114 : 0 : m_computeTransform();
115 : 0 : }
116 : 0 : }
117 : :
118 : 0 : void endFrame() {}
119 : :
120 : 0 : bool resize(const ez::ivec4& vNewDisplayRect) {
121 : 0 : if (mp_fboPipeline->resize(vNewDisplayRect.z, vNewDisplayRect.w)) {
122 : 0 : m_displayRect = vNewDisplayRect;
123 : 0 : if (!m_fboSize.emptyOR()) {
124 : 0 : ez::fvec2 rescale = m_displayRect.zw() / m_fboSize;
125 : 0 : m_transform.origin *= rescale;
126 : 0 : m_transform.scale *= ez::mini(rescale.x, rescale.y);
127 : 0 : }
128 : 0 : m_fboSize = vNewDisplayRect.zw();
129 : 0 : m_computeTransform();
130 : 0 : return true;
131 : 0 : }
132 : 0 : return false;
133 : 0 : }
134 : :
135 : 0 : ez::fvec2 worldToLocal(const ez::fvec2& vWorldPos) const {
136 : 0 : return (vWorldPos - m_transform.origin) * m_transform.invScale;
137 : 0 : }
138 : :
139 : 0 : ez::fvec2 localToWorld(const ez::fvec2& vCanvasPos) const {
140 : 0 : return vCanvasPos * m_transform.scale + m_transform.origin;
141 : 0 : }
142 : :
143 : 0 : bool startOffscreenRender() {
144 : 0 : if (m_isRenderingActive) {
145 : 0 : clearBuffers(m_clearColor);
146 : 0 : if (mp_fboPipeline->bind()) {
147 : 0 : mp_fboPipeline->selectBuffers();
148 : 0 : return true;
149 : 0 : }
150 : 0 : }
151 : 0 : return false;
152 : 0 : }
153 : 0 : void endOffscreenRender() { mp_fboPipeline->unbind(); }
154 : :
155 : 0 : void blitOnScreen() {
156 : 0 : auto fbo_ptr = mp_fboPipeline->getFrontFBO().lock();
157 : 0 : if (fbo_ptr != nullptr) {
158 : 0 : fbo_ptr->blitOnScreen(
159 : 0 : m_displayRect.x, //
160 : 0 : m_displayRect.y, //
161 : 0 : m_displayRect.z, //
162 : 0 : m_displayRect.w, //
163 : 0 : 0,
164 : 0 : GL_COLOR_BUFFER_BIT,
165 : 0 : GL_NEAREST);
166 : 0 : }
167 : 0 : }
168 : 0 : void clearBuffers(const ez::fvec4& vColorPtr) { mp_fboPipeline->clearBuffer({vColorPtr.x, vColorPtr.y, vColorPtr.z, vColorPtr.w}); }
169 : 0 : bool drawUI() {
170 : 0 : #ifdef IMGUI_API
171 : 0 : #ifdef _DEBUG
172 : 0 : if (ImGui::CollapsingHeader("Debug##Renderer")) {
173 : 0 : ImGui::Text("Origin Px : %f,%f", m_transform.origin.x, m_transform.origin.y);
174 : 0 : ImGui::Text("FBO Size : %f,%f", m_fboSize.x, m_fboSize.y);
175 : 0 : ImGui::Text("Scale : %f", m_transform.scale);
176 : 0 : ImGui::Text("Sclae inv : %f", m_transform.invScale);
177 : 0 : ImGui::DragFloat2("Offset", &m_uniformTransform.uOffset.x);
178 : 0 : ImGui::DragFloat2("Scale", &m_uniformTransform.uScale.x);
179 : 0 : }
180 : 0 : #endif
181 : 0 : #endif
182 : 0 : return false;
183 : 0 : }
184 : :
185 : 0 : void updateTransform() { m_computeTransform(); }
186 : :
187 : 0 : void fitToContent(const ez::fAABB& vBoundingBox) {
188 : 0 : m_computeFitToContent( //
189 : 0 : vBoundingBox.lowerBound,
190 : 0 : vBoundingBox.upperBound,
191 : 0 : m_fboSize,
192 : 0 : m_transform.origin,
193 : 0 : m_transform.scale,
194 : 0 : m_transform.invScale);
195 : 0 : m_computeTransform();
196 : 0 : }
197 : :
198 : 0 : ez::fvec4& getBackgroundColorRef() { return m_clearColor; }
199 : 0 : const ez::fvec4& getBackgroundColor() const { return m_clearColor; }
200 : :
201 : 0 : MouseDatas& getMouseDatasRef() { return m_mouseDatas; }
202 : 0 : const MouseDatas& getMouseDatas() const { return m_mouseDatas; }
203 : :
204 : 0 : Transform& getTransfomRef() { return m_transform; }
205 : 0 : const Transform& getTransfom() const { return m_transform; }
206 : :
207 : 0 : UniformTransform& getUniformTransfomRef() { return m_uniformTransform; }
208 : 0 : const UniformTransform& getUniformTransfom() const { return m_uniformTransform; }
209 : :
210 : : private:
211 : 0 : bool m_mouseActions(const MouseDatas& vMouseDatas) {
212 : 0 : bool ret = false;
213 : 0 : const float steps = vMouseDatas.mouseWheel;
214 : 0 : if (fabsf(steps) > 0.0001f) {
215 : 0 : const float factor = powf(1.1f, steps);
216 : 0 : m_applyZoomAtMouse(factor, vMouseDatas.mousePos);
217 : 0 : ret = true;
218 : 0 : }
219 : 0 : if (vMouseDatas.isPanButtonDown) {
220 : 0 : const ez::fvec2 delta = vMouseDatas.mousePos - m_lastMousePos;
221 : 0 : m_isPanning = true;
222 : 0 : m_applyPanDrag(delta);
223 : 0 : ret = true;
224 : 0 : } else {
225 : 0 : m_isPanning = false;
226 : 0 : }
227 : 0 : m_lastMousePos = vMouseDatas.mousePos;
228 : 0 : return ret;
229 : 0 : }
230 : :
231 : 0 : void m_setScale(float v) {
232 : 0 : m_transform.scale = v;
233 : 0 : m_transform.invScale = (v != 0.0f) ? (1.0f / v) : 0.0f;
234 : 0 : }
235 : :
236 : 0 : void m_applyPanDrag(const ez::fvec2& dragPx) {
237 : 0 : m_transform.origin.x += dragPx.x;
238 : 0 : m_transform.origin.y += dragPx.y;
239 : 0 : }
240 : :
241 : 0 : void m_applyZoomAtMouse(float factor, const ez::fvec2& mousePx) {
242 : 0 : if (factor <= 0.0f) {
243 : 0 : return;
244 : 0 : }
245 : 0 : float newScale = m_transform.scale * factor;
246 : 0 : newScale = std::max(1e-6f, std::min(newScale, 1e6f));
247 : 0 : const float applied = (m_transform.scale > 0.0f) ? (newScale / m_transform.scale) : 1.0f;
248 : 0 : if (applied == 1.0f) {
249 : 0 : return;
250 : 0 : }
251 : 0 : m_transform.origin.x = m_transform.origin.x + (1.0f - applied) * (mousePx.x - m_transform.origin.x);
252 : 0 : m_transform.origin.y = m_transform.origin.y + (1.0f - applied) * (mousePx.y - m_transform.origin.y);
253 : 0 : m_setScale(newScale);
254 : 0 : }
255 : :
256 : : // computation of uScale/uOffset (NDC) for
257 : : // gl_Position = vec4(aPos * uScale + uOffset, 0, 1)
258 : 0 : void m_computeTransform() {
259 : 0 : const float VW = m_fboSize.x;
260 : 0 : const float VH = m_fboSize.y;
261 : 0 : if (VW <= 0.0f || VH <= 0.0f) {
262 : 0 : m_transform = {};
263 : 0 : return;
264 : 0 : }
265 : 0 : m_setScale(m_transform.scale);
266 : 0 : m_uniformTransform.uScale.x = (2.0f * m_transform.scale) / VW;
267 : 0 : m_uniformTransform.uOffset.x = (2.0f * m_transform.origin.x) / VW - 1.0f;
268 : 0 : // Y-down (framebuffer in top-left, classic UI)
269 : 0 : m_uniformTransform.uScale.y = -(2.0f * m_transform.scale) / VH;
270 : 0 : m_uniformTransform.uOffset.y = 1.0f - (2.0f * m_transform.origin.y) / VH;
271 : 0 : }
272 : :
273 : : // Fit "contain" d'un AABB monde dans un framebuffer en pixels.
274 : : // Mapping utilis� : screen = world * scale + originPx
275 : : void m_computeFitToContent(
276 : : const ez::fvec2& vWorldMin,
277 : : const ez::fvec2& vWorldMax,
278 : : const ez::fvec2& vFramebufferSizePx,
279 : : ez::fvec2& voOriginPx,
280 : : float& voScale,
281 : 0 : float& voInvScale) {
282 : 0 : const float W = vWorldMax.x - vWorldMin.x;
283 : 0 : const float H = vWorldMax.y - vWorldMin.y;
284 : 0 : const float VW = vFramebufferSizePx.x;
285 : 0 : const float VH = vFramebufferSizePx.y;
286 : 0 :
287 : 0 : if (W <= 0.0f || H <= 0.0f || VW <= 0.0f || VH <= 0.0f) {
288 : 0 : voOriginPx = {0.0f, 0.0f};
289 : 0 : voScale = 1.0f;
290 : 0 : voInvScale = 1.0f;
291 : 0 : return;
292 : 0 : }
293 : 0 :
294 : 0 : const float sx = VW / W;
295 : 0 : const float sy = VH / H;
296 : 0 : const float s = (sx < sy) ? sx : sy;
297 : 0 :
298 : 0 : const float padX = 0.5f * (VW - W * s);
299 : 0 : const float padY = 0.5f * (VH - H * s);
300 : 0 :
301 : 0 : voOriginPx.x = padX - vWorldMin.x * s;
302 : 0 : voOriginPx.y = padY - vWorldMin.y * s;
303 : 0 : voScale = s;
304 : 0 : voInvScale = (s != 0.0f) ? (1.0f / s) : 0.0f;
305 : 0 : }
306 : : };
307 : :
308 : : } // namespace gl
309 : : } // namespace ez
|