1 | /* |
2 | src/glutil.cpp -- Convenience classes for accessing OpenGL >= 3.x |
3 | |
4 | NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. |
5 | The widget drawing code is based on the NanoVG demo application |
6 | by Mikko Mononen. |
7 | |
8 | All rights reserved. Use of this source code is governed by a |
9 | BSD-style license that can be found in the LICENSE.txt file. |
10 | */ |
11 | |
12 | #include <nanogui/glutil.h> |
13 | |
14 | #if defined(WIN32) |
15 | # if !defined(__clang__) |
16 | # include <malloc.h> |
17 | # endif |
18 | #endif |
19 | |
20 | #include <iostream> |
21 | #include <fstream> |
22 | #include <Eigen/Geometry> |
23 | |
24 | NAMESPACE_BEGIN(nanogui) |
25 | |
26 | static GLuint createShader_helper(GLint type, const std::string &name, |
27 | const std::string &defines, |
28 | std::string shader_string) { |
29 | if (shader_string.empty()) |
30 | return (GLuint) 0; |
31 | |
32 | if (!defines.empty()) { |
33 | if (shader_string.length() > 8 && shader_string.substr(0, 8) == "#version" ) { |
34 | std::istringstream iss(shader_string); |
35 | std::ostringstream oss; |
36 | std::string line; |
37 | std::getline(iss, line); |
38 | oss << line << std::endl; |
39 | oss << defines; |
40 | while (std::getline(iss, line)) |
41 | oss << line << std::endl; |
42 | shader_string = oss.str(); |
43 | } else { |
44 | shader_string = defines + shader_string; |
45 | } |
46 | } |
47 | |
48 | GLuint id = glCreateShader(type); |
49 | const char *shader_string_const = shader_string.c_str(); |
50 | glShaderSource(id, 1, &shader_string_const, nullptr); |
51 | glCompileShader(id); |
52 | |
53 | GLint status; |
54 | glGetShaderiv(id, GL_COMPILE_STATUS, &status); |
55 | |
56 | if (status != GL_TRUE) { |
57 | char buffer[512]; |
58 | std::cerr << "Error while compiling " ; |
59 | if (type == GL_VERTEX_SHADER) |
60 | std::cerr << "vertex shader" ; |
61 | else if (type == GL_FRAGMENT_SHADER) |
62 | std::cerr << "fragment shader" ; |
63 | else if (type == GL_GEOMETRY_SHADER) |
64 | std::cerr << "geometry shader" ; |
65 | std::cerr << " \"" << name << "\":" << std::endl; |
66 | std::cerr << shader_string << std::endl << std::endl; |
67 | glGetShaderInfoLog(id, 512, nullptr, buffer); |
68 | std::cerr << "Error: " << std::endl << buffer << std::endl; |
69 | throw std::runtime_error("Shader compilation failed!" ); |
70 | } |
71 | |
72 | return id; |
73 | } |
74 | |
75 | bool GLShader::initFromFiles( |
76 | const std::string &name, |
77 | const std::string &vertex_fname, |
78 | const std::string &fragment_fname, |
79 | const std::string &geometry_fname) { |
80 | auto file_to_string = [](const std::string &filename) -> std::string { |
81 | if (filename.empty()) |
82 | return "" ; |
83 | std::ifstream t(filename); |
84 | return std::string((std::istreambuf_iterator<char>(t)), |
85 | std::istreambuf_iterator<char>()); |
86 | }; |
87 | |
88 | return init(name, |
89 | file_to_string(vertex_fname), |
90 | file_to_string(fragment_fname), |
91 | file_to_string(geometry_fname)); |
92 | } |
93 | |
94 | bool GLShader::init(const std::string &name, |
95 | const std::string &vertex_str, |
96 | const std::string &fragment_str, |
97 | const std::string &geometry_str) { |
98 | std::string defines; |
99 | for (auto def : mDefinitions) |
100 | defines += std::string("#define " ) + def.first + std::string(" " ) + def.second + "\n" ; |
101 | |
102 | glGenVertexArrays(1, &mVertexArrayObject); |
103 | mName = name; |
104 | mVertexShader = |
105 | createShader_helper(GL_VERTEX_SHADER, name, defines, vertex_str); |
106 | mGeometryShader = |
107 | createShader_helper(GL_GEOMETRY_SHADER, name, defines, geometry_str); |
108 | mFragmentShader = |
109 | createShader_helper(GL_FRAGMENT_SHADER, name, defines, fragment_str); |
110 | |
111 | if (!mVertexShader || !mFragmentShader) |
112 | return false; |
113 | if (!geometry_str.empty() && !mGeometryShader) |
114 | return false; |
115 | |
116 | mProgramShader = glCreateProgram(); |
117 | |
118 | glAttachShader(mProgramShader, mVertexShader); |
119 | glAttachShader(mProgramShader, mFragmentShader); |
120 | |
121 | if (mGeometryShader) |
122 | glAttachShader(mProgramShader, mGeometryShader); |
123 | |
124 | glLinkProgram(mProgramShader); |
125 | |
126 | GLint status; |
127 | glGetProgramiv(mProgramShader, GL_LINK_STATUS, &status); |
128 | |
129 | if (status != GL_TRUE) { |
130 | char buffer[512]; |
131 | glGetProgramInfoLog(mProgramShader, 512, nullptr, buffer); |
132 | std::cerr << "Linker error (" << mName << "): " << std::endl << buffer << std::endl; |
133 | mProgramShader = 0; |
134 | throw std::runtime_error("Shader linking failed!" ); |
135 | } |
136 | |
137 | return true; |
138 | } |
139 | |
140 | void GLShader::bind() { |
141 | glUseProgram(mProgramShader); |
142 | glBindVertexArray(mVertexArrayObject); |
143 | } |
144 | |
145 | GLint GLShader::attrib(const std::string &name, bool warn) const { |
146 | GLint id = glGetAttribLocation(mProgramShader, name.c_str()); |
147 | if (id == -1 && warn) |
148 | std::cerr << mName << ": warning: did not find attrib " << name << std::endl; |
149 | return id; |
150 | } |
151 | |
152 | void GLShader::setUniform(const std::string &name, const GLUniformBuffer &buf, bool warn) { |
153 | GLuint blockIndex = glGetUniformBlockIndex(mProgramShader, name.c_str()); |
154 | if (blockIndex == GL_INVALID_INDEX) { |
155 | if (warn) |
156 | std::cerr << mName << ": warning: did not find uniform buffer " << name << std::endl; |
157 | return; |
158 | } |
159 | glUniformBlockBinding(mProgramShader, blockIndex, buf.getBindingPoint()); |
160 | } |
161 | |
162 | GLint GLShader::uniform(const std::string &name, bool warn) const { |
163 | GLint id = glGetUniformLocation(mProgramShader, name.c_str()); |
164 | if (id == -1 && warn) |
165 | std::cerr << mName << ": warning: did not find uniform " << name << std::endl; |
166 | return id; |
167 | } |
168 | |
169 | void GLShader::uploadAttrib(const std::string &name, size_t size, int dim, |
170 | uint32_t compSize, GLuint glType, bool integral, |
171 | const void *data, int version) { |
172 | int attribID = 0; |
173 | if (name != "indices" ) { |
174 | attribID = attrib(name); |
175 | if (attribID < 0) |
176 | return; |
177 | } |
178 | |
179 | GLuint bufferID; |
180 | auto it = mBufferObjects.find(name); |
181 | if (it != mBufferObjects.end()) { |
182 | Buffer &buffer = it->second; |
183 | bufferID = it->second.id; |
184 | buffer.version = version; |
185 | buffer.size = (GLuint) size; |
186 | buffer.compSize = compSize; |
187 | } else { |
188 | glGenBuffers(1, &bufferID); |
189 | Buffer buffer; |
190 | buffer.id = bufferID; |
191 | buffer.glType = glType; |
192 | buffer.dim = dim; |
193 | buffer.compSize = compSize; |
194 | buffer.size = (GLuint) size; |
195 | buffer.version = version; |
196 | mBufferObjects[name] = buffer; |
197 | } |
198 | size_t totalSize = size * (size_t) compSize; |
199 | |
200 | if (name == "indices" ) { |
201 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferID); |
202 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalSize, data, GL_DYNAMIC_DRAW); |
203 | } else { |
204 | glBindBuffer(GL_ARRAY_BUFFER, bufferID); |
205 | glBufferData(GL_ARRAY_BUFFER, totalSize, data, GL_DYNAMIC_DRAW); |
206 | if (size == 0) { |
207 | glDisableVertexAttribArray(attribID); |
208 | } else { |
209 | glEnableVertexAttribArray(attribID); |
210 | glVertexAttribPointer(attribID, dim, glType, integral, 0, 0); |
211 | } |
212 | } |
213 | } |
214 | |
215 | void GLShader::downloadAttrib(const std::string &name, size_t size, int /* dim */, |
216 | uint32_t compSize, GLuint /* glType */, void *data) { |
217 | auto it = mBufferObjects.find(name); |
218 | if (it == mBufferObjects.end()) |
219 | throw std::runtime_error("downloadAttrib(" + mName + ", " + name + ") : buffer not found!" ); |
220 | |
221 | const Buffer &buf = it->second; |
222 | if (buf.size != size || buf.compSize != compSize) |
223 | throw std::runtime_error(mName + ": downloadAttrib: size mismatch!" ); |
224 | |
225 | size_t totalSize = size * (size_t) compSize; |
226 | |
227 | if (name == "indices" ) { |
228 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf.id); |
229 | glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, totalSize, data); |
230 | } else { |
231 | glBindBuffer(GL_ARRAY_BUFFER, buf.id); |
232 | glGetBufferSubData(GL_ARRAY_BUFFER, 0, totalSize, data); |
233 | } |
234 | } |
235 | |
236 | void GLShader::shareAttrib(const GLShader &otherShader, const std::string &name, const std::string &_as) { |
237 | std::string as = _as.length() == 0 ? name : _as; |
238 | auto it = otherShader.mBufferObjects.find(name); |
239 | if (it == otherShader.mBufferObjects.end()) |
240 | throw std::runtime_error("shareAttribute(" + otherShader.mName + ", " + name + "): attribute not found!" ); |
241 | const Buffer &buffer = it->second; |
242 | |
243 | if (name != "indices" ) { |
244 | int attribID = attrib(as); |
245 | if (attribID < 0) |
246 | return; |
247 | glEnableVertexAttribArray(attribID); |
248 | glBindBuffer(GL_ARRAY_BUFFER, buffer.id); |
249 | glVertexAttribPointer(attribID, buffer.dim, buffer.glType, buffer.compSize == 1 ? GL_TRUE : GL_FALSE, 0, 0); |
250 | } else { |
251 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.id); |
252 | } |
253 | } |
254 | |
255 | void GLShader::invalidateAttribs() { |
256 | for (auto &buffer : mBufferObjects) |
257 | buffer.second.version = -1; |
258 | } |
259 | |
260 | void GLShader::freeAttrib(const std::string &name) { |
261 | auto it = mBufferObjects.find(name); |
262 | if (it != mBufferObjects.end()) { |
263 | glDeleteBuffers(1, &it->second.id); |
264 | mBufferObjects.erase(it); |
265 | } |
266 | } |
267 | |
268 | void GLShader::drawIndexed(int type, uint32_t offset_, uint32_t count_) { |
269 | if (count_ == 0) |
270 | return; |
271 | size_t offset = offset_; |
272 | size_t count = count_; |
273 | |
274 | switch (type) { |
275 | case GL_TRIANGLES: offset *= 3; count *= 3; break; |
276 | case GL_LINES: offset *= 2; count *= 2; break; |
277 | } |
278 | |
279 | glDrawElements(type, (GLsizei) count, GL_UNSIGNED_INT, |
280 | (const void *)(offset * sizeof(uint32_t))); |
281 | } |
282 | |
283 | void GLShader::drawArray(int type, uint32_t offset, uint32_t count) { |
284 | if (count == 0) |
285 | return; |
286 | |
287 | glDrawArrays(type, offset, count); |
288 | } |
289 | |
290 | void GLShader::free() { |
291 | for (auto &buf: mBufferObjects) |
292 | glDeleteBuffers(1, &buf.second.id); |
293 | mBufferObjects.clear(); |
294 | |
295 | if (mVertexArrayObject) { |
296 | glDeleteVertexArrays(1, &mVertexArrayObject); |
297 | mVertexArrayObject = 0; |
298 | } |
299 | |
300 | glDeleteProgram(mProgramShader); mProgramShader = 0; |
301 | glDeleteShader(mVertexShader); mVertexShader = 0; |
302 | glDeleteShader(mFragmentShader); mFragmentShader = 0; |
303 | glDeleteShader(mGeometryShader); mGeometryShader = 0; |
304 | } |
305 | |
306 | const GLShader::Buffer &GLShader::attribBuffer(const std::string &name) { |
307 | for (auto &pair : mBufferObjects) { |
308 | if (pair.first == name) |
309 | return pair.second; |
310 | } |
311 | |
312 | throw std::runtime_error(mName + ": attribBuffer: " + name + " not found!" ); |
313 | } |
314 | |
315 | // ---------------------------------------------------- |
316 | |
317 | void GLUniformBuffer::init() { |
318 | glGenBuffers(1, &mID); |
319 | } |
320 | |
321 | void GLUniformBuffer::bind(int bindingPoint) { |
322 | mBindingPoint = bindingPoint; |
323 | glBindBufferBase(GL_UNIFORM_BUFFER, mBindingPoint, mID); |
324 | } |
325 | |
326 | void GLUniformBuffer::release() { |
327 | glBindBufferBase(GL_UNIFORM_BUFFER, mBindingPoint, 0); |
328 | } |
329 | |
330 | void GLUniformBuffer::free() { |
331 | glDeleteBuffers(1, &mID); |
332 | mID = 0; |
333 | } |
334 | |
335 | void GLUniformBuffer::update(const std::vector<uint8_t> &data) { |
336 | glBindBuffer(GL_UNIFORM_BUFFER, mID); |
337 | glBufferData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_DYNAMIC_DRAW); |
338 | glBindBuffer(GL_UNIFORM_BUFFER, 0); |
339 | } |
340 | |
341 | // ---------------------------------------------------- |
342 | |
343 | void GLFramebuffer::init(const Vector2i &size, int nSamples) { |
344 | mSize = size; |
345 | mSamples = nSamples; |
346 | |
347 | glGenRenderbuffers(1, &mColor); |
348 | glBindRenderbuffer(GL_RENDERBUFFER, mColor); |
349 | |
350 | if (nSamples <= 1) |
351 | glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size.x(), size.y()); |
352 | else |
353 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, nSamples, GL_RGBA8, size.x(), size.y()); |
354 | |
355 | glGenRenderbuffers(1, &mDepth); |
356 | glBindRenderbuffer(GL_RENDERBUFFER, mDepth); |
357 | |
358 | if (nSamples <= 1) |
359 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x(), size.y()); |
360 | else |
361 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, nSamples, GL_DEPTH24_STENCIL8, size.x(), size.y()); |
362 | |
363 | glGenFramebuffers(1, &mFramebuffer); |
364 | glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); |
365 | |
366 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mColor); |
367 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepth); |
368 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mDepth); |
369 | |
370 | glDrawBuffer(GL_COLOR_ATTACHMENT0); |
371 | glReadBuffer(GL_COLOR_ATTACHMENT0); |
372 | |
373 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
374 | if (status != GL_FRAMEBUFFER_COMPLETE) |
375 | throw std::runtime_error("Could not create framebuffer object!" ); |
376 | |
377 | release(); |
378 | } |
379 | |
380 | void GLFramebuffer::free() { |
381 | glDeleteRenderbuffers(1, &mColor); |
382 | glDeleteRenderbuffers(1, &mDepth); |
383 | mColor = mDepth = 0; |
384 | } |
385 | |
386 | void GLFramebuffer::bind() { |
387 | glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); |
388 | if (mSamples > 1) |
389 | glEnable(GL_MULTISAMPLE); |
390 | } |
391 | |
392 | void GLFramebuffer::release() { |
393 | if (mSamples > 1) |
394 | glDisable(GL_MULTISAMPLE); |
395 | glBindFramebuffer(GL_FRAMEBUFFER, 0); |
396 | } |
397 | |
398 | void GLFramebuffer::blit() { |
399 | glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer); |
400 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
401 | glDrawBuffer(GL_BACK); |
402 | |
403 | glBlitFramebuffer(0, 0, mSize.x(), mSize.y(), 0, 0, mSize.x(), mSize.y(), |
404 | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); |
405 | |
406 | glBindFramebuffer(GL_FRAMEBUFFER, 0); |
407 | } |
408 | |
409 | void GLFramebuffer::downloadTGA(const std::string &filename) { |
410 | uint8_t *temp = new uint8_t[mSize.prod() * 4]; |
411 | |
412 | std::cout << "Writing \"" << filename << "\" (" << mSize.x() << "x" << mSize.y() << ") .. " ; |
413 | std::cout.flush(); |
414 | glPixelStorei(GL_PACK_ALIGNMENT, 1); |
415 | glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer); |
416 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); |
417 | glReadPixels(0, 0, mSize.x(), mSize.y(), GL_BGRA, GL_UNSIGNED_BYTE, temp); |
418 | glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); |
419 | |
420 | uint32_t rowSize = mSize.x() * 4; |
421 | uint32_t halfHeight = mSize.y() / 2; |
422 | uint8_t *line = (uint8_t *) alloca(rowSize); |
423 | for (uint32_t i=0, j=mSize.y()-1; i<halfHeight; ++i) { |
424 | memcpy(line, temp + i * rowSize, rowSize); |
425 | memcpy(temp + i * rowSize, temp + j * rowSize, rowSize); |
426 | memcpy(temp + j * rowSize, line, rowSize); |
427 | j--; |
428 | } |
429 | |
430 | FILE *tga = fopen(filename.c_str(), "wb" ); |
431 | if (tga == nullptr) |
432 | throw std::runtime_error("GLFramebuffer::downloadTGA(): Could not open output file" ); |
433 | fputc(0, tga); /* ID */ |
434 | fputc(0, tga); /* Color map */ |
435 | fputc(2, tga); /* Image type */ |
436 | fputc(0, tga); fputc(0, tga); /* First entry of color map (unused) */ |
437 | fputc(0, tga); fputc(0, tga); /* Length of color map (unused) */ |
438 | fputc(0, tga); /* Color map entry size (unused) */ |
439 | fputc(0, tga); fputc(0, tga); /* X offset */ |
440 | fputc(0, tga); fputc(0, tga); /* Y offset */ |
441 | fputc(mSize.x() % 256, tga); /* Width */ |
442 | fputc(mSize.x() / 256, tga); /* continued */ |
443 | fputc(mSize.y() % 256, tga); /* Height */ |
444 | fputc(mSize.y() / 256, tga); /* continued */ |
445 | fputc(32, tga); /* Bits per pixel */ |
446 | fputc(0x20, tga); /* Scan from top left */ |
447 | fwrite(temp, mSize.prod() * 4, 1, tga); |
448 | fclose(tga); |
449 | |
450 | delete[] temp; |
451 | std::cout << "done." << std::endl; |
452 | } |
453 | |
454 | // ---------------------------------------------------- |
455 | |
456 | Eigen::Vector3f project(const Eigen::Vector3f &obj, |
457 | const Eigen::Matrix4f &model, |
458 | const Eigen::Matrix4f &proj, |
459 | const Vector2i &viewportSize) { |
460 | Eigen::Vector4f tmp; |
461 | tmp << obj, 1; |
462 | |
463 | tmp = model * tmp; |
464 | |
465 | tmp = proj * tmp; |
466 | |
467 | tmp = tmp.array() / tmp(3); |
468 | tmp = tmp.array() * 0.5f + 0.5f; |
469 | tmp(0) = tmp(0) * viewportSize.x(); |
470 | tmp(1) = tmp(1) * viewportSize.y(); |
471 | |
472 | return tmp.head(3); |
473 | } |
474 | |
475 | Eigen::Vector3f unproject(const Eigen::Vector3f &win, |
476 | const Eigen::Matrix4f &model, |
477 | const Eigen::Matrix4f &proj, |
478 | const Vector2i &viewportSize) { |
479 | Eigen::Matrix4f Inverse = (proj * model).inverse(); |
480 | |
481 | Eigen::Vector4f tmp; |
482 | tmp << win, 1; |
483 | tmp(0) = tmp(0) / viewportSize.x(); |
484 | tmp(1) = tmp(1) / viewportSize.y(); |
485 | tmp = tmp.array() * 2.0f - 1.0f; |
486 | |
487 | Eigen::Vector4f obj = Inverse * tmp; |
488 | obj /= obj(3); |
489 | |
490 | return obj.head(3); |
491 | } |
492 | |
493 | Eigen::Matrix4f lookAt(const Eigen::Vector3f &origin, |
494 | const Eigen::Vector3f &target, |
495 | const Eigen::Vector3f &up) { |
496 | Eigen::Vector3f f = (target - origin).normalized(); |
497 | Eigen::Vector3f s = f.cross(up).normalized(); |
498 | Eigen::Vector3f u = s.cross(f); |
499 | |
500 | Eigen::Matrix4f result = Eigen::Matrix4f::Identity(); |
501 | result(0, 0) = s(0); |
502 | result(0, 1) = s(1); |
503 | result(0, 2) = s(2); |
504 | result(1, 0) = u(0); |
505 | result(1, 1) = u(1); |
506 | result(1, 2) = u(2); |
507 | result(2, 0) = -f(0); |
508 | result(2, 1) = -f(1); |
509 | result(2, 2) = -f(2); |
510 | result(0, 3) = -s.transpose() * origin; |
511 | result(1, 3) = -u.transpose() * origin; |
512 | result(2, 3) = f.transpose() * origin; |
513 | return result; |
514 | } |
515 | |
516 | Eigen::Matrix4f ortho(float left, float right, float bottom, |
517 | float top, float nearVal, float farVal) { |
518 | Eigen::Matrix4f result = Eigen::Matrix4f::Identity(); |
519 | result(0, 0) = 2.0f / (right - left); |
520 | result(1, 1) = 2.0f / (top - bottom); |
521 | result(2, 2) = -2.0f / (farVal - nearVal); |
522 | result(0, 3) = -(right + left) / (right - left); |
523 | result(1, 3) = -(top + bottom) / (top - bottom); |
524 | result(2, 3) = -(farVal + nearVal) / (farVal - nearVal); |
525 | return result; |
526 | } |
527 | |
528 | Eigen::Matrix4f frustum(float left, float right, float bottom, |
529 | float top, float nearVal, |
530 | float farVal) { |
531 | Eigen::Matrix4f result = Eigen::Matrix4f::Zero(); |
532 | result(0, 0) = (2.0f * nearVal) / (right - left); |
533 | result(1, 1) = (2.0f * nearVal) / (top - bottom); |
534 | result(0, 2) = (right + left) / (right - left); |
535 | result(1, 2) = (top + bottom) / (top - bottom); |
536 | result(2, 2) = -(farVal + nearVal) / (farVal - nearVal); |
537 | result(3, 2) = -1.0f; |
538 | result(2, 3) = -(2.0f * farVal * nearVal) / (farVal - nearVal); |
539 | return result; |
540 | } |
541 | |
542 | Eigen::Matrix4f scale(const Eigen::Vector3f &v) { |
543 | return Eigen::Affine3f(Eigen::Scaling(v)).matrix(); |
544 | } |
545 | |
546 | Eigen::Matrix4f translate(const Eigen::Vector3f &v) { |
547 | return Eigen::Affine3f(Eigen::Translation<float, 3>(v)).matrix(); |
548 | } |
549 | |
550 | NAMESPACE_END(nanogui) |
551 | |