Laya实现LineRenderer


前言

在这里插入图片描述

Laya目前为止还没有支持LineRenderer这个组件,在做相关特效的时候不是很方便,本文主要讲解如何仿照Unity在Laya上实现一个基础版的LineRenderer。


提示:以下是本篇文章正文内容,下面案例可供参考

一、目标

Unity场景中包含的LineRenderer导出到Laya后,Laya可自动创建LineSprite3D对象(需要修改引擎),就像MeshSprite3D一样,再使用LineSprite3D的api设置起点和终点,产生连线特效。

二、原理

简单来说,就是把一个带uv动画的Plane拉伸成连接两点的一个平面,通过位移、拉伸和旋转达到想要的效果。我们只需要构建一个新的矩阵,然后使用新的矩阵替换原来的世界矩阵。我们给出两个点 P 1 P_{1} P1 P 2 P_{2} P2 ,即为要连接的两个点。

1.构建Plane

构建一个1x1的Plane,并使用uv动画播放特效(uv动画本文不做解释)
在这里插入图片描述

2.构建转换矩阵

这个步骤又可详细分为以下几步:

  • 1、把Plane移动到 P 1 P_{1} P1 P 1 P_{1} P1的中点,即Plane的位移为:
    d x = ( P 1 . x + P 2 . x ) / 2 dx=(P_{1}.x+P_{2}.x)/2 dx=(P1.x+P2.x)/2 d y = ( P 1 . y + P 2 . y ) / 2 dy=(P_{1}.y+P_{2}.y)/2 dy=(P1.y+P2.y)/2 d z = ( P 1 . z + P 2 . z ) / 2 dz=(P_{1}.z+P_{2}.z)/2 dz=(P1.z+P2.z)/2

  • 2、 跟据上图,可知需要拉伸的是x轴,Plane的x轴拉伸的长度 = 两个点的距离。
    s c a l e X = d i s t a n c e ( P 1 , P 2 ) scaleX=distance(P_{1},P_{2}) scaleX=distance(P1,P2)

  • 3、接下来求新的xyz轴基向量。
    Plane的x轴经过转换后必须指向 P 2 P_{2} P2,即和 P 1 P_{1} P1 P 2 P_{2} P2的方向一致,我们称该方向为 r i g h t right right,于是
    r i g h t = n o r m a l i z e ( P 2 − P 1 ) right=normalize(P_{2}-P_{1}) right=normalize(P2P1)
    z轴转换后的向量起个名字叫 f o r w a r d forward forward f o r w a r d forward forward必须垂直于 r i g h t right right,同时为了使Plane尽可能的面向摄像机方向, f o r w a r d forward forward就必须要垂直于摄像机位置到Plane的向量(给个名字就叫 c a m e r a D i r cameraDir cameraDir吧),因此, r i g h t right right叉乘 c a m e r a D i r cameraDir cameraDir就得到 f o r w a r d forward forward
    c a m e r a D i r = 摄 像 机 坐 标 − P 1 P 2 中 点 cameraDir=摄像机坐标-P_{1}P_{2}中点 cameraDir=P1P2 f o r w a r d = n o r m a l i z e ( r i g h t × c a m e r a D i r ) forward=normalize(right×cameraDir) forward=normalize(right×cameraDir)
    y轴转换后的向量也起个名字叫 u p up up u p up up必须同时垂直于 f o r w a r d forward forward r i g h t right right,所以:
    u p = r i g h t × f o r w a r d up=right×forward up=right×forward
    可得矩阵
    [ d i s t a n c e ⋅ r i g h t d x u p d y f o r w a r d d z 0 1 ] \left[ \begin{matrix} distance·right & dx\\ up & dy \\ forward & dz \\ 0 & 1 \end{matrix} \right] distancerightupforward0dxdydz1

三、实现

  • LayaAir版本:2.10.0_beta(因为需要修改引擎,所以文中使用了ts实验版,本文最下方有经过修改的2.7.0版本,可直接使用)
  • Unity2018.4.7f1
  • LayaAirUnityPlugin为LayaAir引擎对应的版本。

虽然Laya还未实现LineRenderer,但如果Unity场景中包含了LineRenderer,并使用LayaAirUnityPlugin插件导出资源时,资源文件就会包含LineRenderer的数据,我们只需读取这些数据就可以在Laya中生成与Unity中一样的LineRenderer。
先看在Unity中LineRenderer的设置:
在这里插入图片描述
我们先分析导出的数据,以下红框是.ls文件中的LineRenderer:
在这里插入图片描述
Laya对包含了LinerRender的物体(在Unity中该物体名字是“Line”)识别为LineSprite3D类型,我们就使用LineSprite3D这个名字实现一个Sprite类。上图中,LineSprite3D的props属性还包含了许多信息,但接下来我只使用widthMultiplier,即宽度,其它的暂不实现。

LineSprite3D.ts

import { Vector3 } from "../math/Vector3";
import { Laya } from "Laya";
import { BaseCamera } from "./BaseCamera";
import { Transform3D } from "./Transform3D";
import { Node } from "../../display/Node";
import LineRenderer from "./LineRenderer";
import { MeshSprite3D } from "./MeshSprite3D";
import { PrimitiveMesh } from "../resource/models/PrimitiveMesh";


export default class LineSprite3D extends MeshSprite3D {

    private _delta: number;
    private _interval: number;
    private _widthMultiplier: number = 1;

    private _target1: Transform3D;
    private _target2: Transform3D;
    private _startPoint = new Vector3();
    private _endPoint = new Vector3();

    private _middlePos = new Vector3();
    private _right = new Vector3();
    private _forward = new Vector3();
    private _up = new Vector3();
    private _cameraDir = new Vector3();
    private _distance = 0;
    private _wordMatrixElement: Float32Array;

    constructor() {
        super(PrimitiveMesh.createPlane(1, 1));
        this._delta = 0;
        this._interval = 50;
        this._render = new LineRenderer(this);
        /* 为_render赋值之后,需要重新设置meshFilter.sharedMesh,这样才能触发为新的render生成RenderElement
           具体才考meshFilter.sharedMesh的定义*/
        let mesh = this.meshFilter.sharedMesh;
        this.meshFilter.sharedMesh = null;
        this.meshFilter.sharedMesh = mesh;
    }

    _parse(data: any, spriteMap: any): void {
        super._parse(data, spriteMap);
        this._widthMultiplier = data.widthMultiplier;
        this._startPoint = new Vector3(data.positions.values[0][0], data.positions.values[0][1], data.positions.values[0][2]);
        this._endPoint = new Vector3(data.positions.values[1][0], data.positions.values[1][1], data.positions.values[1][2]);
    }

    _cloneTo(destObject: any, rootSprite: Node, dstSprite: Node): void {
        destObject._widthMultiplier = this._widthMultiplier;
        super._cloneTo(destObject, rootSprite, dstSprite);
    }

    /**
     * 设置起点位置
     */
    setStartPoint(startPoint: Vector3): void {
        if (Vector3.distanceSquared(startPoint, this._startPoint) > 1e-6) {
            startPoint.cloneTo(this._startPoint);
            this._delta = this._interval;
        }
    };

    /**
     * 设置终点位置
     */
    setEndPoint(endPoint: Vector3): void {
        if (Vector3.distanceSquared(endPoint, this._endPoint) > 1e-6) {
            endPoint.cloneTo(this._endPoint);
            this._delta = this._interval;
        }
    };

    /**
     * 连线自动跟随两个目标移动
     */
    setTargets(target1: Transform3D, target2: Transform3D): void {
        this._target1 = target1;
        this._target2 = target2;
    }

    /**
     * 根据起点,终点,宽,计算正方形的位置,缩放,旋转
     */
    _updateShape(camera: BaseCamera): void {
        this._delta += Laya.timer.delta;
        if (this._delta >= this._interval) {
            this._delta -= this._interval;
            var m = this._render.material as any;
            m.tilingOffsetW += 0.25;
            if (m.tilingOffsetW >= 1) m.tilingOffsetW = 0;
        }

        if (this._target1 && this._target2) {
            this._startPoint = this._target1.position;
            this._endPoint = this._target2.position;
        }

        //计算两个物体间的距离,即LineSprite3D x轴的缩放大小
        this._distance = Vector3.distance(this._endPoint, this._startPoint);
        if (this._distance == 0) {
            this.transform.localScale.setValue(0, 0, 0);
            this.transform.localScale = this.transform.localScale;
            return;
        }

        //计算两个物体的中点位置,即LineSprite3D的位置
        Vector3.lerp(this._startPoint, this._endPoint, 0.5, this._middlePos);

        //计算right向量,即新的x轴基向量
        Vector3.subtract(this._endPoint, this._startPoint, this._right);
        Vector3.normalize(this._right, this._right);

        //计算forward方向,即新的z轴基向量
        Vector3.subtract(camera.transform.position, this._middlePos, this._cameraDir); //从LineSprite3D指向摄像机的向量
        // camera.transform.getForward(this._cameraDir);
        // Vector3.subtract(Vector3._ZERO, this._cameraDir, this._cameraDir);
        Vector3.cross(this._right, this._cameraDir, this._forward);  //使用叉乘得到forward方向
        Vector3.normalize(this._forward, this._forward);

        //计算up向量,即新的y轴基向量
        Vector3.cross(this._forward, this._right, this._up);

        //开始构建矩阵
        this._wordMatrixElement = this.transform.worldMatrix.elements;

        //位移
        this._wordMatrixElement[12] = this._middlePos.x;
        this._wordMatrixElement[13] = this._middlePos.y;
        this._wordMatrixElement[14] = this._middlePos.z;
        this._wordMatrixElement[15] = 1;

        //新的x基向量
        this._wordMatrixElement[0] = this._right.x * this._distance;
        this._wordMatrixElement[1] = this._right.y * this._distance;
        this._wordMatrixElement[2] = this._right.z * this._distance;
        this._wordMatrixElement[3] = 0;

        //新的y基向量
        this._wordMatrixElement[4] = this._up.x;
        this._wordMatrixElement[5] = this._up.y;
        this._wordMatrixElement[6] = this._up.z;
        this._wordMatrixElement[7] = 0;

        //新的z基向量
        this._wordMatrixElement[8] = this._forward.x * this._widthMultiplier;
        this._wordMatrixElement[9] = this._forward.y * this._widthMultiplier;
        this._wordMatrixElement[10] = this._forward.z * this._widthMultiplier;
        this._wordMatrixElement[11] = 0;
    };

    get widthMultiplier(): number {
        return this._widthMultiplier;
    }

    set widthMultiplier(val: number) {
        this._widthMultiplier = val;
    }

    /**
     * @internal
     */
    protected _create(): Node {
        return new LineSprite3D();
    }
}

LineSprite3D继承MeshSprite3D,这样继承的好处是,只需要传一个1x1大小的Plane,MeshSprite3D原有的方法就会帮我们做渲染,而我们只需要在LineSprite3D中计算新的矩阵即可。

LineRenderer.ts

import { Transform3D } from "./Transform3D";
import { RenderContext3D } from "./render/RenderContext3D";
import { MeshRenderer } from "./MeshRenderer";
import LineSprite3D from "./LineSprite3D";


export default class LineRenderer extends MeshRenderer {

    _renderUpdate(context: RenderContext3D, transform: Transform3D): void {
        super._renderUpdate(context, transform);
        (this._owner as LineSprite3D)._updateShape(context.camera);
    }

}

LineRenderer继承MeshRenderer,作用是为计算矩阵提供渲染状态(这里只用到了摄像机状态)

为Laya引擎增加LineSprite3D

  • 在Laya3D.ts的_getSprite3DHierarchyInnerUrls方法中加入LineSprite3D
    在这里插入图片描述
  • 在Scene3DUtils.ts的_createSprite3DInstance方法中加入
    在这里插入图片描述
  • 在Scene3DUtils.ts的_createNodeByJson方法中加入
    在这里插入图片描述

使用

        var g1 = this.view.getChildByName("Sphere 1") as Sprite3D;
        var g2 = this.view.getChildByName("Sphere 2") as Sprite3D;
        var line = this.view.getChildByName("Line") as LineSprite3D;
        //使用方法1:固定起点和终点
        line.setStartPoint(g1.transform.position);
        line.setEndPoint(g2.transform.position);
        //使用方法2:设定物体的transform,连线会跟随物体移动
        line.setTargets(g1.transform, g2.transform);
        //设置宽度
        line.widthMultiplier = 2;

Demo

Demo使用的是2.7.0 ts版编写,有需要的同学可以直接替换 bin/libs/laya.d3.js 和 libs/LayaAir.d.ts 两个文件即可。
链接:https://pan.baidu.com/s/1yHhgzuZAuqTPFWonw3d7hg
提取码:5dj3

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值