import { mat4 } from "gl-matrix";
import { LineBasicMaterial } from "three";
import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
import { makeEngineOptionsModel } from "../../../../../../../../../model/game/options/makeEngineOptionsModel.js";
import { enableEditor } from "../../../../../../../../editor/enableEditor.js";
import { makeHelperBoxGeometry } from "../../../../../../../../editor/process/symbolic/makeHelperBoxGeometry.js";
import { AABB3 } from "../../../../../../../core/geom/3d/aabb/AABB3.js";
import { TransformedShape3D } from "../../../../../../../core/geom/3d/shape/TransformedShape3D.js";
import { UnionShape3D } from "../../../../../../../core/geom/3d/shape/UnionShape3D.js";
import { UnitCubeShape3D } from "../../../../../../../core/geom/3d/shape/UnitCubeShape3D.js";
import { UnitSphereShape3D } from "../../../../../../../core/geom/3d/shape/UnitSphereShape3D.js";
import {
    shape_to_visual_entity,
    SHAPE_VISUAL_ENTITY_TAG
} from "../../../../../../../core/geom/3d/shape/util/shape_to_visual_entity.js";
import Vector1 from "../../../../../../../core/geom/Vector1.js";
import Vector2 from "../../../../../../../core/geom/Vector2.js";
import Vector3 from "../../../../../../../core/geom/Vector3.js";
import { clamp01 } from "../../../../../../../core/math/clamp01.js";
import { pingpong } from "../../../../../../../core/math/pingpong.js";
import { randomFloatBetween } from "../../../../../../../core/math/random/randomFloatBetween.js";
import { randomFromArray } from "../../../../../../../core/math/random/randomFromArray.js";
import { seededRandom } from "../../../../../../../core/math/random/seededRandom.js";
import { computeStatisticalMean } from "../../../../../../../core/math/statistics/computeStatisticalMean.js";
import ButtonView from "../../../../../../../view/elements/button/ButtonView.js";
import { CanvasView } from "../../../../../../../view/elements/CanvasView.js";
import EmptyView from "../../../../../../../view/elements/EmptyView.js";
import { AttachmentSystem } from "../../../../../../ecs/attachment/AttachmentSystem.js";
import Entity from "../../../../../../ecs/Entity.js";
import { pick } from "../../../../../../ecs/grid/pick.js";
import GUIElement from "../../../../../../ecs/gui/GUIElement.js";
import GUIElementSystem from "../../../../../../ecs/gui/GUIElementSystem.js";
import HeadsUpDisplay from "../../../../../../ecs/gui/hud/HeadsUpDisplay.js";
import HeadsUpDisplaySystem from "../../../../../../ecs/gui/hud/HeadsUpDisplaySystem.js";
import ViewportPosition from "../../../../../../ecs/gui/position/ViewportPosition.js";
import ViewportPositionSystem from "../../../../../../ecs/gui/position/ViewportPositionSystem.js";
import Renderable from "../../../../../../ecs/renderable/Renderable.js";
import AnimationSystem from "../../../../../../ecs/systems/AnimationSystem.js";
import RenderSystem from "../../../../../../ecs/systems/RenderSystem.js";
import { find_entities_with_tag } from "../../../../../../ecs/tag/find_entities_with_tag.js";
import ClingToTerrainSystem from "../../../../../../ecs/terrain/ecs/cling/ClingToTerrainSystem.js";
import TerrainSystem from "../../../../../../ecs/terrain/ecs/TerrainSystem.js";
import { obtainTerrain } from "../../../../../../ecs/terrain/util/obtainTerrain.js";
import { Transform } from "../../../../../../ecs/transform/Transform.js";
import { EngineConfiguration } from "../../../../../../EngineConfiguration.js";
import { EngineHarness } from "../../../../../../EngineHarness.js";
import InputController from "../../../../../../input/ecs/components/InputController.js";
import InputControllerSystem from "../../../../../../input/ecs/systems/InputControllerSystem.js";
import { InputSystem } from "../../../../../../input/ecs/systems/InputSystem.js";
import { RepeatBehavior } from "../../../../../../intelligence/behavior/decorator/RepeatBehavior.js";
import { BehaviorComponent } from "../../../../../../intelligence/behavior/ecs/BehaviorComponent.js";
import { BehaviorSystem } from "../../../../../../intelligence/behavior/ecs/BehaviorSystem.js";
import { ActionBehavior } from "../../../../../../intelligence/behavior/primitive/ActionBehavior.js";
import { SoundEmitterSystem } from "../../../../../../sound/ecs/emitter/SoundEmitterSystem.js";
import SoundControllerSystem from "../../../../../../sound/ecs/SoundControllerSystem.js";
import SoundListenerSystem from "../../../../../../sound/ecs/SoundListenerSystem.js";
import { CameraSystem } from "../../../../../ecs/camera/CameraSystem.js";
import TopDownCameraController from "../../../../../ecs/camera/topdown/TopDownCameraController.js";
import TopDownCameraControllerSystem from "../../../../../ecs/camera/topdown/TopDownCameraControllerSystem.js";
import MeshHighlightSystem from "../../../../../ecs/highlight/system/MeshHighlightSystem.js";
import LightSystem from "../../../../../ecs/light/LightSystem.js";
import { DrawMode } from "../../../../../ecs/mesh-v2/DrawMode.js";
import { ShadedGeometry } from "../../../../../ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometrySystem } from "../../../../../ecs/mesh-v2/ShadedGeometrySystem.js";
import Mesh from "../../../../../ecs/mesh/Mesh.js";
import { MeshSystem } from "../../../../../ecs/mesh/MeshSystem.js";
import { PathDisplaySystem } from "../../../../../ecs/path/PathDisplaySystem.js";
import Trail2DSystem from "../../../../../ecs/trail2d/Trail2DSystem.js";
import { load_and_set_cubemap_v0 } from "../../../../../load_and_set_cubemap_v0.js";
import {
    AmbientOcclusionPostProcessEffect
} from "../../../../../render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
import sampler2d_to_html_canvas from "../../../../../texture/sampler/sampler2d_to_html_canvas.js";
import { threeUpdateTransform } from "../../../../../util/threeUpdateTransform.js";
import { ParticleEmitterSystem } from "../../../../ecs/ParticleEmitterSystem.js";
import { ParameterLookupTable } from "../../parameter/ParameterLookupTable.js";
import { RGBA_LUT_HEATMAP_IR } from "../../parameter/sample/RGBA_LUT_HEATMAP_IR.js";
import { lut_to_sampler } from "./lut_to_sampler.js";
import { ParticleVolume } from "./ParticleVolume.js";
import { SamplingFunctionKind } from "./SamplingFunctionKind.js";

const engineHarness = new EngineHarness();

function makeConfig(engine) {

    const config = new EngineConfiguration();

    /**
     *
     * @type {SoundEngine}
     */
    const sound = engine.sound;
    const graphics = engine.graphics;
    const assetManager = engine.assetManager;
    const devices = engine.devices;

    const guiSystem = new GUIElementSystem(engine.gui.view, engine);
    const headsUpDisplaySystem = new HeadsUpDisplaySystem(graphics);

    config.addManySystems(
        new AttachmentSystem(),
        new SoundEmitterSystem(assetManager, sound.destination, sound.context),
        new SoundControllerSystem(),
        new SoundListenerSystem(sound.context),
        guiSystem,
        new AnimationSystem(graphics.viewport.size),
        new TopDownCameraControllerSystem(graphics),
        new CameraSystem(engine.graphics),
        new MeshSystem(engine),
        new ClingToTerrainSystem(),
        new TerrainSystem(graphics, assetManager),
        new Trail2DSystem(engine),
        new ViewportPositionSystem(graphics.viewport.size),
        new InputControllerSystem(devices),
        new InputSystem(devices),
        new MeshHighlightSystem(engine),
        new LightSystem(engine, {
            shadowResolution: 2048
        }),
        headsUpDisplaySystem,
        new BehaviorSystem(engine),
        new PathDisplaySystem(engine),
        new ParticleEmitterSystem(engine),
        new ShadedGeometrySystem(engine)
    );


    // Plugins
    config.addPlugin(AmbientOcclusionPostProcessEffect);


    // Knowledge

    return config;
}


/**
 *
 * @param {EngineHarness} harness
 * @return {Promise<void>}
 */
async function init(harness) {
    const engine = harness.engine;

    engine.entityManager.addSystem(new RenderSystem(engine.graphics));

    makeEngineOptionsModel(engine.options, engine);

    await makeConfig(engine).apply(engine);

    enableEditor(engine);

    await load_and_set_cubemap_v0(engine.graphics, 'data/textures/cubemaps/hip_miramar/32/', '.png');


    await harness.initialize();

    main(engine);
}


const heatmap_lut_0 = RGBA_LUT_HEATMAP_IR;

const heatmap_lut_orange_to_red = new ParameterLookupTable(4);
heatmap_lut_orange_to_red.write([
    255, 127, 0, 100,
    206, 2, 86, 100
]);
heatmap_lut_orange_to_red.computeUniformPositions();

const heatmap_lut_pink_to_blue = new ParameterLookupTable(4);
heatmap_lut_pink_to_blue.write([
    255, 151, 213, 50,
    2, 243, 255, 50
]);
heatmap_lut_pink_to_blue.computeUniformPositions();

const heatmap_lut_discrete = new ParameterLookupTable(4);
heatmap_lut_discrete.write(
    [
        196, 196, 196, 255,
        196, 196, 196, 255,
        0, 230, 0, 255,
        0, 230, 0, 255,
        255, 212, 0, 255,
        255, 212, 0, 255,
    ],
    [
        0,
        0.1,
        0.1,
        0.7,
        0.7,
        1
    ]
);


/**
 *
 * @param {Engine} engine
 */
function make_shape_vis_toggle(engine) {
    const ecd = engine.entityManager.dataset;
    const entities = find_entities_with_tag(SHAPE_VISUAL_ENTITY_TAG, ecd);


    const builders = entities.map(e => Entity.readFromDataset(e, ecd));

    function toggle() {
        if (builders.length <= 1) {
            return;
        }

        if (builders[0].isBuilt) {
            builders.forEach(b => b.destroy());
        } else {
            builders.forEach(b => b.build(ecd));
        }
    }

    const buttonView = new ButtonView({
        action: toggle,
        name: 'Toggle Shapes',
        css: {
            position: 'absolute',
            bottom: '10px',
            right: '10px',
            background: 'rgba(255,255,255,0.8)',
            border: '1px solid rgba(0,0,0,1)',
            padding: '2px'
        }
    });

    new Entity()
        .add(GUIElement.fromView(buttonView))
        .build(ecd);

    return {
        toggle
    };
}

function mat_from_aabb3(aabb3) {
    const m4 = mat4.create();

    mat4.fromRotationTranslationScale(m4, [0, 0, 0, 1], [(aabb3.x0 + aabb3.x1) / 2, (aabb3.y0 + aabb3.y1) / 2, (aabb3.z0 + aabb3.z1) / 2], [aabb3.getExtentsX(), aabb3.getExtentsY(), aabb3.getExtentsZ()]);

    return m4;
}

/**
 *
 * @param {string} url
 * @param {EntityComponentDataset} ecd
 * @returns {Entity}
 */
function load_model(url, ecd) {

    const b = new Entity();
    b
        .add(new Transform())
        .add(Mesh.fromJSON({
            url: url,
            castShadow: true,
            receiveShadow: true
        }))
        .build(ecd);

    return b;
}

/**
 *
 * @param {Engine} engine
 * @returns {Promise<void>}
 */
async function sample_scene_0(engine) {
    const ecd = engine.entityManager.dataset;

    function load_model_s(url, y_offset = 0) {
        const b = load_model(url, ecd);
        b.getComponent(Transform).position.set(
            20,
            -5.8 + y_offset,
            100
        );

        return b;
    }

    // load_model('moicon/06_04_hapro/00-environment/model.gltf', -0.1);
    load_model_s('moicon/06_04_hapro/05-ground-floor/model.gltf');
    load_model_s('C:/git/moh/app/moicon/06_04_hapro/08-clickable-assets-new/model.gltf');
    // load_model('moicon/06_04_hapro/02-bearers/model.gltf');
    // load_model('moicon/06_04_hapro/04-1st-floor/model.gltf');


    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_0,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), mat_from_aabb3(new AABB3(67.73, 0, 89, 115.84, 3, 118.34))),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), mat_from_aabb3(new AABB3(98, 0, 76.5, 109.5, 3, 89)))
        ]),
        particle_size: 4,
        density: 4,
        period: 10
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_0,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), mat_from_aabb3(new AABB3(67.73, 0, 89, 115.84, 3, 118.34))),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), mat_from_aabb3(new AABB3(98, 0, 76.5, 109.5, 3, 89)))
        ]),
        particle_size: 2,
        density: 1,
        period: 10,
        period_offset: 3,
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), mat_from_aabb3(new AABB3(97.68, 0, 67.71, 115.76, 3, 76.14))),
        particle_size: 1,
        density: 1.2,
        period: 7,
        period_offset: 2,
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [15.218520811592711, 0, 0, 0, 0, 3.058639690434693, 0, 0, 0, 0, 5.828701399328644, 0, 89.53848861962253, 2.0721850959505907, 73.05504876489894, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [18.10977091153633, 0, 0, 0, 0, 3.058639690434693, 0, 0, 0, 0, 2.5862076050328002, 0, 88.03260126722904, 2.0721850959505907, 69.0797462656922, 1])
        ]),
        particle_size: 4,
        density: 4,
        period: 7,
        period_offset: 2,
    });

    // large open room, dense particles
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_discrete,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [20.977896627021547, 0, 0, 0, 0, 2.956899734575006, 0, 0, 0, 0, 36.201589869571954, 0, 99.2120028317975, 1.5919924971428276, 49.21655402009093, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [24.455615450514458, 0, 0, 0, 0, 2.956899734575006, 0, 0, 0, 0, 28.081536987027807, 0, 80.11618755300161, 1.5919924971428276, 45.13808745329574, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.562874425497998, 0, 0, 0, 0, 2.956899734575006, 0, 0, 0, 0, 2.654155224548708, 0, 70.80986716000054, 1.5919924971428276, 59.679876796143446, 1])
        ]),
        particle_size: 0.15,
        density: 5,
        period: 7,
        period_offset: 2,
    });

    // popsicle
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [-3.8971594611231577, -1.9562533614625899, -0.5725042419276598, 0, -8.346390838999248, 16.986127440019352, -1.226109479938015, 0, 0.12112312197093604, 0, -0.8245111322851176, 0, 55.83305788880312, 9.579537179233043, 83.59013920067233, 1]),
            TransformedShape3D.from_m4(new UnitSphereShape3D(), [10.237283879346698, 0, 0, 0, 0, 10.237283879346698, 0, 0, 0, 0, 10.237283879346698, 0, 51.783732965076716, 18.028888481196716, 82.91084586075068, 1])
        ]),
        particle_size: 0.7,
        density: 5,
        period: 3,
        period_offset: 2,
        sort: true
    });

    engine.devices.pointer.on.down.add((p) => {
        pick(p.x, p.y, engine.graphics, obtainTerrain(ecd), (grid_position, position, normal) => {
            console.log(position.toJSON());
        });
    });

    make_shape_vis_toggle(engine).toggle(); // turn off shape vis initially

    // editing box
    make_box_editor(engine, Transform.fromJSON({
        position: {
            x: 20,
            y: 5,
            z: 100
        },
        scale: {
            x: 1,
            y: 1,
            z: 1
        }
    }));
}


/**
 *
 * @param {Engine} engine
 * @returns {Promise<void>}
 */
async function sample_scene_nam(engine) {
    const ecd = engine.entityManager.dataset;

    ecd.getAnyComponent(TopDownCameraController).component.fromJSON({
        "target": {
            "x": 21.09522080045099,
            "y": -0.6327784109623013,
            "z": 6.138907038731565
        },
        "pitch": 1.1099999999999923,
        "yaw": -3.143185307179591,
        "roll": 0,
        "distance": 29,
        "distanceMin": 0,
        "distanceMax": 1000
    });

    load_model('moicon/20_Dec_2021_Nammo/with_assets(for_alex)/model.gltf', ecd)
    // load_model('moicon/20_Dec_2021_Nammo/with_assets_micron_conveted/model.gltf', ecd)
    // load_model('moicon/micron_container/model.gltf', ecd)
    // load_model('moicon/test_philippe_jan_2022/test/model.gltf', ecd)

    const P_SIZE_SMALL = 0.15;
    const P_SIZE_MEDIUM = 0.6;
    const P_SIZE_LARGE = 2;

    const random = seededRandom(42);

    const PERIOD_LONG = 13;

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.267098043281134, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 2.8037239954452864, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Hexagon_01.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.7577406314060395, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 8.075207307031247, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.956797095914032, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 14.17733466985805, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/travnik/plus_2.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.733772190018377, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 20.284613387782148, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.924905976332568, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 31.662442543335885, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_0,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.7305058801925, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 37.70942297505848, 1.4169994923726943, 11.460158766988037, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 8,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_0,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.575632395877399, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 37.272800538855826, 1.4169994923726943, 2.4066558057134095, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 8,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_0,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.410614101275306, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 30.458756650580582, 1.4169994923726943, 2.4066558057134095, 1]),
        particle_size: P_SIZE_MEDIUM,
        density: 8,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_discrete,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.260857135913424, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 24.82865688356064, 1.4169994923726943, 2.4066558057134095, 1]),
        particle_size: P_SIZE_SMALL,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_01.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_discrete,
        shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.870266350649175, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 20.022408905267525, 1.4169994923726943, 2.4066558057134095, 1]),
        particle_size: P_SIZE_SMALL,
        density: 3,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Cross_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.985827128973811, 0, 0, 0, 0, 2.435269776236539, 0, 0, 0, 0, 4.669621139110231, 0, 11.08356834576874, 1.2643565739307023, 2.4360416869081885, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [8.413758403821523, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 7.775469675813772, 0, 4.313201108449287, 1.4169994923726943, 4.011630095541064, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [8.413758403821523, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 2.925065210015915, 0, 12.417224289491141, 1.4169994923726943, 6.432483110925785, 1])
        ]),
        particle_size: P_SIZE_LARGE,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    make_shape_vis_toggle(engine).toggle(); // turn off shape vis initially

    // editing box
    // make_box_editor(engine, Transform.fromJSON({
    //     position: {
    //         x: -2,
    //         y: 5,
    //         z: 0
    //     },
    //     scale: {
    //         x: 1,
    //         y: 1,
    //         z: 1
    //     }
    // }));
}

/**
 *
 * @param {Engine} engine
 * @returns {Promise<void>}
 */
async function sample_scene_moicon_0(engine) {
    const ecd = engine.entityManager.dataset;

    ecd.getAnyComponent(TopDownCameraController).component.fromJSON({
        "target": {
            "x": 21.09522080045099,
            "y": -0.6327784109623013,
            "z": 6.138907038731565
        },
        "pitch": 1.1099999999999923,
        "yaw": -3.143185307179591,
        "roll": 0,
        "distance": 29,
        "distanceMin": 0,
        "distanceMax": 1000
    });

    load_model('moicon/10_Feb_22_Demo/1/model.gltf', ecd);
    load_model('moicon/10_Feb_22_Demo/2/model.gltf', ecd);
    // load_model('moicon/10_Feb_22_Demo/3/model.gltf', ecd); // volume markers - main floor
    // load_model('moicon/10_Feb_22_Demo/4/model.gltf', ecd); // volume markers - small corner room
    load_model('moicon/10_Feb_22_Demo/5/model.gltf', ecd);
    // load_model('moicon/10_Feb_22_Demo/6/model.gltf', ecd); // roof
    load_model('moicon/10_Feb_22_Demo/7/model.gltf', ecd);
    load_model('moicon/10_Feb_22_Demo/assets/model.gltf', ecd);

    const P_SIZE_SMALL = 0.15;
    const P_SIZE_MEDIUM = 0.6;
    const P_SIZE_LARGE = 2;

    const random = seededRandom(42);

    const PERIOD_LONG = 13;

    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_pink_to_blue,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.267098043281134, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 2.8037239954452864, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Hexagon_01.png'
    // });
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_orange_to_red,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.7577406314060395, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 8.075207307031247, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_pink_to_blue,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.956797095914032, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 14.17733466985805, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/travnik/plus_2.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_pink_to_blue,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.733772190018377, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 20.284613387782148, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_pink_to_blue,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.924905976332568, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 31.662442543335885, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_0,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.7305058801925, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 6.606365126660234, 0, 37.70942297505848, 1.4169994923726943, 11.460158766988037, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 8,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_0,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.575632395877399, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 37.272800538855826, 1.4169994923726943, 2.4066558057134095, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 8,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_0,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.410614101275306, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 30.458756650580582, 1.4169994923726943, 2.4066558057134095, 1]),
    //     particle_size: P_SIZE_MEDIUM,
    //     density: 8,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_3,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.260857135913424, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 24.82865688356064, 1.4169994923726943, 2.4066558057134095, 1]),
    //     particle_size: P_SIZE_SMALL,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_01.png'
    // });
    //
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_3,
    //     shape: TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.870266350649175, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 4.51714948412602, 0, 20.022408905267525, 1.4169994923726943, 2.4066558057134095, 1]),
    //     particle_size: P_SIZE_SMALL,
    //     density: 3,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Cross_04.png'
    // });
    // await make_demo_volume({
    //     ecd: ecd,
    //     lut: heatmap_lut_pink_to_blue,
    //     shape: UnionShape3D.from([
    //         TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.985827128973811, 0, 0, 0, 0, 2.435269776236539, 0, 0, 0, 0, 4.669621139110231, 0, 11.08356834576874, 1.2643565739307023, 2.4360416869081885, 1]),
    //         TransformedShape3D.from_m4(new UnitCubeShape3D(), [8.413758403821523, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 7.775469675813772, 0, 4.313201108449287, 1.4169994923726943, 4.011630095541064, 1]),
    //         TransformedShape3D.from_m4(new UnitCubeShape3D(), [8.413758403821523, 0, 0, 0, 0, 2.3166487265965343, 0, 0, 0, 0, 2.925065210015915, 0, 12.417224289491141, 1.4169994923726943, 6.432483110925785, 1])
    //     ]),
    //     particle_size: P_SIZE_LARGE,
    //     density: 4,
    //     period: PERIOD_LONG,
    //     period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
    //     sprite: 'data/textures/particle/UETools/Circle_04.png'
    // });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [24.84144698268643, 0, 0, 0, 0, 5, 0, 0, 0, 0, 10.014132199037626, 0, 17.535114352814745, 2.5767637841580786, 15.087856547232908, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [3.119746486000208, 0, 0, 0, 0, 5, 0, 0, 0, 0, 5.08916554361867, 0, 6.026351574317095, 2.5767637841580786, 17.554804169904074, 1])
        ]),
        particle_size: P_SIZE_LARGE,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.23148852648068, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 4.843722925544828, 0, 2.178004397277256, 1.4442400806689264, 17.49419453432575, 1])
        ]),
        particle_size: P_SIZE_MEDIUM,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.23148852648068, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 4.843722925544828, 0, -2.3684201443478874, 1.4819864080894103, 17.435101092023466, 1])
        ]),
        particle_size: P_SIZE_LARGE,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.827875822302671, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 7.195620725226215, 0, -7.134776782683499, 1.4819864080894103, 16.41960212963852, 1])
        ]),
        particle_size: P_SIZE_MEDIUM,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.827875822302671, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 7.142815034586885, 0, -12.122694984141244, 1.4819864080894103, 16.39260186069935, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [2.0910380492069933, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 5.042906378175556, 0, -13.835348677127275, 1.4819864080894103, 17.348732325334534, 1])
        ]),
        particle_size: P_SIZE_LARGE,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.630054702949254, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 5.132455640292213, 0, -17.444335306984925, 1.4819864080894103, 17.416555162219638, 1])
        ]),
        particle_size: P_SIZE_MEDIUM,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_pink_to_blue,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [3.9334060066229277, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 4.909748196656206, 0, -2.993297445749089, 1.4819864080894103, -6.9614900068054615, 1])
        ]),
        particle_size: P_SIZE_LARGE,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_orange_to_red,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [5.693637284485993, 0, 0, 0, 0, 2.8688316144733066, 0, 0, 0, 0, 9.774881301181045, 0, -26.90124286380605, 4.656150234283974, 14.984350120152822, 1])
        ]),
        particle_size: P_SIZE_MEDIUM,
        density: 4,
        period: PERIOD_LONG,
        period_offset: randomFloatBetween(random, 0, PERIOD_LONG),
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_discrete,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [6.331512511030024, 0, 0, 0, 0, 2.329600948715924, 0, 0, 0, 0, 9.610682816430089, 0, -21.021354476323573, 1.3409243433436, -1.345765978128314, 1])]),
        particle_size: P_SIZE_SMALL,
        density: 4,
        period: PERIOD_LONG,
        period_offset: 0,
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });
    await make_demo_volume({
        ecd: ecd,
        lut: heatmap_lut_discrete,
        shape: UnionShape3D.from([
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [13.623136537846888, 0, 0, 0, 0, 2.193382560214131, 0, 0, 0, 0, 4.880453458023407, 0, 8.339521865997591, 1.1217848106509791, -1.5586407794900627, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.162666210784313, 0, 0, 0, 0, 1.7455477622575266, 0, 0, 0, 0, 4.880453458023407, 0, 2.0126515547811556, 0.9110827940157051, -1.5586407794900627, 1]),
            TransformedShape3D.from_m4(new UnitCubeShape3D(), [4.162666210784313, 0, 0, 0, 0, 1.1232395183163202, 0, 0, 0, 0, 3.565672994277359, 0, 2.0440383546262932, 1.8620329637166853, -2.1963940023653703, 1])
        ]),
        particle_size: P_SIZE_SMALL,
        density: 4,
        period: PERIOD_LONG,
        period_offset: PERIOD_LONG * 0.5,
        sprite: 'data/textures/particle/UETools/Circle_04.png'
    });

    make_shape_vis_toggle(engine).toggle(); // turn off shape vis initially

    // editing box
    // make_box_editor(engine, Transform.fromJSON({
    //     position: {
    //         x: -2,
    //         y: 0,
    //         z: 0
    //     },
    //     scale: {
    //         x: 5,
    //         y: 5,
    //         z: 5
    //     }
    // }));
}

/**
 *
 * @param {Engine} engine
 */
async function main(engine) {
    await EngineHarness.buildBasics({
        engine,
        enableWater: false,
        enableTerrain: false,
        // enableTerrain: true,
        terrainSize: new Vector2(100, 100),
        focus: new Vector3(102, 0, 61),
        // pitch: -0.5,
        // yaw: 2.2,
        // distance: 3
        pitch: 0.9,
        yaw: 3.1,
        distance: 20
    });


    engine.graphics.renderer.setClearColor('rgba(99,99,99,1)');

    // await sample_scene_nam(engine);
    await sample_scene_0(engine);
    // await sample_scene_moicon_0(engine);
}

/**
 *
 * @param {Engine} engine
 * @param {Transform} [transform]
 */
function make_box_editor(engine, transform = new Transform()) {
    const controls = new TransformControls(engine.graphics.camera, engine.graphics.domElement);

    const eb = new Entity();
    const g_unit_cube = makeHelperBoxGeometry(1, 1, 1);

    const ecd = engine.entityManager.dataset;

    const lineMaterial = new LineBasicMaterial({
        depthTest: true,
        depthWrite: false,
        transparent: true,
        linewidth: 1,
        fog: false,
        color: '#4aff51',
        opacity: 1
    });

    const box_transform = transform.clone();
    eb.add(box_transform);
    const sg = ShadedGeometry.from(g_unit_cube, lineMaterial, DrawMode.LineSegments);
    eb.add(sg);

    eb.build(ecd);

    const cachedObject = sg.__cached_object;
    cachedObject.frustumCulled = false;
    controls.attach(cachedObject);


    controls.setSize(1);
    const r_controls = new Renderable(controls);
    r_controls.matrixAutoUpdate = true;
    r_controls.boundingBoxNeedsUpdate = false;
    r_controls.boundingBox.setBounds(0, 0, 0, 1, 1, 1);

    // make camera controller holder
    const entity = find_entities_with_tag('CameraController', ecd)[0];

    const camera_controls = Entity.readFromDataset(entity, ecd);


    new Entity()
        .add(r_controls)
        .add(new Transform())
        .add(new InputController([
            {
                path: 'keyboard/keys/w/down',
                listener() {
                    controls.setMode('translate');
                }
            },
            {
                path: 'keyboard/keys/e/down',
                listener() {
                    controls.setMode('scale');
                }
            },
            {
                path: 'keyboard/keys/r/down',
                listener() {
                    controls.setMode('rotate');
                }
            },
            {
                path: 'keyboard/keys/s/down',
                listener() {
                    // output current box

                    const code_snippet = `TransformedShape3D.from_m4(new UnitCubeShape3D(), ${JSON.stringify(cachedObject.matrix.elements)})`;
                    console.warn(code_snippet);

                    const navigator = globalThis.navigator;
                    navigator.clipboard.writeText(code_snippet);
                }
            },
            {
                path: 'keyboard/keys/space/down',
                listener() {
                    // reset scale and rotation
                    box_transform.scale.setScalar(1);
                    box_transform.rotation.set(0, 0, 0, 1);
                }
            }
        ]))
        .add(BehaviorComponent.loop(() => {

            threeUpdateTransform(controls);

            if (controls.dragging) {
                if (camera_controls.isBuilt) {
                    camera_controls.destroy();
                }
            } else {
                if (!camera_controls.isBuilt) {
                    camera_controls.build(ecd);
                }
            }

            cachedObject.updateMatrix();
            const matrix = cachedObject.matrix;
            const t = eb.getComponent(Transform);

            t.position.onChanged.mute();
            t.rotation.onChanged.mute();
            t.scale.onChanged.mute();

            t.fromMatrix(matrix.elements);

            t.position.onChanged.unmute();
            t.rotation.onChanged.unmute();
            t.scale.onChanged.unmute();

            sg.updateTransform();

        }))
        .build(ecd);
}

/**
 *
 * @param {EntityComponentDataset} ecd
 * @param {AbstractShape3D} shape
 * @param {number} density
 * @param {number} particle_size
 * @param {boolean} sort
 * @returns {ParticleVolume}
 */
function create_volume({
                           ecd,
                           shape = new UnitCubeShape3D(),
                           density = 10,
                           particle_size = 1,
                           sort = false,
                           sprite
                       }) {

    const adjusted_density = density / particle_size;

    const v = new ParticleVolume();

    v.from({
        density: adjusted_density,
        particle_size,
        shape: shape,
        sort,
        sprite,
        sampling_function: randomFromArray(Math.random, [SamplingFunctionKind.Random, SamplingFunctionKind.Grid])
    });

    v.build();

    v.entity.build(ecd);

    return v;
}

/**
 *
 * @param {Vector3} position
 * @param {ParameterLookupTable} lut
 * @return {{position: Vector1, entity:Entity}}
 */
function make_symbol_view({
                              position,
                              lut
                          }) {

    const r = {
        position: new Vector1(1)
    };

    const symbol_view = new EmptyView();
    symbol_view.size.set(64, 20);

    symbol_view.css({
        position: 'absolute',
        whiteSpace: 'pre',
        left: 0,
        top: 0
    });

    const view_lut = new CanvasView();
    view_lut.size.set(64, 4);
    view_lut.css({
        position: 'absolute',
        border: '1px solid white',
        boxShadow: '0 0 6px rgba(0,0,0,0.4)',
        left: 0,
        top: 0
    });

    const sampler_lut = lut_to_sampler(lut, view_lut.size.x, view_lut.size.y);

    sampler2d_to_html_canvas(sampler_lut, 1, 0, view_lut.el);

    symbol_view.addChild(view_lut)

    const view_cursor = new EmptyView();

    view_cursor.css({
        position: 'absolute',
        left: "-6px",
        top: "-4px",
        height: "10px",
        width: "10px",
        borderRadius: "10px",
        border: '2px solid white',
        zIndex: 2,
        boxShadow: '0 0 4px black'
    });


    r.position.process(f => {
        const c = new Uint8Array(4);

        lut.sample(f, c);

        const hex_string = `rgb(${c[0]},${c[1]},${c[2]})`;

        view_cursor.position.set(f * view_lut.size.x, 0);
        view_cursor.css({
            backgroundColor: hex_string
        })
    });

    symbol_view.addChild(view_cursor);

    const symbol_transform = new Transform();
    symbol_transform.position.copy(position);

    const vp = new ViewportPosition();
    vp.anchor.set(0.5, 1);

    const entity = new Entity()
        .add(GUIElement.fromView(symbol_view))
        .add(new HeadsUpDisplay())
        .add(vp)
        .add(symbol_transform);

    r.entity = entity;

    return r;
}

async function make_demo_volume({
                                    ecd,
                                    shape,
                                    lut,
                                    period = 5,
                                    period_offset = 0,
                                    density,
                                    particle_size,
                                    sort = false,
                                    sprite
                                }) {

    // estimate bounds
    const bounds = new AABB3();
    bounds.setNegativelyInfiniteBounds();

    const sample = [];
    const samples_x = [];
    const samples_y = [];
    const samples_z = [];

    const random = seededRandom(0xBEEF);

    for (let i = 0; i < 1024; i++) {
        shape.sample_random_point_in_volume(sample, 0, random);

        samples_x.push(sample[0]);
        samples_y.push(sample[1]);
        samples_z.push(sample[2]);
    }

    const v = create_volume({
        ecd,
        shape,
        density,
        particle_size,
        sort,
        sprite
    });

    let time_total = 0;

    const symbol = make_symbol_view({
        position: new Vector3(
            computeStatisticalMean(samples_x),
            computeStatisticalMean(samples_y),
            computeStatisticalMean(samples_z),
        ),
        lut
    });

    symbol.entity.build(ecd);

    v.attributes.color.lookup = lut;

    new Entity()
        .add(BehaviorComponent.from(RepeatBehavior.from(new ActionBehavior((time) => {
            time_total += time;

            const _t = pingpong(time_total + period_offset, period);

            const f = clamp01(_t / period);

            symbol.position.set(f);

            v.attributes.color.write_lookup_value(f);
        }))))
        .build(ecd);

    const shape_entity = shape_to_visual_entity(v.shape, ecd);
    shape_entity
        .getComponent(Transform)
        .copy(v.transform);
}

init(engineHarness);
