Behind the scene of Camera.ScreenToWorldpoint() in Unity 2D

2016-11-19 09:00:12 Coding C++ Unity

For some reason I need to compute Camera.main.ScreenToWorldpoint manually. So, I need to know how it works internally. Since in 2D we have $$P_s = (P_v.x * w, P_v.y * h)$$, it is enough to understand how ViewportToWorldpoint works.

In theory (CG basics): $$P_v = M_{proj} * M_{view} * P_w$$, thus

$$P_w = M_{view}^{-1} * M_{proj}^{-1} * P_v $$

However, in Unity, $$P_v$$ is in the range [0, 1] rather than [-1, 1], which means it is actually

$$P_v = M_{extra} * M_{proj} * M_{view} * P_w$$

and thus

$$P_w = M_{view}^{-1} * M_{proj}^{-1} * M_{extra}^{-1} * P_v $$

Where

M_extra = [1, 0, 0, 1]   inv(M_extra) = [1, 0, 0, -0.5]
          [0, 1, 0, 1]                  [0, 1, 0, -0.5]
          [0, 0, 1, 0]                  [0, 0, 1,  0  ]
          [0, 0, 0, 2]                  [0, 0, 0,  0.5]

Note that we don’t care about Z since it is in 2D orthographic projection mode.

Now, in the language of Unity:

Initialize:

var magicMatrix = new Matrix4x4
{
    m00 = 1,
    m03 = -0.5f,
    m11 = 1,
    m13 = -0.5f,
    m22 = 1,
    m33 = 0.5f
};
_originalViewportToWorldMatrix = Camera.main.cameraToWorldMatrix*Camera.main.projectionMatrix.inverse*magicMatrix;

Compute:

private Vector3 OriginalScreenToWorld(float x, float y, float setZ)
{
    var vec = _originalViewportToWorldMatrix*new Vector4(x/_width, y/_height, 0, 1);
    return new Vector3(vec.x/vec.w, vec.y/vec.w, setZ);
}

Record all cout output in C++

2016-03-27 17:41:32 Coding C++

我去年做UROP的时候,用C++写了一个控制台程序。后来希望把整个过程自动化,也就需要导出程序的所有输出。然而这些输出本身是在cout里的,我也不想弄得太复杂(比如再写个程序wrap在外面什么的)。同时我希望这个过程是透明的——我不需要修改那些用了cout<<的地方。

于是先写一个用于把输入一个streambuf的字符分配到两个streambuf中的dist_streambuf:

/*
dist_streambuf.h
Author: logchan
Created: 2015-07-29
Header file for class dist_streambuf.
A dist_streambuf object is used to distribute the output operations to two streambuf objects (primary and secondary).
It can be used in some tricks, such as recording cout.
*/
#ifndef _LOGCHAN_DIST_STREAMBUF_H
#define _LOGCHAN_DIST_STREAMBUF_H
#include <streambuf>
#include <locale>
#include <ios>

using std::streambuf;
using std::streamsize;
using std::streampos;
using std::streamoff;
using std::locale;
using std::ios_base;

class dist_streambuf : public streambuf
{
public:
    dist_streambuf(streambuf* primary, streambuf* secondary);
    streambuf* primary_buf();
    streambuf* primary_buf(streambuf* newbuf);
    streambuf* secondary_buf();
    streambuf* secondary_buf(streambuf* newbuf);

protected:
    // Locales
    void imbue(const locale& loc);
    // Buffer management and positioning
    streambuf* setbuf(char* s, streamsize n);
    streampos seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in | ios_base::out);
    streampos seekpos(streampos sp, ios_base::openmode which = ios_base::in | ios_base::out);
    int sync();
    // Input functions
    // Not implemented. It does not make sense to "distribute input to" two streambufs.
    // Output functions
    streamsize xsputn(const char* s, streamsize n);
    int overflow(int c = EOF);

private:
    streambuf* buf1;
    streambuf* buf2;
};

#endif //_LOGCHAN_DIST_STREAMBUF_H

以及实现的cpp文件:

#include "dist_streambuf.h"
#include <streambuf>
#include <stdexcept>
using namespace std;

dist_streambuf::dist_streambuf(streambuf * primary, streambuf * secondary)
{
    if (primary == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    buf1 = primary;
    buf2 = secondary;
}

streambuf * dist_streambuf::primary_buf()
{
    return buf1;
}

streambuf * dist_streambuf::primary_buf(streambuf * newbuf)
{
    if (newbuf == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    streambuf* prevbuf = buf1;
    buf1 = newbuf;
    return prevbuf;
}

streambuf * dist_streambuf::secondary_buf()
{
    return buf2;
}

streambuf * dist_streambuf::secondary_buf(streambuf * newbuf)
{
    streambuf* prevbuf = buf2;
    buf2 = newbuf;
    return prevbuf;
}
streamsize dist_streambuf::xsputn(const char * s, streamsize n)
{
    streamsize r = buf1->sputn(s, n);
    if (buf2 != nullptr)
    {
        buf2->sputn(s, n);
    }

    return r;
}

int dist_streambuf::overflow(int c)
{
    int r = buf1->sputc(c);
    if (buf2 != nullptr)
    {
        buf2->sputc(c);
    }

    return r;
}

void dist_streambuf::imbue(const locale & loc)
{
    buf1->pubimbue(loc);
    if (buf2 != nullptr)
    {
        buf2->pubimbue(loc);
    }
}

streambuf * dist_streambuf::setbuf(char * s, streamsize n)
{
    streambuf* r = buf1->pubsetbuf(s, n);
    if (buf2 != nullptr)
    {
        buf2->pubsetbuf(s, n);
    }

    return r;
}

streampos dist_streambuf::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which)
{
    streampos r = buf1->pubseekoff(off, way, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekoff(off, way, which);
    }

    return r;
}

streampos dist_streambuf::seekpos(streampos sp, ios_base::openmode which)
{
    streampos r = buf1->pubseekpos(sp, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekpos(sp, which);
    }

    return r;
}

int dist_streambuf::sync()
{
    int r = buf1->pubsync();
    if (buf2 != nullptr)
    {
        buf2->pubsync();
    }

    return r;
}

dist_streambuf允许buf2为nullptr。使用时,通过cout本身的buffer和一个sstream的buffer构造一个dist_streambuf并用其替换cout原来的buffer就可以了:

// initialize the logging of cout
auto preserved_cout_buf = cout.rdbuf();
auto logstream_buf = logstream.rdbuf();
dist_buf = new dist_streambuf(preserved_cout_buf, logstream_buf);
cout.rdbuf(dist_buf);

如果希望临时关闭分配,既可以把preserved_cout_buf还给cout,也可以用dist_buf->secondary_buf(nullptr);来使logstream_buf不再接受到新的字符。当然,如果想分配到更多的buffer,也可以把一个dist_streambuf作为另一个的primary或者secondary……这么一说,构造器没有检查传入的是不是等于this似乎有点危险……失策

啊,多么好用的东西。