Skip to content

3D Graphic

Orillusion provides an extension package @orillusion/graphic for rendering dynamically changing points, lines, surfaces, and volumes. This package allows for the creation of dynamic meshes using specific methods, which are efficiently managed and integrated into the engine's rendering pipeline. It is both high-performance and user-friendly.

Three main modules are available for creating high-performance graphic data:

Graphic3D: Provides basic line drawing capabilities, commonly used for drawing auxiliary lines. Graphic3DMesh Renderer: Allows batch creation of Mesh clones in a single renderer, with the ability to freely adjust each clone's Transform, Texture, and Material, offering high flexibility in creating graphics and animations. 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, allowing developers to use familiar methods for graphic rendering.

Installation

Like the engine itself, the graphic plugins can be introduced using NPM or CDN links:

1. Installing via NPM Packages

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/stats/dist/graphic.umd.js"></script>
<script>
  const { Engine3D, Graphic3D, Shape3D } = Orillusion
</script>

Graphic3D

Create a Graphic3D object to uniformly draw graphics in the scene. Currently, three APIs are provided for quick creation of 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 } from '@orillusion/core';
import { Graphic3D } from '@orillusion/graphic';
import { Stats } from '@orillusion/stats';

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

    async run() {
        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, Engine3D.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;
        Engine3D.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));
    }
}

new GraphicLine().run();

Graphic3DMesh Renderer

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

ParameterDescription
scenewhich scene to add the renderer
geowhich geometry to clone
texturethe index of a given texture array
countmaximum number of clones in the collection that a renderer can support (choosing an appropriate value will improve performance)

TIP

The geo parameter typically uses a simple PlaneGeometry as the model source, with different textures applied to create various appearances. In theory, you can use any model source to create diverse effects. For example, by using a BoxGeometry model, you can create graphics composed of many cubes, enabling the creation of pixel art-style scenes or simulating voxel rendering.

  1. Modifying Transform: To modify the rotation, scale, or position of a specific unit at a given index, access the object3Ds belonging to the Graphic3DMeshRenderer. Use the index to obtain the corresponding Object3D and adjust its Transform. This change will be synchronized with the target unit.

  2. Modifying Texture: Call the function setTextureID to specify and modify the texture index for a particular unit at a given index. The textures are sourced from the textures array provided in the initialization parameters of the Graphic3DMeshRenderer.

  3. Modifying Material: The Graphic3DMeshRenderer class provides a series of APIs with names similar to setTextureID. The first parameter specifies the target unit, while the second parameter sets the relevant properties. Developers can use these APIs to modify various aspects of the graphics, such as Color, UV, Emissive properties, and more.

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 textures
let textureArray = [];
textureArray.push(await Engine3D.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);

bitmapTexture2DArray.setTextures(textureArray);

// take a plane as the clone sorce
let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS);

// Create a Graphic3DMeshRenderer instance with maxium 100 clones
let mr:Graphic3DMeshRenderer = Graphic3DMesh.draw(scene, geometry, bitmapTexture2DArray, 100);

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

// Get the corresponding object3Ds and modify the Transform property of that Object3D to synchronously update the Transform of the target clone
// By placing the same operation in the main update function of the engine, you can modify 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;

    graphicMeshRenderer: Graphic3DMeshRenderer;

    constructor() {}

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

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

        Engine3D.setting.render.debug = true;
        Engine3D.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, Engine3D.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;

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

    async initScene() {
        let texts:any[] = [];
        texts.push((await Engine3D.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.scaleBy(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();

See more Graphic3D API usage in Graphic3D

Shape3D Renderer

By Shape3DMaker, we can create a Shape3DRenderer renderer,which can hold and maintain a dataset of Shape3D objects. Each Shape3D corresponds to a variety of predefined shapes, such as EllipseShape3DRoundRectShape3DCircleShape3D and so on. Additionally, Path2DShape3D and Path3DShape3D offer a more extensive API that can assist you in combining and drawing complex graphics.

ParameterDescription
namename to identify Shape3DRenderer
scenewhich scene to add the renderer
textureListthe index of a given texture array
maxNodeCountmaximum number of nodes in the collection that a renderer can support
triangleEachNodehow many triangles to draw for each node

The renderer is designed based on the API of CanvasPath, allowing developers to continue using familiar development practices while working with the Orillusion engine for 3D graphics rendering. The 2D drawing section of the renderer refers to drawing points, lines, and surfaces in the XZ plane. Each unit can still be independently controlled via Transform. For drawing shapes in 3D space, you need to use Path3DShape3D to begin drawing graphics that incorporate Y-axis elevation data.

Properties

The following table provides a brief summary and description of the properties of Shape3D.

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 for textures used in filled areas
shapeOrderSets the layering of each shape (to eliminate z-fighting; each Shape3DRenderer can define the maximum range for z-fighting, and based on this range and the number of Shape3D instances, calculates the offset for each Shape3D)
points3DA placeholder for externally provided key points collection
isClosedIndicates whether the shape is closed (starts and ends at the same point)
fillIndicates whether the shape is filled
lineWidthThe width of the line when drawn
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 movement speed of the UVs for the fill area texture; zw corresponds to the movement speed of the UVs for the line texture.

Shapes

Like the CanvasPath API, we have provided some subclasses of Shape3D for users to draw specific shapes:

ShapeDescription
CircleShape3DCircle and arc shapes
CurveShape3DBezier curve controlled by two anchor points
EllipseShape3DElliptical shapes
LineShape3DPolyline shapes
Path2DShape3DDraws line paths on the XZ plane
Path3DShape3DDraws line paths in 3D space
QuadraticCurveShape3DBezier curve controlled by one anchor point
RoundRectShape3DRectangle and rounded rectangle shapes

Methods

All instances from Shape3DMaker could create shapes by the following methods:

MethodShape
ellipseEllipseShape3D
arcCircleShape3D
lineLineShape3D
quadraticCurveQuadraticCurveShape3D
curveCurveShape3D
path2DPath2DShape3D
path3DPath3DShape3D
rectRoundRectShape3D
roundRectRoundRectShape3D

TIP

All 2D shapes, e.g. Path2D, will ignore the y-axis elevation data, and will be drawn in the XZ plane.

Additionally, we could create/delete Shape3D from Shape3DRenderer

MethodsDescription
createShapecreate a new Shape3D instance
removeShapedelete a Shape3D instance
getShapeObject3Dget the Object3D instance of a shape3D

Usage

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 textures
let textureArray = [];
textureArray.push(await Engine3D.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);
bitmapTexture2DArray.setTextures(textureArray);

// create a Shape3DRenderer in the scene with a texture array
maker = Shape3DMaker.makeRenderer(`path`, bitmapTexture2DArray, scene);
maker.renderer.material.doubleSide = true;

// create a Circle shape with radius 5 at center (0, 0)
let circle:CircleShape3D = maker.arc(5, 0, 0);
circle.lineWidth = 1; // width of line
circle.segment = 16; // the segment of circle
circle.fill = true; // if fill the circle
circle.line = true; // if draw a line border
circle.uvSpeed = new Vector4(0, 0, 0, Math.random() - 0.5).multiplyScalar(0.005); // set UV speed
circle.fillColor = Color.randomRGB(); // set a fill color
circle.lineColor = Color.randomRGB(); // set a border color

circle.startAngle = 30; // set a start angle
circle.endAngle = 240; // set the end angle

// we could tween a animation of the circle by updating properties in the main loop

The above code demonstrates how to draw an independent circle/arc by creating an instance of CircleShape3D. You can also achieve this 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;

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

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

        Engine3D.setting.render.debug = true;
        Engine3D.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, Engine3D.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;

        Engine3D.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 Engine3D.res.loadTexture('https://cdn.orillusion.com/textures/128/vein_0013.png')) as BitmapTexture2D);
        textureArray.push((await Engine3D.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();

See more Shape3D API usage in Shape3D

Released under the MIT License