Skip to content

Draw Mesh Line


WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

ts
import { Engine3D, Scene3D, CameraUtil, HoverCameraController, Object3D, MeshRenderer, View3D, PlaneGeometry, UnLitMaterial, Color, Vector3, PointerEvent3D, Camera3D, SphereGeometry, CylinderGeometry, MathUtil, BlendMode, GPUCullMode } from '@orillusion/core';
import { Stats } from '@orillusion/stats';
import * as dat from 'dat.gui';

class Sample_MeshLines {
    private onDraw: boolean = false;
    private scene: Scene3D;
    private camera: Camera3D;
    private lastTime: number;
    private path: Object3D[] = [];
    private hoverCameraController: HoverCameraController;
    private lastX: number = -1;
    private lastY: number = -1;
    private pointGeometry: SphereGeometry;
    private lineGeometry: CylinderGeometry;
    private material: UnLitMaterial;

    public lineWidth: number = 0.1;
    public drawInterval: number = 30;
    public precision: number = 32;
    public depth: number = 0;
    public lineColor: Color = new Color(1, 0, 0);

    async run() {
        // init engine
        await Engine3D.init();
        // create new Scene
        let scene = new Scene3D();
        scene.addComponent(Stats);
        this.scene = scene;

        // init camera3D
        let mainCamera = CameraUtil.createCamera3D(null, scene);
        mainCamera.perspective(60, Engine3D.aspect, 1, 2000.0);

        // add a basic camera controller
        this.hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController);
        this.hoverCameraController.setCamera(0, 0, 20);

        this.camera = mainCamera;

        this.pointGeometry = new SphereGeometry(0.5, 32, 32);
        this.lineGeometry = new CylinderGeometry(0.5, 0.5, 1, 32, 32);
        this.material = new UnLitMaterial();
        this.material.baseColor = this.lineColor;

        // add basic plane
        let plane = new Object3D();
        let mr = plane.addComponent(MeshRenderer);
        mr.geometry = new PlaneGeometry(20, 20, 1, 1, Vector3.Z_AXIS);
        let mat = new UnLitMaterial();
        mat.baseColor = new Color(1, 1, 1, 0.4);
        mat.transparent = true;
        mat.cullMode = GPUCullMode.none;
        mat.blendMode = BlendMode.NORMAL;
        mr.material = mat;
        scene.addChild(plane);

        // create a view with target scene and camera
        let view = new View3D();
        view.scene = scene;
        view.camera = mainCamera;

        // start render
        Engine3D.startRenderView(view);

        Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_DOWN, this.onMouseDown, this, null, 999);
        Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_MOVE, this.onMouseMove, this);
        Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_UP, this.onMouseUp, this);

        // debug GUI
        let gui = new dat.GUI();
        let f = gui.addFolder('Orillusion');
        f.add(this, 'lineWidth', 0.1, 2, 0.1);
        f.add(this, 'precision', 4, 64, 1).onChange((precision) => {
            this.lineGeometry = new CylinderGeometry(0.5, 0.5, 1, precision, precision);
            this.pointGeometry = new SphereGeometry(0.5, precision, precision);
        });
        f.add(this, 'depth', -1, 1, 0.01);
        f.add(this, 'drawInterval', 15, 100, 1);
        f.addColor({ lineColor: Object.values(this.lineColor).map((v, i) => (i === 3 ? v : v * 255)) }, 'lineColor').onChange((v) => {
            this.lineColor = new Color(v[0] / 255, v[1] / 255, v[2] / 255, v[3]);
            this.material = new UnLitMaterial();
            this.material.baseColor = this.lineColor;
        });
        f.add({ resetView: () => this.hoverCameraController.setCamera(0, 0, 20) }, 'resetView');
        f.add(
            {
                clearCanvas: () => {
                    this.path.map((point) => {
                        this.scene.removeChild(point);
                    });
                    this.path.length = 0;
                }
            },
            'clearCanvas'
        );
        f.open();

        // add tips
        gui.add({ tips: 'Press to rotate camera' }, 'tips').name('Left Mouse');
        gui.add({ tips: 'Press to draw lines' }, 'tips').name('Right Mouse');
    }

    onMouseDown(e: PointerEvent3D) {
        if (e.mouseCode === 2) {
            e.stopImmediatePropagation();
            this.lastTime = Date.now();
            this.onDraw = true;
            this.drawPoint(e.mouseX, e.mouseY);
            this.lastX = e.mouseX;
            this.lastY = e.mouseY;
        }
    }

    onMouseMove(e: PointerEvent3D) {
        if (!this.onDraw) return;
        e.stopImmediatePropagation();
        const now = Date.now();
        if (now - this.lastTime > this.drawInterval) {
            this.drawLine(e.mouseX, e.mouseY);
            this.drawPoint(e.mouseX, e.mouseY);
            this.lastTime = now;
            this.lastX = e.mouseX;
            this.lastY = e.mouseY;
        }
    }

    onMouseUp(e: PointerEvent3D) {
        this.onDraw = false;
        this.lastX = -1;
        this.lastY = -1;
    }

    drawPoint(x: number, y: number) {
        let point = new Object3D();
        let mr = point.addComponent(MeshRenderer);
        mr.geometry = this.pointGeometry;
        mr.material = this.material;
        point.scaleX = point.scaleY = point.scaleZ = this.lineWidth;
        this.camera.worldToScreenPoint(this.hoverCameraController.target, Vector3.HELP_0);
        const pos = this.camera.screenPointToWorld(x, y, Vector3.HELP_0.z + this.depth / 100);
        point.x = pos.x;
        point.y = pos.y;
        point.z = pos.z;
        this.path.push(point);
        this.scene.addChild(point);
    }

    drawLine(x: number, y: number) {
        this.camera.worldToScreenPoint(this.hoverCameraController.target, Vector3.HELP_0);
        const start = this.camera.screenPointToWorld(this.lastX, this.lastY, Vector3.HELP_0.z + this.depth / 100);
        const end = this.camera.screenPointToWorld(x, y, Vector3.HELP_0.z);
        const distance = Math.sqrt(end.distanceToSquared(start));
        let line = new Object3D();
        let mr = line.addComponent(MeshRenderer);
        mr.geometry = this.lineGeometry;
        mr.material = this.material;
        line.scaleX = line.scaleZ = this.lineWidth;
        line.scaleY = distance;
        line.x = start.x + (end.x - start.x) / 2;
        line.y = start.y + (end.y - start.y) / 2;
        line.z = start.z + (end.z - start.z) / 2;

        // normalize the direction vector
        const dir = Vector3.HELP_1.set(end.x - start.x, end.y - start.y, end.z - start.z).normalize();
        const rot = MathUtil.fromToRotation(Vector3.Y_AXIS, dir);
        // make sure the rotation is valid
        if (!Number.isNaN(rot.x) && !Number.isNaN(rot.y) && !Number.isNaN(rot.z)) {
            line.transform.localRotQuat = rot;
        }
        this.path.push(line);
        this.scene.addChild(line);
    }
}

new Sample_MeshLines().run();