forked from LeenkxTeam/LNXSDK
308 lines
8.9 KiB
Haxe
308 lines
8.9 KiB
Haxe
package leenkx.trait;
|
|
|
|
import iron.Trait;
|
|
import iron.Scene;
|
|
import iron.object.LightObject;
|
|
import iron.system.Input;
|
|
import kha.graphics2.Graphics;
|
|
import zui.Zui;
|
|
import zui.Id;
|
|
import iron.data.Data;
|
|
import leenkx.ui.Canvas;
|
|
|
|
/**
|
|
* Light Controls Trait for parameter controls via ZUI
|
|
*/
|
|
|
|
class LightControl extends Trait {
|
|
|
|
@prop var enableUI: Bool = true;
|
|
@prop var toggleKey: String = "l";
|
|
|
|
var ui: Zui;
|
|
var controlsVisible: Bool = false;
|
|
var lights: Array<LightObject> = [];
|
|
var worldStrength: Float = 1.0;
|
|
var worldProbe: iron.data.WorldData = null;
|
|
var scrollHandle = new zui.Zui.Handle();
|
|
|
|
public function new() {
|
|
super();
|
|
notifyOnInit(init);
|
|
notifyOnUpdate(update);
|
|
notifyOnRender2D(render);
|
|
}
|
|
|
|
function init() {
|
|
if (Scene.active.world != null) {
|
|
worldProbe = Scene.active.world;
|
|
if (worldProbe.raw.probe != null) {
|
|
worldStrength = worldProbe.raw.probe.strength;
|
|
}
|
|
}
|
|
}
|
|
|
|
function update() {
|
|
if (!enableUI) return;
|
|
|
|
if (Input.getKeyboard().started(toggleKey)) {
|
|
controlsVisible = !controlsVisible;
|
|
|
|
if (controlsVisible) {
|
|
gatherLights();
|
|
}
|
|
if (controlsVisible && ui == null) {
|
|
Data.getFont(Canvas.defaultFontName, function(font: kha.Font) {
|
|
ui = new Zui({font: font, scaleFactor: 1.0});
|
|
});
|
|
}
|
|
}
|
|
Input.occupied = controlsVisible;
|
|
}
|
|
|
|
function render(g: Graphics) {
|
|
if (!controlsVisible || ui == null) return;
|
|
g.end();
|
|
ui.begin(g);
|
|
drawControlPanel();
|
|
ui.end();
|
|
g.begin(false);
|
|
}
|
|
|
|
function gatherLights() {
|
|
lights = [];
|
|
for (light in Scene.active.lights) {
|
|
if (light != null && light.visible) {
|
|
lights.push(light);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawControlPanel() {
|
|
var h = Id.handle();
|
|
h.nest(9000);
|
|
h.redraws = 1;
|
|
|
|
var panelWidth = 450;
|
|
var panelHeight = Std.int(iron.App.h() * 0.9);
|
|
var panelX = Std.int(iron.App.w() - panelWidth - 20);
|
|
var panelY = Std.int((iron.App.h() - panelHeight) / 2);
|
|
|
|
if (ui.window(h, panelX, panelY, panelWidth, panelHeight, true)) {
|
|
ui.text("LIGHT & SHADOW CONTROLS");
|
|
ui.separator();
|
|
|
|
var hRefresh = Id.handle();
|
|
hRefresh.nest(9001);
|
|
if (ui.button("Refresh Lights")) {
|
|
gatherLights();
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
drawWorldControls();
|
|
|
|
ui.separator();
|
|
ui.separator();
|
|
|
|
ui.text("Lights in Scene: " + lights.length);
|
|
ui.separator();
|
|
|
|
var hScroll = Id.handle();
|
|
hScroll.nest(9002);
|
|
|
|
for (i in 0...lights.length) {
|
|
drawLightControls(lights[i], i);
|
|
if (i < lights.length - 1) {
|
|
ui.separator();
|
|
ui.separator();
|
|
}
|
|
}
|
|
|
|
ui.separator();
|
|
ui.separator();
|
|
|
|
var hClose = Id.handle();
|
|
hClose.nest(9999);
|
|
if (ui.button("Close Panel")) {
|
|
controlsVisible = false;
|
|
Input.occupied = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawWorldControls() {
|
|
ui.text("-- WORLD ENVIRONMENT --", Align.Center);
|
|
ui.separator();
|
|
|
|
if (worldProbe == null || worldProbe.raw.probe == null) {
|
|
ui.text("No world probe found");
|
|
return;
|
|
}
|
|
|
|
var hWorldStr = Id.handle();
|
|
hWorldStr.nest(9100);
|
|
hWorldStr.value = worldStrength;
|
|
worldStrength = ui.slider(hWorldStr, "Strength: " + formatValue(worldStrength, 2), 0.0, 10.0, true, 100, true, Left, true);
|
|
|
|
if (hWorldStr.changed) {
|
|
worldProbe.raw.probe.strength = worldStrength;
|
|
}
|
|
}
|
|
|
|
function drawLightControls(light: LightObject, index: Int) {
|
|
var lightNameHash = 0;
|
|
for (i in 0...light.name.length) {
|
|
lightNameHash = ((lightNameHash << 5) - lightNameHash) + light.name.charCodeAt(i);
|
|
lightNameHash = lightNameHash & lightNameHash;
|
|
}
|
|
var baseId: Int = Std.int(Math.abs(lightNameHash)) + (index * 100);
|
|
|
|
ui.text(light.name + " (" + light.data.raw.type.toUpperCase() + ")", Align.Left);
|
|
ui.separator();
|
|
|
|
var hStrength = Id.handle().nest(baseId + 0, {value: light.data.raw.strength});
|
|
var newStrength = ui.slider(hStrength, "Strength: " + formatValue(hStrength.value, 2),
|
|
0.0, 100.0, true, 100, true, Left, true);
|
|
|
|
if (hStrength.changed) {
|
|
light.data.raw.strength = newStrength;
|
|
}
|
|
|
|
ui.text("Color:");
|
|
|
|
var hColorR = Id.handle().nest(baseId + 1, {value: light.data.raw.color[0]});
|
|
var newColorR = ui.slider(hColorR, "R: " + formatValue(hColorR.value, 2),
|
|
0.0, 1.0, true, 100, true, Left, true);
|
|
if (hColorR.changed) light.data.raw.color[0] = newColorR;
|
|
|
|
var hColorG = Id.handle().nest(baseId + 2, {value: light.data.raw.color[1]});
|
|
var newColorG = ui.slider(hColorG, "G: " + formatValue(hColorG.value, 2),
|
|
0.0, 1.0, true, 100, true, Left, true);
|
|
if (hColorG.changed) light.data.raw.color[1] = newColorG;
|
|
|
|
var hColorB = Id.handle().nest(baseId + 3, {value: light.data.raw.color[2]});
|
|
var newColorB = ui.slider(hColorB, "B: " + formatValue(hColorB.value, 2),
|
|
0.0, 1.0, true, 100, true, Left, true);
|
|
if (hColorB.changed) light.data.raw.color[2] = newColorB;
|
|
|
|
if (light.data.raw.cast_shadow != null) {
|
|
var hCastShadow = Id.handle().nest(baseId + 4);
|
|
hCastShadow.selected = light.data.raw.cast_shadow;
|
|
|
|
ui.row([1/4, 3/4]);
|
|
light.data.raw.cast_shadow = ui.check(hCastShadow, "");
|
|
ui.text("Cast Shadow");
|
|
}
|
|
|
|
if (light.data.raw.cast_shadow == true) {
|
|
ui.separator();
|
|
ui.text("— Shadow Settings —", Align.Center);
|
|
|
|
if (light.data.raw.shadows_bias != null) {
|
|
var hBias = Id.handle().nest(baseId + 5, {value: light.data.raw.shadows_bias * 10000.0});
|
|
var biasDisplay = ui.slider(hBias, "Bias: " + formatValue(hBias.value, 1),
|
|
0.0, 100.0, true, 100, true, Left, true);
|
|
|
|
if (hBias.changed) {
|
|
light.data.raw.shadows_bias = biasDisplay / 10000.0; // Convert back
|
|
trace("[LightControl] " + light.name + " Bias: " + light.data.raw.shadows_bias);
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.near_plane != null) {
|
|
var hNear = Id.handle().nest(baseId + 6, {value: light.data.raw.near_plane});
|
|
var newNear = ui.slider(hNear, "Near: " + formatValue(hNear.value, 3),
|
|
0.001, 10.0, true, 1000, true, Left, true);
|
|
|
|
if (hNear.changed) {
|
|
light.data.raw.near_plane = newNear;
|
|
trace("[LightControl] " + light.name + " Near Plane: " + light.data.raw.near_plane);
|
|
light.buildMatrix(Scene.active.camera);
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.far_plane != null) {
|
|
var hFar = Id.handle().nest(baseId + 7, {value: light.data.raw.far_plane});
|
|
var newFar = ui.slider(hFar, "Far: " + formatValue(hFar.value, 1),
|
|
1.0, 1000.0, true, 100, true, Left, true);
|
|
|
|
if (hFar.changed) {
|
|
light.data.raw.far_plane = newFar;
|
|
trace("[LightControl] " + light.name + " Far Plane: " + light.data.raw.far_plane);
|
|
light.buildMatrix(Scene.active.camera);
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.shadowmap_size != null) {
|
|
ui.text("ShadowMap: " + light.data.raw.shadowmap_size + "px");
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.type == "spot") {
|
|
ui.separator();
|
|
ui.text("— Spot Settings —", Align.Center);
|
|
|
|
if (light.data.raw.spot_size != null) {
|
|
// spot_size is stored as cos(angle), convert to degrees for UI
|
|
var angleRad = Math.acos(light.data.raw.spot_size);
|
|
var angleDeg = angleRad * (180.0 / Math.PI) * 2.0; // Full cone angle
|
|
var hSpotSize = Id.handle().nest(baseId + 8, {value: angleDeg});
|
|
|
|
angleDeg = ui.slider(hSpotSize, "Cone: " + formatValue(hSpotSize.value, 1) + "°",
|
|
1.0, 179.0, true, 100, true, Left, true);
|
|
|
|
if (hSpotSize.changed) {
|
|
var halfAngleRad = (angleDeg / 2.0) * (Math.PI / 180.0);
|
|
light.data.raw.spot_size = Math.cos(halfAngleRad);
|
|
trace("[LightControl] " + light.name + " Spot Cone: " + angleDeg + "°");
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.spot_blend != null) {
|
|
var hSpotBlend = Id.handle().nest(baseId + 9, {value: light.data.raw.spot_blend * 10.0});
|
|
var blendDisplay = ui.slider(hSpotBlend, "Blend: " + formatValue(hSpotBlend.value, 2),
|
|
0.0, 1.0, true, 100, true, Left, true);
|
|
if (hSpotBlend.changed) {
|
|
light.data.raw.spot_blend = blendDisplay / 10.0;
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.fov != null) {
|
|
var fovDeg = light.data.raw.fov * (180.0 / Math.PI);
|
|
var hFOV = Id.handle().nest(baseId + 10, {value: fovDeg});
|
|
fovDeg = ui.slider(hFOV, "FOV: " + formatValue(hFOV.value, 1) + "°",
|
|
10.0, 170.0, true, 100, true, Left, true);
|
|
|
|
if (hFOV.changed) {
|
|
light.data.raw.fov = fovDeg * (Math.PI / 180.0);
|
|
trace("[LightControl] " + light.name + " FOV: " + fovDeg + "°");
|
|
light.buildMatrix(Scene.active.camera);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.type == "point") {
|
|
ui.separator();
|
|
ui.text("— Point Light Settings —", Align.Center);
|
|
|
|
if (light.data.raw.fov != null) {
|
|
ui.text("FOV: " + formatValue(light.data.raw.fov * (180.0 / Math.PI), 1) + "° (90° for cubemap)");
|
|
}
|
|
}
|
|
|
|
if (light.data.raw.type == "sun") {
|
|
ui.separator();
|
|
ui.text("— Sun Settings —", Align.Center);
|
|
ui.text("Directional light source");
|
|
ui.text("Shadow cascades: " + LightObject.cascadeCount);
|
|
}
|
|
}
|
|
|
|
function formatValue(value: Float, decimals: Int): String {
|
|
var multiplier = Math.pow(10, decimals);
|
|
var rounded = Math.round(value * multiplier) / multiplier;
|
|
return Std.string(rounded);
|
|
}
|
|
}
|