2. threejs源码阅读——math/matrix

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2. threejs源码阅读——math/matrix相关的知识,希望对你有一定的参考价值。

参考技术A 与 Vector接口 一样,threejs也在 Matrix3.d.ts 文件中提供了一个 Matrix 接口,并且在 Matrix3 (3x3矩阵类)和 Maxtrix4 (4x4矩阵类)中实现了该接口。

我们可以看到,矩阵中的元素是用数组的方式储存的。由于不同维度的矩阵内所用的元素个数不同,所以Matrix3和Matrix4的set方法是分开声明在每个类中的。这里我们以Matrix3.set为例:

可以看到,在set时是以行优先的顺序进行传值的,也就是第一行第一个,第一行第二个,第一行第三个,第二行第一个,第二行第二个...

但是在set方法的实现中,可以看到elements数组内是以列优先的顺序进行存储的:

简单点说,这里只是 存储方式的不同 。由于大多数人都习惯以行优先的方式考虑矩阵,所以threejs中所有的文档都是以行优先的方式表示的。但如果我们想要阅读源码,就要注意到[n11, n12, n13, n21 ... n33]并不是按顺序储存在elements中。事实上如果我们按顺序读取elements中的元素并以行优先的方式组成矩阵,它将是原本矩阵的转置矩阵。

如同Vector的类型,Matrix类型中所有返回值为自身类对象的方法也是原地修改该对象。例如 Transpose转置

所有的Matrix也提供了clone和copy方法,用于深拷贝不同Matrix对象。

那么threejs中的向量与矩阵进行计算是以什么形式进行的呢?这里我们举一个例子。

在Vector3类中提供了appyMatrix3这个方法,这个方法以一个3x3的矩阵作为参数,相乘后原地修改向量的值。

如果我们将Vector3看做 三行一列的矩阵(3x1) 的话,那么就需要左乘3x3矩阵,最后得到列向量:

如果我们将Vecot3看做 一行三列的矩阵(1x3) 的话,那么就需要右乘3x3矩阵,最后得到行向量:

接下来,我们再假设一个向量

加入齐次坐标w=1,来表示三维空间中的一个点(1,2,3)

然后我们构建一个仿射变换矩阵做缩放+平移运算

即沿着xyz轴均缩放2倍,然后沿着xyz移动5个单位距离。最终结果是:

这个计算我们会使用Vector3.applyMatrix4

接下来验证一下:

控制台打印结果:

threejs中的矩阵以 行优先 的形式初始化,以 列优先 的形式储存。

threejs中的向量采用 列向量 ,与矩阵做 左乘运算 。

threejs中一个三维向量与4x4的矩阵进行运算时,会先给三维向量补充第四个值w,也就是齐次坐标,w值与矩阵的第四行有关系:

在上面的例子中,我们使用了一个缩放+平移的 仿射变换矩阵 作为例子,此时w为1。

server.go 源码阅读


package pingo

import (
    "bufio"
    "bytes"
    "flag"
    "fmt"
    "io"
    "math/rand"
    "net"
    "net/rpc"
    "os"
    "path"
    "path/filepath"
    "reflect"
    "strings"
    "time"
)

// Register a new object this plugin exports. The object must be
// an exported symbol and obey all rules an object in the standard
// "rpc" module has to obey.
//注册的一个对象作为可以导出对象。这个对象必须符合RPC规则
//               - exported method of exported type
//    - two arguments, both of exported type
//    - the second argument is a pointer
//    - one return value, of type error
// Register will panic if called after Run.
//如果在运行中  注册对象 就会报错 
func Register(obj interface{}) {
    if defaultServer.running {
        panic("Do not call Register after Run")
    }
    defaultServer.register(obj)//注册可导出的对象
}

// Run will start all the necessary steps to make the plugin available.
//调用Run函数是必须的来保证插件的可用性
func Run() error {
    if !flag.Parsed() {//判断参数是否解析
        flag.Parse()//解析当前参数
    }
    return defaultServer.run()
}

// Internal object for plugin control
type PingoRpc struct{}

// Default constructor for interal object. Do not call manually.
func NewPingoRpc() *PingoRpc {
    return &PingoRpc{}
}

// Internal RPC call to shut down a plugin. Do not call manually.
func (s *PingoRpc) Exit(status int, unused *int) error {
    os.Exit(status)
    return nil
}

type config struct {
    proto   string
    addr    string
    prefix  string
    unixdir string
}

func makeConfig() *config {
    c := &config{}
    flag.StringVar(&c.proto, "pingo:proto", "unix", "Protocol to use: unix or tcp")
    flag.StringVar(&c.unixdir, "pingo:unixdir", "", "Alternative directory for unix socket")
    flag.StringVar(&c.prefix, "pingo:prefix", "pingo", "Prefix to output lines")
    return c
}

type rpcServer struct {
    *rpc.Server
    secret  string
    objs    []string
    conf    *config
    running bool
}

func newRpcServer() *rpcServer {
    rand.Seed(time.Now().UTC().UnixNano())
    r := &rpcServer{
        Server: rpc.NewServer(),
        secret: randstr(64),
        objs:   make([]string, 0),
        conf:   makeConfig(), // conf remains fixed after this point
    }
    r.register(&PingoRpc{})
    return r
}

var defaultServer = newRpcServer()

type bufReadWriteCloser struct {
    *bufio.Reader
    r io.ReadWriteCloser
}

func newBufReadWriteCloser(r io.ReadWriteCloser) *bufReadWriteCloser {
    return &bufReadWriteCloser{Reader: bufio.NewReader(r), r: r}
}

func (b *bufReadWriteCloser) Write(data []byte) (int, error) {
    return b.r.Write(data)
}

func (b *bufReadWriteCloser) Close() error {
    return b.r.Close()
}

func readHeaders(brwc *bufReadWriteCloser) ([]byte, error) {
    var buf bytes.Buffer
    var headerEnd bool

    for {
        b, err := brwc.ReadByte()
        if err != nil {
            return []byte(""), err
        }

        buf.WriteByte(b)

        if b == ‘\n‘ {
            if headerEnd {
                break
            }
            headerEnd = true
        } else {
            headerEnd = false
        }
    }

    return buf.Bytes(), nil
}

func parseHeaders(brwc *bufReadWriteCloser, m map[string]string) error {
    headers, err := readHeaders(brwc)
    if err != nil {
        return err
    }

    r := bytes.NewReader(headers)
    scanner := bufio.NewScanner(r)

    for scanner.Scan() {
        parts := strings.SplitN(scanner.Text(), ": ", 2)
        if parts[0] == "" {
            continue
        }
        m[parts[0]] = parts[1]
    }

    return nil
}

func (r *rpcServer) authConn(token string) bool {
    if token != "" && token == r.secret {
        return true
    }
    return false
}

func (r *rpcServer) serveConn(conn io.ReadWriteCloser, h meta) {
    bconn := newBufReadWriteCloser(conn)
    defer bconn.Close()

    headers := make(map[string]string)
    if err := parseHeaders(bconn, headers); err != nil {
        h.output("error", err.Error())
        return
    }

    if r.authConn(headers["Auth-Token"]) {
        r.Server.ServeConn(bconn)
    }

    return
}

func (r *rpcServer) register(obj interface{}) {
    element := reflect.TypeOf(obj).Elem() //获取对象元素类型
    r.objs = append(r.objs, element.Name()) //添加到objs中  获取元素类型名称
    r.Server.Register(obj)//调用rpc 的注册方法
}

type connection interface {
    addr() string
    retries() int
}

type tcp int

func (t *tcp) addr() string {
    if *t < 1024 {
        // Only use unprivileged ports
        *t = 1023
    }

    *t = *t + 1
    return fmt.Sprintf("127.0.0.1:%d", *t)
}

func (t *tcp) retries() int {
    return 500
}

type unix string

func (u *unix) addr() string {
    name := randstr(8)
    if *u != "" {
        name = filepath.FromSlash(path.Join(string(*u), name))
    }
    return name
}

func (u *unix) retries() int {
    return 4
}
//
func (r *rpcServer) run() error {
    var conn connection
    var err error
    var listener net.Listener

    r.running = true  //设置默认运行状态为true

    h := meta(r.conf.prefix)//设置自定数据类型  参数值为config 的前缀
    h.output("objects", strings.Join(r.objs, ", "))//方法参数 key 为常量 objects  参数 val 值为注册服务对象元素名称  。使用,链接的字符串
       //协议判断
    switch r.conf.proto {
    case "tcp":
        conn = new(tcp)
    default:
        r.conf.proto = "unix"
        conn = new(unix)
    }
        //获取尝试连接500次 但是只要有一次执行成功 立刻返回
    for i := 0; i < conn.retries(); i++ {
        r.conf.addr = conn.addr()
        listener, err = net.Listen(r.conf.proto, r.conf.addr)
        if err == nil {
            break
        }
    }

    if err != nil {
        h.output("fatal", fmt.Sprintf("%s: Could not connect in %d attemps, using %s protocol", errorCodeConnFailed, conn.retries(), r.conf.proto))
        return err
    }

    h.output("auth-token", defaultServer.secret)
    h.output("ready", fmt.Sprintf("proto=%s addr=%s", r.conf.proto, r.conf.addr))
    for {
        var conn net.Conn
        conn, err = listener.Accept()
        if err != nil {
            h.output("fatal", fmt.Sprintf("err-http-serve: %s", err.Error()))
            continue
        }
        go r.serveConn(conn, h)
    }
}

以上是关于2. threejs源码阅读——math/matrix的主要内容,如果未能解决你的问题,请参考以下文章

0. threejs源码阅读——结构概述

threejs可视化大屏酷炫3D地图附源码

threejs为啥不火

Threejs TrackballControl源码解析

Threejs OrbitControl源码解析

threejs 复杂模型光线效果怎么调