Or could the ObjectWrapper component be part of haxeui-core, but its ObjectWrapper.object member would be a typedef for a backend specific GPU 2D element? The HaxeUI backends would then define internal objects for displaying those. In any case, I don’t know enough about the division of work between haxeui-core and the backends to say whether that would cause issues compared to completely backend-specific ObjectWrapper components.
Please find below a screenshot of the test application. I’ll put the whole program here too in case it might be useful for anyone reading this thread. It is a very simple test, basically just a slightly modified Heaps Lights sample.
Heaps has a small UI system in its SampleApp class and a larger UI system called DomKit, but HaxeUI is more mature and better documented, so it’s nice to be able to use it with Heaps. In the test program, I’ve replaced the Heaps SampleApp UI with HaxeUI components. I’m using the manual update mode for HaxeUI, as I guess it is better to have it synchronized with the Heaps main loop.
The sample program is like this:
import haxe.ui.backend.BackendImpl;
import haxe.ui.components.CheckBox;
import haxe.ui.components.DropDown;
import haxe.ui.components.Label;
import haxe.ui.ComponentBuilder;
import haxe.ui.containers.VBox;
import haxe.ui.core.Screen;
import haxe.ui.Toolkit;
import hxd.App;
import hxd.Math;
class Lights extends App
{
var lights : Array<h3d.scene.pbr.Light>;
var movingObjects : Array<{ m : h3d.scene.Mesh, cx : Float, cy : Float, pos : Float, ray : Float, speed : Float }> = [];
var curLight : Int = 0;
var bitmap : h2d.Bitmap;
var styles : DropDown;
var shadows : DropDown;
var dyn : CheckBox;
var inf : Label;
var dynCullingEnable = true;
var controller : h3d.scene.CameraController;
function addCullingCollider() {
dynCullingEnable = true;
for( o in s3d ) {
if( o.cullingCollider != null ) continue;
var absPos = o.getAbsPos();
o.cullingCollider = new h3d.col.Sphere(absPos.tx, absPos.ty, absPos.tz, hxd.Math.max(o.scaleZ, hxd.Math.max(o.scaleX, o.scaleY)));
}
}
function removeCullingCollider() {
dynCullingEnable = false;
for( o in s3d ) {
o.cullingCollider = null;
}
}
override function init() {
super.init();
s3d.camera.pos.set(100, 20, 80);
controller = new h3d.scene.CameraController(s3d);
controller.loadFromCamera();
var prim = new h3d.prim.Grid(100,100,1,1);
prim.addNormals();
prim.addUVs();
var floor = new h3d.scene.Mesh(prim, s3d);
floor.material.castShadows = false;
floor.x = -50;
floor.y = -50;
var box = new h3d.prim.Cube(1,1,1,true);
box.unindex();
box.addNormals();
for( i in 0...50 ) {
var m = new h3d.scene.Mesh(box, s3d);
m.material.color.set(Math.random(), Math.random(), Math.random());
m.material.color.normalize();
m.scale(1 + Math.random() * 10);
m.z = m.scaleX * 0.5;
m.setRotation(0,0,Math.random() * Math.PI * 2);
do {
m.x = Std.random(80) - 40;
m.y = Std.random(80) - 40;
} while( m.x * m.x + m.y * m.y < 25 + m.scaleX * m.scaleX );
m.material.getPass("shadow").isStatic = true;
var absPos = m.getAbsPos();
m.cullingCollider = new h3d.col.Sphere(absPos.tx, absPos.ty, absPos.tz, hxd.Math.max(m.scaleZ, hxd.Math.max(m.scaleX, m.scaleY)));
}
var sp = new h3d.prim.Sphere(1,16,16);
sp.addNormals();
for( i in 0...20 ) {
var m = new h3d.scene.Mesh(sp, s3d);
m.material.color.set(Math.random(), Math.random(), Math.random());
m.material.color.normalize();
m.scale(0.5 + Math.random() * 4);
m.z = 2 + Math.random() * 5;
var cx = (Math.random() - 0.5) * 20;
var cy = (Math.random() - 0.5) * 20;
var absPos = m.getAbsPos();
m.cullingCollider = new h3d.col.Sphere(absPos.tx, absPos.ty, absPos.tz, hxd.Math.max(m.scaleZ, hxd.Math.max(m.scaleX, m.scaleY)));
movingObjects.push({ m : m, pos : Math.random() * Math.PI * 2, cx : cx, cy : cy, ray : 8 + Math.random() * 50, speed : (0.5 + Math.random()) * 0.2 });
}
var pt = new h3d.scene.pbr.PointLight(s3d);
pt.setPosition(0,0,15);
pt.range = 40;
pt.color.scale(20);
var sp = new h3d.scene.pbr.SpotLight(s3d);
sp.setPosition(-30,-30,30);
sp.setDirection(new h3d.Vector(1,2,-5));
sp.range = 70;
sp.angle = 70;
sp.color.scale(10);
lights = [
new h3d.scene.pbr.DirLight(new h3d.Vector(1,2,-5), s3d),
pt,
sp,
];
for( l in lights )
l.shadows.mode = Static;
s3d.computeStatic();
for( l in lights )
l.shadows.mode = Dynamic;
for( l in lights )
l.visible = false;
lights[curLight].visible = true;
// Define the UI
Toolkit.init({manualUpdate: true}); // Init HaxeUI
var root = new VBox();
Screen.instance.root = root;
s2d.add(root);
root.addComponent(ComponentBuilder.fromString(
'<vbox>
<scrollview width="220" height="400">
<vbox>
<label text="Shadowmap" />
<vbox id="shadowMapBox" width="200" height="200" />
<label text="Style" />
<dropdown id="styles">
<data>
<item text="Directional" />
<item text="Point" />
<item text="Spot" />
<item text="All" />
</data>
</dropdown>
<label text="Shadows" />
<dropdown id="shadows">
<data>
<item text="Dynamic" />
<item text="Static" />
<item text="Mixed" />
<item text="None" />
</data>
</dropdown>
<checkbox id="dyn" text="DynCulling" selected="true" />
<label id="inf" />
</vbox>
</scrollview>
</vbox>'));
// Turn off the 3D scene mouse rotation while within the UI area
root.onMouseOver = e -> if (controller.parent != null) controller.remove();
root.onMouseOut = e -> if (controller.parent == null) s3d.addChild(controller);
// Connect the UI to the scene
styles = root.findComponent("styles", DropDown);
styles.onChange = e -> {
if (styles.selectedIndex != curLight)
{
for( l in lights )
l.visible = false;
curLight = styles.selectedIndex;
if( curLight == lights.length ) {
for( l in lights )
l.visible = true;
} else
lights[curLight].visible = true;
}
};
var modes = ([Dynamic,Static,Mixed,None] : Array<h3d.pass.Shadows.RenderMode>);
shadows = root.findComponent("shadows", DropDown);
shadows.onChange = e -> {
if (lights[0].shadows.mode != modes[shadows.selectedIndex])
for( l in lights )
l.shadows.mode = modes[shadows.selectedIndex];
};
dyn = root.findComponent("dyn", CheckBox);
dyn.onChange = e -> {
if (dyn.selected != dynCullingEnable)
(dynCullingEnable = dyn.selected) ? addCullingCollider() : removeCullingCollider();
};
inf = root.findComponent("inf", Label);
// Add the shadow map view to the UI
bitmap = new h2d.Bitmap(null, null);
bitmap.scale(192 / 1024);
bitmap.filter = h2d.filter.ColorMatrix.grayed();
root.findComponent("shadowMapBox", VBox).addChild(bitmap);
}
override function update(dt:Float) {
BackendImpl.update(); // Update HaxeUI
for( m in movingObjects ) {
m.pos += m.speed / m.ray;
m.m.x = m.cx + Math.cos(m.pos) * m.ray;
m.m.y = m.cy + Math.sin(m.pos) * m.ray;
var cc = Std.downcast(m.m.cullingCollider, h3d.col.Sphere);
if( cc != null ) {
var absPos = m.m.getAbsPos();
cc.x = absPos.tx;
cc.y = absPos.ty;
cc.z = absPos.tz;
cc.r = hxd.Math.max(m.m.scaleZ, hxd.Math.max(m.m.scaleX, m.m.scaleY));
}
}
var light = lights[curLight];
var tex = light == null ? null : light.shadows.getShadowTex();
bitmap.tile = tex == null || tex.flags.has(Cube) ? null : h2d.Tile.fromTexture(tex);
inf.text = "Shadows Draw calls: "+ s3d.lightSystem.drawPasses;
for( o in s3d ) {
o.culled = false;
}
}
static function main() {
h3d.mat.MaterialSetup.current = new h3d.mat.PbrMaterialSetup();
new Lights();
}
}
It can be compiled like this:
haxe -lib heaps:1.10.0 -lib hlsdl:1.13.0 -lib haxeui-core:1.6.0 -lib haxeui-heaps:1.6.0 -D windowSize=1024x800 -debug -main Lights -hl Lights.hl
And the end result should look like this: