Skip to content

Graphic Drawing

Orillusion provides the @orillusion/graphic extension package, mainly used for drawing points, lines, surfaces, and volumes that change in real time. Using specific methods, it creates a dynamic mesh that is uniformly managed and integrated into the engine's rendering pipeline, offering high performance and ease of use.

Currently, three modules are provided to create high-performance graphic data:

  1. Graphic3D: Provides basic line drawing capabilities, commonly used for drawing auxiliary lines.
  2. Graphic3DMesh Renderer: Batch creates a set of Mesh clones within a single renderer, allowing you to freely define and adjust each clone's Transform, Texture, and Material to compose graphics and animations with a high degree of freedom.
  3. Shape3D Renderer: Creates complex custom Shape3D objects, such as EllipseShape3D, RoundRectShape3D, CircleShape3D, etc. For Shape3D objects with continuous drawing capabilities, such as Path2DShape3D and Path3DShape3D, the design references the CanvasPath API design, allowing developers to draw on and reuse the development practices they are already familiar with for graphic drawing work.

Installation

Like the engine itself, the graphic plugin can be introduced using NPM and CDN links:

1. Installing via NPM Package

bash
npm install @orillusion/core --save
npm install @orillusion/graphic --save
ts
import { Engine3D } from "@orillusion/core"
import { Graphic3D, Shape3D } from "@orillusion/graphic"

It is recommended to use the ESModule build version

html
<script type="module">
  import { Engine3D } from "https://unpkg.com/@orillusion/core/dist/orillusion.es.js" 
  import { Graphic3D, Shape3D } from "https://unpkg.com/@orillusion/graphic/dist/graphic.es.js" 
</script>

Or load the UMD build version using <script>, accessing the Shape3D module from the global Orillusion variable:

html
<script src="https://unpkg.com/@orillusion/core/orillusion.umd.js"></script>
<script src="https://unpkg.com/@orillusion/graphic/dist/graphic.umd.js"></script>
<script>
  const { Engine3D, Graphic } = Orillusion
  const { Graphic3D, Shape3D } = Graphic
</script>

Graphic3D

Create a Graphic3D object to uniformly draw graphics in the scene. Currently, three APIs are provided for quickly creating different line combinations: drawLines, drawBox, and drawCircle.

Basic Methods

ts
import {Graphic3D} from '@orillusion/graphic'
// ...

// Create a Graphic3D object
let graphic3D = new Graphic3D();
// Add to the scene
scene.addChild(graphic3D);

// Use graphic3D to uniformly draw lines
// line - (uid, [start1, end1, start2, end2, ...], color)
graphic3D.drawLines('line', [new Vector3(0, 0, 0), new Vector3(0, 10, 0)], new Color(1, 0, 0));
// box - (uid, center, size, color)
graphic3D.drawBox('box', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color(0, 1, 0));
// circle - (uid, center, radius, segments, up, color)
graphic3D.drawCircle('circle', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color(0, 0, 1));

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

<
ts
import { Object3D, Scene3D, Engine3D, Vector3, Color, AnimationCurve, Keyframe, View3D, AtmosphericComponent, CameraUtil, HoverCameraController, DirectLight, KelvinUtil, MeshRenderer, BoxGeometry, LitMaterial } from '@orillusion/core';
import { Graphic3D, Graphic3DLineRenderer } from '@orillusion/graphic';
// import { Stats } from '@orillusion/stats';
import * as dat from 'dat.gui';

class GraphicLine {
    scene: Scene3D;
    view: View3D;
    graphic3D: Graphic3D;

    async run() {
        let engine = await Engine3D.init();

        // init Scene3D
        this.scene = new Scene3D();
        this.scene.exposure = 1;
        // this.scene.addComponent(Stats);

        // init sky
        let atmosphericSky: AtmosphericComponent;
        atmosphericSky = this.scene.addComponent(AtmosphericComponent);
        atmosphericSky.exposure = 1.0;

        // init Camera3D
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, engine.aspect, 1, 5000);

        // init Camera Controller
        let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
        hoverCtrl.setCamera(-30, -15, 100);

        // init View3D
        let view = new View3D();
        view.scene = this.scene;
        view.camera = camera;

        // add a Graphic3D
        this.graphic3D = new Graphic3D();
        this.scene.addChild(this.graphic3D);

        // create direction light
        let lightObj3D = new Object3D();
        lightObj3D.x = 0;
        lightObj3D.y = 30;
        lightObj3D.z = -40;
        lightObj3D.rotationX = 20;
        lightObj3D.rotationY = 160;
        lightObj3D.rotationZ = 0;

        let light = lightObj3D.addComponent(DirectLight);
        light.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
        light.intensity = 30;

        this.scene.addChild(light.object3D);

        // relative light to sky
        atmosphericSky.relativeTransform = light.transform;
        engine.startRenderView(view);
        this.view = view;
        await this.initScene();
    }

    async initScene() {
        this.graphic3D.drawLines('line1', [Vector3.ZERO, new Vector3(0, 10, 0)], new Color().hexToRGB(Color.RED));

        let animCurve = new AnimationCurve();
        animCurve.addKeyFrame(new Keyframe(0, 0.5));
        animCurve.addKeyFrame(new Keyframe(0.15, -0.2));
        animCurve.addKeyFrame(new Keyframe(0.22, 0.4));
        animCurve.addKeyFrame(new Keyframe(0.34, 0.2));
        animCurve.addKeyFrame(new Keyframe(0.65, -0.2));
        animCurve.addKeyFrame(new Keyframe(1, 0.9));
        let lines: Vector3[] = [];
        for (let i = 0; i < 100; i++) {
            let y = animCurve.getValue(i / (100 - 1)) * 10;
            lines.push(new Vector3(i, y, 0));
        }
        this.graphic3D.drawLines('line2', lines, new Color().hexToRGB(Color.RED));

        this.graphic3D.drawBox('box1', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color().hexToRGB(Color.GREEN));

        this.graphic3D.drawCircle('Circle1', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color().hexToRGB(Color.GREEN));
        this.graphic3D.drawCircle('Circle2', new Vector3(-15, -5, -5), 5, 15, Vector3.Y_AXIS, new Color().hexToRGB(Color.GREEN));
        this.graphic3D.drawCircle('Circle3', new Vector3(-15, -5, -5), 5, 15, Vector3.Z_AXIS, new Color().hexToRGB(Color.GREEN));

        {
            let obj = new Object3D();
            let mr = obj.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(5, 5, 5);
            mr.material = new LitMaterial();
            this.scene.addChild(obj);
        }
        let gui = new dat.GUI();
        let btn = {'depthTest': true}
        gui.add(btn, 'depthTest').onChange(v=>{
            this.graphic3D.getComponents(Graphic3DLineRenderer).forEach(mr=>{
                mr.materials[0].depthCompare = v ? 'less' : 'always'
            })
        })
    }
}

new GraphicLine().run();

Graphic3DMesh Renderer

Using Graphic3DMesh.draw(), you can quickly create a Graphic3DMeshRenderer instance. This object can be viewed as a collection of multiple cloned Geometry objects. For each object in this collection, you can set its position and texture, and combine them to achieve the desired visual effect.

Parameter Overview

ParameterDescription
sceneCreated in the specified Scene3D
geoSpecifies the mesh data source
textureTexture list (indexed by subscript)
countSpecifies the maximum number of clone collections a renderer can support (choosing an appropriate value will improve performance)

TIP

For geo, generally inputting a simple PlaneGeometry as the model source is sufficient, using different textures to express different appearances. In theory, you can pass in any model source to create with. For example, passing in a BoxGeometry model produces a graphic composed of many cubes, enabling the creation of pixel-art scenes or simulating voxel rendering.

  1. Modifying Transform: To modify the rotation, scale, or position of a unit at a specific index.
    Get the object3Ds belonging to the Graphic3DMeshRenderer, use the array index to obtain the corresponding Object3D, and modifying the Transform of that Object3D will synchronize to the target unit.

  2. Modifying Texture: Call the function setTextureID, specifying the texture index (textureIndex) to modify for the unit at a specific index. The texture is obtained from the texture passed in the Graphic3DMeshRenderer initialization parameters.

  3. Modifying Material: The Graphic3DMeshRenderer class exposes a series of APIs named similarly to setTextureID. The first parameter specifies the target unit to set, and the second parameter sets the relevant property. Developers can use such APIs to modify the content of the graphics, such as Color, UV, Emissive, and other data.

Example

ts
import { Object3D, Scene3D, Engine3D, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color, ColorUtil } from "@orillusion/core";
import { Graphic3D, Graphic3DMesh, Graphic3DMeshRenderer } from '@orillusion/graphic';

// Load texture list
let textureArray = [];
textureArray.push(await engine.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);

bitmapTexture2DArray.setTextures(textureArray);

// Use Plane as the mesh clone data source
let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS);

// In the current scene, using plane as the clone data source, create a Graphic3DMeshRenderer instance that supports up to 100 clones.
let mr:Graphic3DMeshRenderer = Graphic3DMesh.draw(scene, geometry, bitmapTexture2DArray, 100);

// Modify material properties
mr.material.blendMode = BlendMode.ADD;
mr.material.transparent = true;
mr.material.depthWriteEnabled = false;
mr.material.useBillboard = true;

// Get the Object3D corresponding to each clone unit, and modifying the Transform property of that Object3D will synchronously modify the Transform of the target clone.
// Placing the same operation in the engine's main update function modifies it every frame to drive the animation effect.
let parts = mr.object3Ds;
for (let i = 0; i < 100; i++) {
    const element = parts[i];
    // set texture index from textureArray
    mr.setTextureID(i, 0);
    // update transform
    element.transform.x = 1;
    element.transform.scaleX = 1;
    element.transform.rotationX = 0;
    // ...
}

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

<
ts
import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color } from '@orillusion/core';
import { Stats } from '@orillusion/stats';
import { Graphic3DMesh, Graphic3DMeshRenderer } from '@orillusion/graphic';

class Sample_GraphicMesh {
    private scene: Scene3D;
    private parts: Object3D[];
    private width: number;
    private height: number;
    private cafe: number = 47;
    private view: View3D;
    private engine: Engine3D;

    graphicMeshRenderer: Graphic3DMeshRenderer;

    constructor() {}

    async run() {
        Matrix4.maxCount = 500000;
        Matrix4.allocCount = 500000;

        this.engine = await Engine3D.init({beforeRender: ()=> this.update()});

        this.engine.setting.render.debug = true;
        this.engine.setting.shadow.shadowBound = 5;

        this.scene = new Scene3D();
        this.scene.addComponent(Stats);
        let sky = this.scene.addComponent(AtmosphericComponent);
        sky.enable = false;
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, this.engine.aspect, 1, 5000.0);

        camera.object3D.addComponent(HoverCameraController).setCamera(30, 0, 120);

        this.view = new View3D();
        this.view.scene = this.scene;
        this.view.camera = camera;

        this.engine.startRenderView(this.view);
        await this.initScene();
    }

    async initScene() {
        let texts:any[] = [];
        texts.push((await this.engine.res.loadTexture('https://cdn.orillusion.com/textures/128/star_0008.png')) as BitmapTexture2D);

        let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length);
        bitmapTexture2DArray.setTextures(texts);

        let mat = new UnLitTexArrayMaterial();
        mat.baseMap = bitmapTexture2DArray;
        mat.name = 'LitMaterial';
        
        {
            this.width = 15;
            this.height = 15;
            let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS);
            this.graphicMeshRenderer = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height);
            this.parts = this.graphicMeshRenderer.object3Ds;

            this.graphicMeshRenderer.material.blendMode = BlendMode.ADD;
            this.graphicMeshRenderer.material.transparent = true;
            this.graphicMeshRenderer.material.depthWriteEnabled = false;
            this.graphicMeshRenderer.material.useBillboard = true;

            for (let i = 0; i < this.width * this.height; i++) {
                const element = this.parts[i];
                this.graphicMeshRenderer.setTextureID(i, 0);
                element.transform.scaleX = 5.5;
                element.transform.scaleY = 5.5;
                element.transform.scaleZ = 5.5;
            }
        }
    }

    update(){
        if (this.parts) {
            let len = this.parts.length;
            for (let i = 0; i < len; i++) {
                const element = this.parts[i];
                let tmp = this.sphericalFibonacci(i, len);
                tmp.multiplyScalar(Math.sin(i + Time.frame * 0.01) * this.cafe);
                element.transform.localPosition = tmp;
            }
        }
    }

    public madfrac(A: number, B: number): number {
        return A * B - Math.floor(A * B);
    }

    public sphericalFibonacci(i: number, n: number): Vector3 {
        const PHI = Math.sqrt(5.0) * 0.5 + 0.5;
        let phi = 2.0 * Math.PI * this.madfrac(i, PHI - 1);
        let cosTheta = 1.0 - (2.0 * i + 1.0) * (1.0 / n);
        let sinTheta = Math.sqrt(Math.max(Math.min(1.0 - cosTheta * cosTheta, 1.0), 0.0));

        return new Vector3(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta);
    }
}

new Sample_GraphicMesh().run();

For more Graphic3D API usage, please refer to the GraphicMesh example code.

Shape3D Renderer

Using Shape3DMaker, create a Shape3DRenderer renderer, which can hold and maintain a Shape3D dataset. Each Shape3D is a predefined variety of shapes, such as EllipseShape3D, RoundRectShape3D, CircleShape3D, etc. Among them, Path2DShape3D and Path3DShape3D have a richer API that can help you combine and draw complex graphics.

ParameterDescription
nameName, used to identify the Shape3DRenderer
sceneSpecifies which scene to put the Shape3DRenderer into
textureListTexture list, indexed by index
maxNodeCountSpecifies the maximum number of Shape3D objects the renderer supports
triangleEachNodeSpecifies the average number of triangles each Shape3D has

The renderer is designed based on the API of CanvasPath, allowing developers to reuse and draw on the development practices they are already familiar with for 3D drawing work. The 2D drawing part of the renderer refers to drawing points, lines, and surfaces in the XZ plane. At the same time, each unit can still be independently controlled via Transform. To draw graphics in 3D space, you need to use Path3DShape3D to begin drawing graphics that incorporate Y-axis elevation data.

Basic Properties

The engine has many built-in basic shapes, all inheriting from the Shape3D class, which mainly contain the following properties:

PropertyDescription
lineColorThe color additive when drawing lines
fillColorThe color additive when drawing filled areas
lineTextureIDSets the texture used when drawing lines
fillTextureIDSets the texture used when filling areas
fillRotationSets the rotation angle of the texture used for the filled area
shapeOrderSets the layering of each Shape (to eliminate z-fighting; each Shape3DRenderer can define the maximum z-fighting range, and based on this range and the number of Shape3D objects, the offset each Shape3D has is derived)
points3DA reserved collection of key points passed in externally
isClosedWhether the shape is closed at its start and end
fillWhether the shape is filled
lineWidthThe width of the drawn line
lineUVRectUV data: xy correspond to the offset of the line texture, and zw correspond to the scaling of the texture data
fillUVRectUV data: xy correspond to the offset of the fill area texture, and zw correspond to the scaling of the texture data
uvSpeedUV data: xy correspond to the UV movement speed of the fill area texture; zw correspond to the UV movement speed of the texture data when drawing lines

Built-in Shapes

Similar to the CanvasPath API, the engine currently provides the following subclasses/derived classes of Shape3D:

ShapeDescription
CircleShape3DCircle, arc
CurveShape3DBezier curve controlled by 2 anchor points
EllipseShape3DEllipse
LineShape3DPolyline
Path2DShape3DDraws a line path on the XZ plane
Path3DShape3DDraws a line path in 3D space
QuadraticCurveShape3DBezier curve controlled by 1 anchor point
RoundRectShape3DRectangle, rounded rectangle

Built-in Methods

Through an instance of Shape3DMaker, we can call the following methods to obtain the corresponding specific shapes:

MethodShape Type
ellipseEllipseShape3D
arcCircleShape3D
lineLineShape3D
quadraticCurveQuadraticCurveShape3D
curveCurveShape3D
path2DPath2DShape3D
path3DPath3DShape3D
rectRoundRectShape3D
roundRectRoundRectShape3D

TIP

All 2D shapes, e.g. path2D, will ignore the Y-axis data, and the shape will be unfolded in the XZ plane.

In addition, we can also add, delete, and modify Shape3D via the Shape3DRenderer:

MethodDescription
createShapeSpecify the type of Shape3D and create a Shape3D instance in the renderer
removeShapeDelete a Shape3D instance
getShapeObject3DGet the corresponding Object3D through the shapeIndex property of a Shape3D instance. Used for subsequently modifying the Transform

Example

ts
import { Object3D, Scene3D, Engine3D, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color,ColorUtil } from "@orillusion/core";
import { CircleShape3D, EllipseShape3D, Shape3DMaker, Shape3D } from "@orillusion/graphic";

// Load texture list
let textureArray = [];
textureArray.push(await engine.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);
bitmapTexture2DArray.setTextures(textureArray);

// In the current scene, create a Shape3DRenderer instance
maker = Shape3DMaker.makeRenderer(`path`, bitmapTexture2DArray, scene);
maker.renderer.material.doubleSide = true;

// Create a Circle based on the XZ plane, with a radius of 5 and a center of (0, 0)
let circle:CircleShape3D = maker.arc(5, 0, 0);
circle.lineWidth = 1; // Line width is 1
circle.segment = 16; // This arc will be fit using 16 line segments
circle.fill = true; // Set whether to fill
circle.line = true; // Set whether to draw the outline
circle.uvSpeed = new Vector4(0, 0, 0, Math.random() - 0.5).multiplyScalar(0.005); // Set UV scroll speed
circle.fillColor = Color.randomRGB(); // Set fill color additive
circle.lineColor = Color.randomRGB(); // Set line outline color additive

circle.startAngle = 30; // Set arc start angle
circle.endAngle = 240; // Set arc end angle

// Placing the control script for circle in the engine's main loop drives the animation effect

The above code demonstrates drawing an independent circle/arc by creating an instance of CircleShape3D. You can also obtain it by creating a generic Path2DShape3D instance and then calling its arc() function.

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

<
ts
import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, LineJoin, Vector4, Object3DUtil, AxisObject } from '@orillusion/core';
import { Stats } from '@orillusion/stats';
import { Shape3DMaker, Shape3D, CircleArcType, CircleShape3D } from '@orillusion/graphic';
import * as dat from 'dat.gui';

/**
 * This example shows how to use Shape2D to draw various different paths on xz plane.
 *
 * @export
 * @class Sample_Shape3DPath2D
 */
class Sample_Shape3DPath2D {
    lightObj3D: Object3D;
    scene: Scene3D;
    view: View3D;
    engine: Engine3D;

    async run() {
        Matrix4.maxCount = 10000;
        Matrix4.allocCount = 10000;

        this.engine = await Engine3D.init({ beforeRender: () => this.update() });

        this.engine.setting.render.debug = true;
        this.engine.setting.shadow.shadowBound = 5;

        this.scene = new Scene3D();
        this.scene.addComponent(Stats);
        let sky = this.scene.addComponent(AtmosphericComponent);
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, this.engine.aspect, 1, 5000.0);

        camera.object3D.addComponent(HoverCameraController).setCamera(0, -60, 60);

        this.view = new View3D();
        this.view.scene = this.scene;
        this.view.camera = camera;

        this.engine.startRenderView(this.view);

        await this.initScene();

        this.scene.addChild(new AxisObject(10, 0.1));

        sky.relativeTransform = this.lightObj3D.transform;
    }

    async initScene() {
        {
            /******** light *******/
            this.lightObj3D = new Object3D();
            this.lightObj3D.rotationX = 21;
            this.lightObj3D.rotationY = 108;
            this.lightObj3D.rotationZ = 10;
            let directLight = this.lightObj3D.addComponent(DirectLight);
            directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            directLight.castShadow = false;
            directLight.intensity = 20;
            this.scene.addChild(this.lightObj3D);
            await this.addNode();
        }
        {
            let floor = Object3DUtil.GetSingleCube(100, 0.1, 100, 0.2, 0.2, 0.2);
            floor.y = -0.2;
            this.scene.addChild(floor);
        }
    }

    private maker: Shape3DMaker;
    private async addNode() {
        let textureArray:any[] = [];
        textureArray.push((await this.engine.res.loadTexture('https://cdn.orillusion.com/textures/128/vein_0013.png')) as BitmapTexture2D);
        textureArray.push((await this.engine.res.loadTexture('https://cdn.orillusion.com/textures/128/vein_0014.png')) as BitmapTexture2D);

        let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);
        bitmapTexture2DArray.setTextures(textureArray);

        this.maker = Shape3DMaker.makeRenderer(`path`, bitmapTexture2DArray, this.scene);
        this.maker.renderer.material.doubleSide = true;

        this.createPath();
    }

    private createPath(): Shape3D {
        let circle = this.maker.arc(20, 0, 360, undefined);
        circle.lineWidth = 2;
        circle.segment = 40;
        circle.fill = true;
        circle.line = true;
        circle.isClosed = false;
        circle.lineUVRect.z = 0.5;
        circle.lineUVRect.w = 0.5;
        circle.fillUVRect.z = 0.1;
        circle.fillUVRect.w = 0.1;

        circle.fillTextureID = 0;
        circle.lineTextureID = 1;

        circle.lineColor = Color.random();
        circle.uvSpeed = new Vector4(0, 0, 0, Math.random() - 0.5).multiplyScalar(0.005);

        const GUIHelp = new dat.GUI();
        this.renderCircle(GUIHelp, circle, 5, false);
        return circle;
    }

    update() {}

    renderCircle(GUIHelp: dat.GUI, shape: CircleShape3D, maxSize: number, open: boolean = true, name?: string) {
        name ||= 'Circle3D_' + shape.shapeIndex;
        GUIHelp.addFolder(name);
        GUIHelp.add(shape, 'radius', 0, maxSize, 0.1);
        GUIHelp.add(shape, 'segment', 0, 100, 1);
        GUIHelp.add(shape, 'startAngle', 0, 360, 1);
        GUIHelp.add(shape, 'endAngle', 0, 360, 1);
        let arcType = {};
        arcType['sector'] = CircleArcType.Sector;
        arcType['moon'] = CircleArcType.Moon;
        GUIHelp.add({ arcType: shape.arcType }, 'arcType', arcType).onChange((v) => {
            shape.arcType = Number.parseInt(v);
        });

        this.renderCommonShape3D(GUIHelp, shape, maxSize);
    }

    renderCommonShape3D(GUIHelp: dat.GUI, shape: Shape3D, maxSize: number, uvMin: number = 0.01, uvMax: number = 1.0) {
        GUIHelp.add(shape, 'line');
        GUIHelp.add(shape, 'fill');
        GUIHelp.add(shape, 'isClosed');
        GUIHelp.add(shape, 'lineWidth', 0, maxSize, 0.01);
        GUIHelp.add(shape, 'fillRotation', -Math.PI, Math.PI, 0.01);

        this.renderVec4(GUIHelp, 'FillUVRect.', shape, 'fillUVRect', 0, 10, 0.01);
        this.renderVec4(GUIHelp, 'LineUVRect.', shape, 'lineUVRect', 0, 10, 0.01);
        this.renderVec4(GUIHelp, 'UVSpeed.', shape, 'uvSpeed', -0.01, 0.01, 0.0001);

        GUIHelp.add(shape, 'lineTextureID', 0, 1, 1);
        GUIHelp.add(shape, 'fillTextureID', 0, 1, 1);
    }

    renderVec4(GUIHelp: dat.GUI, label: string, target: Object, key: string, min: number, max: number, step: number = 0.01) {
        let components = ['x', 'y', 'z', 'w'];
        let data = {} as any;
        let vec4: Vector4 = target[key];
        for (let component of components) {
            data[label + component] = vec4[component];
            GUIHelp.add(data, label + component, min, max, step).onChange((v) => {
                vec4[component] = v;
                target[key] = vec4;
            });
        }
    }
}

new Sample_Shape3DPath2D().run();

For more Shape3D API usage, please refer to the Shape3D example code.

Released under the MIT License