diff --git a/Kha/Backends/Krom/Krom.hx b/Kha/Backends/Krom/Krom.hx index 1b14eeb..a6708de 100644 --- a/Kha/Backends/Krom/Krom.hx +++ b/Kha/Backends/Krom/Krom.hx @@ -80,6 +80,7 @@ extern class Krom { static function unloadImage(image: kha.Image): Void; static function loadSound(file: String): Dynamic; static function writeAudioBuffer(buffer: js.lib.ArrayBuffer, samples: Int): Void; + static function getSamplesPerSecond(): Int; static function loadBlob(file: String): js.lib.ArrayBuffer; static function init(title: String, width: Int, height: Int, samplesPerPixel: Int, vSync: Bool, windowMode: Int, windowFeatures: Int, kromApi: Int): Void; @@ -115,6 +116,7 @@ extern class Krom { static function screenDpi(): Int; static function systemId(): String; static function requestShutdown(): Void; + static function displayFrequency(): Int; static function displayCount(): Int; static function displayWidth(index: Int): Int; static function displayHeight(index: Int): Int; diff --git a/Kha/Backends/Krom/kha/Display.hx b/Kha/Backends/Krom/kha/Display.hx index 97d798c..954bf1c 100644 --- a/Kha/Backends/Krom/kha/Display.hx +++ b/Kha/Backends/Krom/kha/Display.hx @@ -79,7 +79,7 @@ class Display { public var frequency(get, never): Int; function get_frequency(): Int { - return 60; + return Krom.displayFrequency(); } public var pixelsPerInch(get, never): Int; diff --git a/Kha/Backends/Krom/kha/SystemImpl.hx b/Kha/Backends/Krom/kha/SystemImpl.hx index 7fc3dc4..b5fc940 100644 --- a/Kha/Backends/Krom/kha/SystemImpl.hx +++ b/Kha/Backends/Krom/kha/SystemImpl.hx @@ -1,343 +1,344 @@ -package kha; - -import kha.graphics4.TextureFormat; -import kha.input.Gamepad; -import kha.input.Keyboard; -import kha.input.Mouse; -import kha.input.MouseImpl; -import kha.input.Pen; -import kha.input.Surface; -import kha.System; -import haxe.ds.Vector; - -class SystemImpl { - static var start: Float; - static var framebuffer: Framebuffer; - static var keyboard: Keyboard; - static var mouse: Mouse; - static var pen: Pen; - static var maxGamepads: Int = 4; - static var gamepads: Array; - static var mouseLockListeners: ArrayVoid> = []; - - static function renderCallback(): Void { - Scheduler.executeFrame(); - System.render([framebuffer]); - } - - static function dropFilesCallback(filePath: String): Void { - System.dropFiles(filePath); - } - - static function copyCallback(): String { - if (System.copyListener != null) { - return System.copyListener(); - } - else { - return null; - } - } - - static function cutCallback(): String { - if (System.cutListener != null) { - return System.cutListener(); - } - else { - return null; - } - } - - static function pasteCallback(data: String): Void { - if (System.pasteListener != null) { - System.pasteListener(data); - } - } - - static function foregroundCallback(): Void { - System.foreground(); - } - - static function resumeCallback(): Void { - System.resume(); - } - - static function pauseCallback(): Void { - System.pause(); - } - - static function backgroundCallback(): Void { - System.background(); - } - - static function shutdownCallback(): Void { - System.shutdown(); - } - - static function keyboardDownCallback(code: Int): Void { - keyboard.sendDownEvent(cast code); - } - - static function keyboardUpCallback(code: Int): Void { - keyboard.sendUpEvent(cast code); - } - - static function keyboardPressCallback(charCode: Int): Void { - keyboard.sendPressEvent(String.fromCharCode(charCode)); - } - - static function mouseDownCallback(button: Int, x: Int, y: Int): Void { - mouse.sendDownEvent(0, button, x, y); - } - - static function mouseUpCallback(button: Int, x: Int, y: Int): Void { - mouse.sendUpEvent(0, button, x, y); - } - - static function mouseMoveCallback(x: Int, y: Int, mx: Int, my: Int): Void { - mouse.sendMoveEvent(0, x, y, mx, my); - } - - static function mouseWheelCallback(delta: Int): Void { - mouse.sendWheelEvent(0, delta); - } - - static function penDownCallback(x: Int, y: Int, pressure: Float): Void { - pen.sendDownEvent(0, x, y, pressure); - } - - static function penUpCallback(x: Int, y: Int, pressure: Float): Void { - pen.sendUpEvent(0, x, y, pressure); - } - - static function penMoveCallback(x: Int, y: Int, pressure: Float): Void { - pen.sendMoveEvent(0, x, y, pressure); - } - - static function gamepadAxisCallback(gamepad: Int, axis: Int, value: Float): Void { - gamepads[gamepad].sendAxisEvent(axis, value); - } - - static function gamepadButtonCallback(gamepad: Int, button: Int, value: Float): Void { - gamepads[gamepad].sendButtonEvent(button, value); - } - - static function audioCallback(samples: Int): Void { - kha.audio2.Audio._callCallback(samples); - var buffer = @:privateAccess kha.audio2.Audio.buffer; - Krom.writeAudioBuffer(buffer.data.buffer, samples); - } - - public static function init(options: SystemOptions, callback: Window->Void): Void { - Krom.init(options.title, options.width, options.height, options.framebuffer.samplesPerPixel, options.framebuffer.verticalSync, - cast options.window.mode, options.window.windowFeatures, Krom.KROM_API); - - start = Krom.getTime(); - - haxe.Log.trace = function(v: Dynamic, ?infos: haxe.PosInfos) { - var message = haxe.Log.formatOutput(v, infos); - Krom.log(message); - }; - - new Window(0); - Scheduler.init(); - Shaders.init(); - - var g4 = new kha.krom.Graphics(); - framebuffer = new Framebuffer(0, null, null, g4); - framebuffer.init(new kha.graphics2.Graphics1(framebuffer), new kha.graphics4.Graphics2(framebuffer), g4); - Krom.setCallback(renderCallback); - Krom.setDropFilesCallback(dropFilesCallback); - Krom.setCutCopyPasteCallback(cutCallback, copyCallback, pasteCallback); - Krom.setApplicationStateCallback(foregroundCallback, resumeCallback, pauseCallback, backgroundCallback, shutdownCallback); - - keyboard = new Keyboard(); - mouse = new MouseImpl(); - pen = new Pen(); - gamepads = new Array(); - for (i in 0...maxGamepads) { - gamepads[i] = new Gamepad(i); - } - - Krom.setKeyboardDownCallback(keyboardDownCallback); - Krom.setKeyboardUpCallback(keyboardUpCallback); - Krom.setKeyboardPressCallback(keyboardPressCallback); - Krom.setMouseDownCallback(mouseDownCallback); - Krom.setMouseUpCallback(mouseUpCallback); - Krom.setMouseMoveCallback(mouseMoveCallback); - Krom.setMouseWheelCallback(mouseWheelCallback); - Krom.setPenDownCallback(penDownCallback); - Krom.setPenUpCallback(penUpCallback); - Krom.setPenMoveCallback(penMoveCallback); - Krom.setGamepadAxisCallback(gamepadAxisCallback); - Krom.setGamepadButtonCallback(gamepadButtonCallback); - - kha.audio2.Audio._init(); - kha.audio1.Audio._init(); - Krom.setAudioCallback(audioCallback); - - Scheduler.start(); - - callback(Window.get(0)); - } - - public static function initEx(title: String, options: Array, windowCallback: Int->Void, callback: Void->Void): Void {} - - static function translateWindowMode(value: Null): Int { - if (value == null) { - return 0; - } - - return switch (value) { - case Windowed: 0; - case Fullscreen: 1; - case ExclusiveFullscreen: 2; - } - } - - public static function getScreenRotation(): ScreenRotation { - return ScreenRotation.RotationNone; - } - - public static function getTime(): Float { - return Krom.getTime() - start; - } - - public static function getVsync(): Bool { - return true; - } - - public static function getRefreshRate(): Int { - return 60; - } - - public static function getSystemId(): String { - return Krom.systemId(); - } - - public static function vibrate(ms: Int): Void { - // TODO: Implement - } - - public static function getLanguage(): String { - return "en"; // TODO: Implement - } - - public static function requestShutdown(): Bool { - Krom.requestShutdown(); - return true; - } - - public static function getMouse(num: Int): Mouse { - return mouse; - } - - public static function getPen(num: Int): Pen { - return pen; - } - - public static function getKeyboard(num: Int): Keyboard { - return keyboard; - } - - public static function lockMouse(): Void { - if (!isMouseLocked()) { - Krom.lockMouse(); - for (listener in mouseLockListeners) { - listener(); - } - } - } - - public static function unlockMouse(): Void { - if (isMouseLocked()) { - Krom.unlockMouse(); - for (listener in mouseLockListeners) { - listener(); - } - } - } - - public static function canLockMouse(): Bool { - return Krom.canLockMouse(); - } - - public static function isMouseLocked(): Bool { - return Krom.isMouseLocked(); - } - - public static function notifyOfMouseLockChange(func: Void->Void, error: Void->Void): Void { - if (canLockMouse() && func != null) { - mouseLockListeners.push(func); - } - } - - public static function removeFromMouseLockChange(func: Void->Void, error: Void->Void): Void { - if (canLockMouse() && func != null) { - mouseLockListeners.remove(func); - } - } - - public static function hideSystemCursor(): Void { - Krom.showMouse(false); - } - - public static function showSystemCursor(): Void { - Krom.showMouse(true); - } - - static function unload(): Void {} - - public static function canSwitchFullscreen(): Bool { - return false; - } - - public static function isFullscreen(): Bool { - return false; - } - - public static function requestFullscreen(): Void {} - - public static function exitFullscreen(): Void {} - - public static function notifyOfFullscreenChange(func: Void->Void, error: Void->Void): Void {} - - public static function removeFromFullscreenChange(func: Void->Void, error: Void->Void): Void {} - - public static function changeResolution(width: Int, height: Int): Void {} - - public static function setKeepScreenOn(on: Bool): Void {} - - public static function loadUrl(url: String): Void {} - - public static function getGamepadId(index: Int): String { - return "unknown"; - } - - public static function getGamepadVendor(index: Int): String { - return "unknown"; - } - - public static function setGamepadRumble(index: Int, leftAmount: Float, rightAmount: Float): Void {} - - public static function safeZone(): Float { - return 1.0; - } - - public static function login(): Void {} - - public static function automaticSafeZone(): Bool { - return true; - } - - public static function setSafeZone(value: Float): Void {} - - public static function unlockAchievement(id: Int): Void {} - - public static function waitingForLogin(): Bool { - return false; - } - - public static function disallowUserChange(): Void {} - - public static function allowUserChange(): Void {} -} +package kha; + +import kha.graphics4.TextureFormat; +import kha.input.Gamepad; +import kha.input.Keyboard; +import kha.input.Mouse; +import kha.input.MouseImpl; +import kha.input.Pen; +import kha.input.Surface; +import kha.System; +import haxe.ds.Vector; + +class SystemImpl { + static var start: Float; + static var framebuffer: Framebuffer; + static var keyboard: Keyboard; + static var mouse: Mouse; + static var pen: Pen; + static var maxGamepads: Int = 4; + static var gamepads: Array; + static var mouseLockListeners: ArrayVoid> = []; + + static function renderCallback(): Void { + Scheduler.executeFrame(); + System.render([framebuffer]); + } + + static function dropFilesCallback(filePath: String): Void { + System.dropFiles(filePath); + } + + static function copyCallback(): String { + if (System.copyListener != null) { + return System.copyListener(); + } + else { + return null; + } + } + + static function cutCallback(): String { + if (System.cutListener != null) { + return System.cutListener(); + } + else { + return null; + } + } + + static function pasteCallback(data: String): Void { + if (System.pasteListener != null) { + System.pasteListener(data); + } + } + + static function foregroundCallback(): Void { + System.foreground(); + } + + static function resumeCallback(): Void { + System.resume(); + } + + static function pauseCallback(): Void { + System.pause(); + } + + static function backgroundCallback(): Void { + System.background(); + } + + static function shutdownCallback(): Void { + System.shutdown(); + } + + static function keyboardDownCallback(code: Int): Void { + keyboard.sendDownEvent(cast code); + } + + static function keyboardUpCallback(code: Int): Void { + keyboard.sendUpEvent(cast code); + } + + static function keyboardPressCallback(charCode: Int): Void { + keyboard.sendPressEvent(String.fromCharCode(charCode)); + } + + static function mouseDownCallback(button: Int, x: Int, y: Int): Void { + mouse.sendDownEvent(0, button, x, y); + } + + static function mouseUpCallback(button: Int, x: Int, y: Int): Void { + mouse.sendUpEvent(0, button, x, y); + } + + static function mouseMoveCallback(x: Int, y: Int, mx: Int, my: Int): Void { + mouse.sendMoveEvent(0, x, y, mx, my); + } + + static function mouseWheelCallback(delta: Int): Void { + mouse.sendWheelEvent(0, delta); + } + + static function penDownCallback(x: Int, y: Int, pressure: Float): Void { + pen.sendDownEvent(0, x, y, pressure); + } + + static function penUpCallback(x: Int, y: Int, pressure: Float): Void { + pen.sendUpEvent(0, x, y, pressure); + } + + static function penMoveCallback(x: Int, y: Int, pressure: Float): Void { + pen.sendMoveEvent(0, x, y, pressure); + } + + static function gamepadAxisCallback(gamepad: Int, axis: Int, value: Float): Void { + gamepads[gamepad].sendAxisEvent(axis, value); + } + + static function gamepadButtonCallback(gamepad: Int, button: Int, value: Float): Void { + gamepads[gamepad].sendButtonEvent(button, value); + } + + static function audioCallback(samples: Int): Void { + kha.audio2.Audio._callCallback(samples); + var buffer = @:privateAccess kha.audio2.Audio.buffer; + Krom.writeAudioBuffer(buffer.data.buffer, samples); + } + + public static function init(options: SystemOptions, callback: Window->Void): Void { + Krom.init(options.title, options.width, options.height, options.framebuffer.samplesPerPixel, options.framebuffer.verticalSync, + cast options.window.mode, options.window.windowFeatures, Krom.KROM_API); + + start = Krom.getTime(); + + haxe.Log.trace = function(v: Dynamic, ?infos: haxe.PosInfos) { + var message = haxe.Log.formatOutput(v, infos); + Krom.log(message); + }; + + new Window(0); + Scheduler.init(); + Shaders.init(); + + var g4 = new kha.krom.Graphics(); + framebuffer = new Framebuffer(0, null, null, g4); + framebuffer.init(new kha.graphics2.Graphics1(framebuffer), new kha.graphics4.Graphics2(framebuffer), g4); + Krom.setCallback(renderCallback); + Krom.setDropFilesCallback(dropFilesCallback); + Krom.setCutCopyPasteCallback(cutCallback, copyCallback, pasteCallback); + Krom.setApplicationStateCallback(foregroundCallback, resumeCallback, pauseCallback, backgroundCallback, shutdownCallback); + + keyboard = new Keyboard(); + mouse = new MouseImpl(); + pen = new Pen(); + gamepads = new Array(); + for (i in 0...maxGamepads) { + gamepads[i] = new Gamepad(i); + } + + Krom.setKeyboardDownCallback(keyboardDownCallback); + Krom.setKeyboardUpCallback(keyboardUpCallback); + Krom.setKeyboardPressCallback(keyboardPressCallback); + Krom.setMouseDownCallback(mouseDownCallback); + Krom.setMouseUpCallback(mouseUpCallback); + Krom.setMouseMoveCallback(mouseMoveCallback); + Krom.setMouseWheelCallback(mouseWheelCallback); + Krom.setPenDownCallback(penDownCallback); + Krom.setPenUpCallback(penUpCallback); + Krom.setPenMoveCallback(penMoveCallback); + Krom.setGamepadAxisCallback(gamepadAxisCallback); + Krom.setGamepadButtonCallback(gamepadButtonCallback); + + kha.audio2.Audio.samplesPerSecond = Krom.getSamplesPerSecond(); + kha.audio1.Audio._init(); + kha.audio2.Audio._init(); + Krom.setAudioCallback(audioCallback); + + Scheduler.start(); + + callback(Window.get(0)); + } + + public static function initEx(title: String, options: Array, windowCallback: Int->Void, callback: Void->Void): Void {} + + static function translateWindowMode(value: Null): Int { + if (value == null) { + return 0; + } + + return switch (value) { + case Windowed: 0; + case Fullscreen: 1; + case ExclusiveFullscreen: 2; + } + } + + public static function getScreenRotation(): ScreenRotation { + return ScreenRotation.RotationNone; + } + + public static function getTime(): Float { + return Krom.getTime() - start; + } + + public static function getVsync(): Bool { + return true; + } + + public static function getRefreshRate(): Int { + return Krom.displayFrequency(); + } + + public static function getSystemId(): String { + return Krom.systemId(); + } + + public static function vibrate(ms: Int): Void { + // TODO: Implement + } + + public static function getLanguage(): String { + return "en"; // TODO: Implement + } + + public static function requestShutdown(): Bool { + Krom.requestShutdown(); + return true; + } + + public static function getMouse(num: Int): Mouse { + return mouse; + } + + public static function getPen(num: Int): Pen { + return pen; + } + + public static function getKeyboard(num: Int): Keyboard { + return keyboard; + } + + public static function lockMouse(): Void { + if (!isMouseLocked()) { + Krom.lockMouse(); + for (listener in mouseLockListeners) { + listener(); + } + } + } + + public static function unlockMouse(): Void { + if (isMouseLocked()) { + Krom.unlockMouse(); + for (listener in mouseLockListeners) { + listener(); + } + } + } + + public static function canLockMouse(): Bool { + return Krom.canLockMouse(); + } + + public static function isMouseLocked(): Bool { + return Krom.isMouseLocked(); + } + + public static function notifyOfMouseLockChange(func: Void->Void, error: Void->Void): Void { + if (canLockMouse() && func != null) { + mouseLockListeners.push(func); + } + } + + public static function removeFromMouseLockChange(func: Void->Void, error: Void->Void): Void { + if (canLockMouse() && func != null) { + mouseLockListeners.remove(func); + } + } + + public static function hideSystemCursor(): Void { + Krom.showMouse(false); + } + + public static function showSystemCursor(): Void { + Krom.showMouse(true); + } + + static function unload(): Void {} + + public static function canSwitchFullscreen(): Bool { + return false; + } + + public static function isFullscreen(): Bool { + return false; + } + + public static function requestFullscreen(): Void {} + + public static function exitFullscreen(): Void {} + + public static function notifyOfFullscreenChange(func: Void->Void, error: Void->Void): Void {} + + public static function removeFromFullscreenChange(func: Void->Void, error: Void->Void): Void {} + + public static function changeResolution(width: Int, height: Int): Void {} + + public static function setKeepScreenOn(on: Bool): Void {} + + public static function loadUrl(url: String): Void {} + + public static function getGamepadId(index: Int): String { + return "unknown"; + } + + public static function getGamepadVendor(index: Int): String { + return "unknown"; + } + + public static function setGamepadRumble(index: Int, leftAmount: Float, rightAmount: Float): Void {} + + public static function safeZone(): Float { + return 1.0; + } + + public static function login(): Void {} + + public static function automaticSafeZone(): Bool { + return true; + } + + public static function setSafeZone(value: Float): Void {} + + public static function unlockAchievement(id: Int): Void {} + + public static function waitingForLogin(): Bool { + return false; + } + + public static function disallowUserChange(): Void {} + + public static function allowUserChange(): Void {} +} diff --git a/Kha/Backends/Krom/kha/audio2/Audio.hx b/Kha/Backends/Krom/kha/audio2/Audio.hx index a2842e5..b1b87b5 100644 --- a/Kha/Backends/Krom/kha/audio2/Audio.hx +++ b/Kha/Backends/Krom/kha/audio2/Audio.hx @@ -1,57 +1,56 @@ -package kha.audio2; - -import kha.Sound; -import kha.internal.IntBox; - -class Audio { - public static var disableGcInteractions = false; - static var intBox: IntBox = new IntBox(0); - static var buffer: Buffer; - - public static function _init() { - var bufferSize = 1024 * 2; - buffer = new Buffer(bufferSize * 4, 2, 44100); - Audio.samplesPerSecond = 44100; - } - - public static function _callCallback(samples: Int): Void { - if (buffer == null) - return; - if (audioCallback != null) { - intBox.value = samples; - audioCallback(intBox, buffer); - } - else { - for (i in 0...samples) { - buffer.data.set(buffer.writeLocation, 0); - buffer.writeLocation += 1; - if (buffer.writeLocation >= buffer.size) { - buffer.writeLocation = 0; - } - } - } - } - - public static function _readSample(): Float { - if (buffer == null) - return 0; - var value = buffer.data.get(buffer.readLocation); - buffer.readLocation += 1; - if (buffer.readLocation >= buffer.size) { - buffer.readLocation = 0; - } - return value; - } - - public static var samplesPerSecond: Int; - - public static var audioCallback: IntBox->Buffer->Void; - - public static function play(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel { - return null; - } - - public static function stream(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel { - return null; - } -} +package kha.audio2; + +import kha.Sound; +import kha.internal.IntBox; + +class Audio { + public static var disableGcInteractions = false; + static var intBox: IntBox = new IntBox(0); + static var buffer: Buffer; + + public static function _init() { + var bufferSize = 1024 * 2; + buffer = new Buffer(bufferSize * 4, 2, samplesPerSecond); + } + + public static function _callCallback(samples: Int): Void { + if (buffer == null) + return; + if (audioCallback != null) { + intBox.value = samples; + audioCallback(intBox, buffer); + } + else { + for (i in 0...samples) { + buffer.data.set(buffer.writeLocation, 0); + buffer.writeLocation += 1; + if (buffer.writeLocation >= buffer.size) { + buffer.writeLocation = 0; + } + } + } + } + + public static function _readSample(): FastFloat { + if (buffer == null) + return 0; + var value = buffer.data.get(buffer.readLocation); + ++buffer.readLocation; + if (buffer.readLocation >= buffer.size) { + buffer.readLocation = 0; + } + return value; + } + + public static var samplesPerSecond: Int; + + public static var audioCallback: IntBox->Buffer->Void; + + public static function play(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel { + return null; + } + + public static function stream(sound: Sound, loop: Bool = false): kha.audio1.AudioChannel { + return null; + } +} diff --git a/Kha/Backends/Krom/kha/krom/Graphics.hx b/Kha/Backends/Krom/kha/krom/Graphics.hx index bd0a6d0..255b3e4 100644 --- a/Kha/Backends/Krom/kha/krom/Graphics.hx +++ b/Kha/Backends/Krom/kha/krom/Graphics.hx @@ -59,7 +59,7 @@ class Graphics implements kha.graphics4.Graphics { } public function refreshRate(): Int { - return 60; + return Krom.displayFrequency(); } public function clear(?color: Color, ?depth: Float, ?stencil: Int): Void { diff --git a/Kha/Sources/kha/Scheduler.hx b/Kha/Sources/kha/Scheduler.hx index a5e0470..34a493f 100644 --- a/Kha/Sources/kha/Scheduler.hx +++ b/Kha/Sources/kha/Scheduler.hx @@ -1,554 +1,554 @@ -package kha; - -class TimeTask { - public var task: Void->Bool; - - public var start: Float; - public var period: Float; - public var duration: Float; - public var next: Float; - - public var id: Int; - public var groupId: Int; - public var active: Bool; - public var paused: Bool; - - public function new() {} -} - -class FrameTask { - public var task: Void->Bool; - public var priority: Int; - public var id: Int; - public var active: Bool; - public var paused: Bool; - - public function new(task: Void->Bool, priority: Int, id: Int) { - this.task = task; - this.priority = priority; - this.id = id; - active = true; - paused = false; - } -} - -class Scheduler { - static var timeTasks: Array; - static var pausedTimeTasks: Array; - static var outdatedTimeTasks: Array; - static var timeTasksScratchpad: Array; - static inline var timeWarpSaveTime: Float = 10.0; - - static var frameTasks: Array; - static var toDeleteFrame: Array; - - static var current: Float; - static var lastTime: Float; - static var lastFrameEnd: Float; - - static var frame_tasks_sorted: Bool; - static var stopped: Bool; - static var vsync: Bool; - - // Html5 target can update display frequency after some delay - #if kha_html5 - static var onedifhz(get, never): Float; - - static inline function get_onedifhz(): Float { - return 1.0 / Display.primary.frequency; - } - #else - static var onedifhz: Float; - #end - - static var currentFrameTaskId: Int; - static var currentTimeTaskId: Int; - static var currentGroupId: Int; - - static var DIF_COUNT = 3; - static var maxframetime = 0.5; - - static var deltas: Array; - - static var startTime: Float = 0; - - static var activeTimeTask: TimeTask = null; - - public static function init(): Void { - deltas = new Array(); - for (i in 0...DIF_COUNT) - deltas[i] = 0; - - stopped = true; - frame_tasks_sorted = true; - current = lastTime = lastFrameEnd = realTime(); - - currentFrameTaskId = 0; - currentTimeTaskId = 0; - currentGroupId = 0; - - timeTasks = []; - pausedTimeTasks = []; - outdatedTimeTasks = []; - timeTasksScratchpad = []; - frameTasks = []; - toDeleteFrame = []; - } - - public static function start(restartTimers: Bool = false): Void { - vsync = Window.get(0).vSynced; - #if !kha_html5 - var hz = Display.primary != null ? Display.primary.frequency : 60; - if (hz >= 57 && hz <= 63) - hz = 60; - onedifhz = 1.0 / hz; - #end - - stopped = false; - resetTime(); - lastTime = realTime() - startTime; - for (i in 0...DIF_COUNT) - deltas[i] = 0; - - if (restartTimers) { - for (timeTask in timeTasks) { - timeTask.paused = false; - } - - for (frameTask in frameTasks) { - frameTask.paused = false; - } - } - } - - public static function stop(): Void { - stopped = true; - } - - public static function isStopped(): Bool { - return stopped; - } - - static function warpTimeTasksBack(time: Float, tasks: Array): Void { - for (timeTask in tasks) { - if (timeTask.start >= time) { - timeTask.next = timeTask.start; - } - else if (timeTask.period > 0) { - var sinceStart = time - timeTask.start; - var times = Math.ceil(sinceStart / timeTask.period); - timeTask.next = timeTask.start + times * timeTask.period; - } - } - } - - public static function warp(time: Float): Void { - if (time < lastTime) { - current = time; - lastTime = time; - lastFrameEnd = time; - - warpTimeTasksBack(time, outdatedTimeTasks); - warpTimeTasksBack(time, timeTasks); - - for (task in outdatedTimeTasks) { - if (task.next >= time) { - timeTasksScratchpad.push(task); - } - } - for (task in timeTasksScratchpad) { - outdatedTimeTasks.remove(task); - } - for (task in timeTasksScratchpad) { - insertSorted(timeTasks, task); - } - while (timeTasksScratchpad.length > 0) { - timeTasksScratchpad.remove(timeTasksScratchpad[0]); - } - } - else if (time > lastTime) { - // TODO: Changing startTime line prevents clients from falling into a - // warp-forward-then-wait-for-systemtime-to-catch-up-loop that causes - // choppy movement (e.g. every 3rd frame forward 3 times). - // But it causes backwards jumps in originally constant movements. - // And on HTML5 packets are received while no frames are executed, - // which causes the client to overtakes the server and then move - // farther away with each packet while being unable to synch back - // (backwards warping is not allowed to change startTime). - startTime -= (time - lastTime); - - current = time; - lastTime = time; - lastFrameEnd = time; - - executeTimeTasks(time); - } - } - - public static function executeFrame(): Void { - var real = realTime(); - var now: Float = real - startTime; - var delta = now - lastTime; - - var frameEnd: Float = lastFrameEnd; - - if (delta >= 0) { - if (kha.netsync.Session.the() == null) { - // tdif = 1.0 / 60.0; //force fixed frame rate - - if (delta > maxframetime) { - startTime += delta - maxframetime; - now = real - startTime; - delta = maxframetime; - frameEnd += delta; - } - else { - if (vsync) { - // var measured = delta; - // this is optimized not to run at exact speed - // but to run as fluid as possible - var frames = Math.round(delta / onedifhz); - if (frames < 1) { - return; - } - - var realdif = frames * onedifhz; - - delta = realdif; - for (i in 0...DIF_COUNT - 2) { - delta += deltas[i]; - deltas[i] = deltas[i + 1]; - } - delta += deltas[DIF_COUNT - 2]; - delta /= DIF_COUNT; - deltas[DIF_COUNT - 2] = realdif; - - frameEnd += delta; - - // trace("Measured: " + measured + " Frames: " + frames + " Delta: " + delta + " "); - } - else { - for (i in 0...DIF_COUNT - 1) { - deltas[i] = deltas[i + 1]; - } - deltas[DIF_COUNT - 1] = delta; - - var next: Float = 0; - for (i in 0...DIF_COUNT) { - next += deltas[i]; - } - next /= DIF_COUNT; - - // delta = interpolated_delta; // average the frame end estimation - - // lastTime = now; - frameEnd += next; - } - } - } - else { - frameEnd += delta; - } - - lastTime = now; - - if (!stopped) { // Stop simulation time - lastFrameEnd = frameEnd; - } - - // Extend endpoint by paused time (individually paused tasks) - for (pausedTask in pausedTimeTasks) { - pausedTask.next += delta; - } - - if (stopped) { - // Extend endpoint by paused time (running tasks) - for (timeTask in timeTasks) { - timeTask.next += delta; - } - } - - executeTimeTasks(frameEnd); - - // Maintain outdated task list - for (task in outdatedTimeTasks) { - if (task.next < frameEnd - timeWarpSaveTime) { - timeTasksScratchpad.push(task); - } - } - for (task in timeTasksScratchpad) { - outdatedTimeTasks.remove(task); - } - while (timeTasksScratchpad.length > 0) { - timeTasksScratchpad.remove(timeTasksScratchpad[0]); - } - } - - current = frameEnd; - - sortFrameTasks(); - for (frameTask in frameTasks) { - if (!stopped && !frameTask.paused && frameTask.active) { - if (!frameTask.task()) - frameTask.active = false; - } - } - - for (frameTask in frameTasks) { - if (!frameTask.active) { - toDeleteFrame.push(frameTask); - } - } - - while (toDeleteFrame.length > 0) { - frameTasks.remove(toDeleteFrame.pop()); - } - } - - static function executeTimeTasks(until: Float) { - while (timeTasks.length > 0) { - activeTimeTask = timeTasks[0]; - - if (activeTimeTask.next <= until) { - current = activeTimeTask.next; - - activeTimeTask.next += activeTimeTask.period; - timeTasks.remove(activeTimeTask); - - if (activeTimeTask.active && activeTimeTask.task()) { - if (activeTimeTask.period > 0 - && (activeTimeTask.duration == 0 || activeTimeTask.duration >= activeTimeTask.start + activeTimeTask.next)) { - insertSorted(timeTasks, activeTimeTask); - } - else { - archiveTimeTask(activeTimeTask, until); - } - } - else { - activeTimeTask.active = false; - archiveTimeTask(activeTimeTask, until); - } - } - else { - break; - } - } - activeTimeTask = null; - } - - static function archiveTimeTask(timeTask: TimeTask, frameEnd: Float) { - #if sys_server - if (timeTask.next > frameEnd - timeWarpSaveTime) { - outdatedTimeTasks.push(timeTask); - } - #end - } - - /** - * An approximation of the amount of time (in fractional seconds) that elapsed while the game was active. - * This value is optimized for achieving smooth framerates. - */ - public static function time(): Float { - return current; - } - - /** - * The amount of time (in fractional seconds) that elapsed since the game started. - */ - public static function realTime(): Float { - return System.time; - } - - public static function resetTime(): Void { - var now = System.time; - var dif = now - startTime; - startTime = now; - for (timeTask in timeTasks) { - timeTask.start -= dif; - timeTask.next -= dif; - } - for (i in 0...DIF_COUNT) - deltas[i] = 0; - current = 0; - lastTime = 0; - lastFrameEnd = 0; - } - - public static function addBreakableFrameTask(task: Void->Bool, priority: Int): Int { - frameTasks.push(new FrameTask(task, priority, ++currentFrameTaskId)); - frame_tasks_sorted = false; - return currentFrameTaskId; - } - - public static function addFrameTask(task: Void->Void, priority: Int): Int { - return addBreakableFrameTask(function() { - task(); - return true; - }, priority); - } - - public static function pauseFrameTask(id: Int, paused: Bool): Void { - for (frameTask in frameTasks) { - if (frameTask.id == id) { - frameTask.paused = paused; - break; - } - } - } - - public static function removeFrameTask(id: Int): Void { - for (frameTask in frameTasks) { - if (frameTask.id == id) { - frameTask.active = false; - break; - } - } - } - - public static function generateGroupId(): Int { - return ++currentGroupId; - } - - public static function addBreakableTimeTaskToGroup(groupId: Int, task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int { - var t = new TimeTask(); - t.active = true; - t.task = task; - t.id = ++currentTimeTaskId; - t.groupId = groupId; - - t.start = current + start; - t.period = 0; - if (period != 0) - t.period = period; - t.duration = 0; // infinite - if (duration != 0) - t.duration = t.start + duration; - - t.next = t.start; - insertSorted(timeTasks, t); - return t.id; - } - - public static function addTimeTaskToGroup(groupId: Int, task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int { - return addBreakableTimeTaskToGroup(groupId, function() { - task(); - return true; - }, start, period, duration); - } - - public static function addBreakableTimeTask(task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int { - return addBreakableTimeTaskToGroup(0, task, start, period, duration); - } - - public static function addTimeTask(task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int { - return addTimeTaskToGroup(0, task, start, period, duration); - } - - static function getTimeTask(id: Int): TimeTask { - if (activeTimeTask != null && activeTimeTask.id == id) - return activeTimeTask; - for (timeTask in timeTasks) { - if (timeTask.id == id) { - return timeTask; - } - } - for (timeTask in pausedTimeTasks) { - if (timeTask.id == id) { - return timeTask; - } - } - return null; - } - - public static function pauseTimeTask(id: Int, paused: Bool): Void { - var timeTask = getTimeTask(id); - if (timeTask != null) { - pauseRunningTimeTask(timeTask, paused); - } - if (activeTimeTask != null && activeTimeTask.id == id) { - activeTimeTask.paused = paused; - } - } - - static function pauseRunningTimeTask(timeTask: TimeTask, paused: Bool): Void { - timeTask.paused = paused; - if (paused) { - timeTasks.remove(timeTask); - pausedTimeTasks.push(timeTask); - } - else { - insertSorted(timeTasks, timeTask); - pausedTimeTasks.remove(timeTask); - } - } - - public static function pauseTimeTasks(groupId: Int, paused: Bool): Void { - if (paused) { - for (timeTask in timeTasks) { - if (timeTask.groupId == groupId) { - pauseRunningTimeTask(timeTask, paused); - } - } - } - else { - for (timeTask in pausedTimeTasks) { - if (timeTask.groupId == groupId) { - pauseRunningTimeTask(timeTask, paused); - } - } - } - if (activeTimeTask != null && activeTimeTask.groupId == groupId) { - activeTimeTask.paused = paused; - } - } - - public static function removeTimeTask(id: Int): Void { - var timeTask = getTimeTask(id); - if (timeTask != null) { - timeTask.active = false; - timeTasks.remove(timeTask); - } - } - - public static function removeTimeTasks(groupId: Int): Void { - for (timeTask in timeTasks) { - if (timeTask.groupId == groupId) { - timeTask.active = false; - timeTasksScratchpad.push(timeTask); - } - } - for (timeTask in timeTasksScratchpad) { - timeTasks.remove(timeTask); - } - while (timeTasksScratchpad.length > 0) { - timeTasksScratchpad.remove(timeTasksScratchpad[0]); - } - - if (activeTimeTask != null && activeTimeTask.groupId == groupId) { - activeTimeTask.active = false; - } - } - - public static function numTasksInSchedule(): Int { - return timeTasks.length + frameTasks.length; - } - - static function insertSorted(list: Array, task: TimeTask) { - for (i in 0...list.length) { - if (list[i].next > task.next) { - list.insert(i, task); - return; - } - } - list.push(task); - } - - static function sortFrameTasks(): Void { - if (frame_tasks_sorted) - return; - frameTasks.sort(function(a: FrameTask, b: FrameTask): Int { - return a.priority > b.priority ? 1 : ((a.priority < b.priority) ? -1 : 0); - }); - frame_tasks_sorted = true; - } -} +package kha; + +class TimeTask { + public var task: Void->Bool; + + public var start: Float; + public var period: Float; + public var duration: Float; + public var next: Float; + + public var id: Int; + public var groupId: Int; + public var active: Bool; + public var paused: Bool; + + public function new() {} +} + +class FrameTask { + public var task: Void->Bool; + public var priority: Int; + public var id: Int; + public var active: Bool; + public var paused: Bool; + + public function new(task: Void->Bool, priority: Int, id: Int) { + this.task = task; + this.priority = priority; + this.id = id; + active = true; + paused = false; + } +} + +class Scheduler { + static var timeTasks: Array; + static var pausedTimeTasks: Array; + static var outdatedTimeTasks: Array; + static var timeTasksScratchpad: Array; + static inline var timeWarpSaveTime: Float = 10.0; + + static var frameTasks: Array; + static var toDeleteFrame: Array; + + static var current: Float; + static var lastTime: Float; + static var lastFrameEnd: Float; + + static var frame_tasks_sorted: Bool; + static var stopped: Bool; + static var vsync: Bool; + + // Html5 target can update display frequency after some delay + #if (kha_html5 || kha_debug_html5) + static var onedifhz(get, never): Float; + + static inline function get_onedifhz(): Float { + return 1.0 / Display.primary.frequency; + } + #else + static var onedifhz: Float; + #end + + static var currentFrameTaskId: Int; + static var currentTimeTaskId: Int; + static var currentGroupId: Int; + + static var DIF_COUNT = 3; + static var maxframetime = 0.5; + + static var deltas: Array; + + static var startTime: Float = 0; + + static var activeTimeTask: TimeTask = null; + + public static function init(): Void { + deltas = new Array(); + for (i in 0...DIF_COUNT) + deltas[i] = 0; + + stopped = true; + frame_tasks_sorted = true; + current = lastTime = lastFrameEnd = realTime(); + + currentFrameTaskId = 0; + currentTimeTaskId = 0; + currentGroupId = 0; + + timeTasks = []; + pausedTimeTasks = []; + outdatedTimeTasks = []; + timeTasksScratchpad = []; + frameTasks = []; + toDeleteFrame = []; + } + + public static function start(restartTimers: Bool = false): Void { + vsync = Window.get(0).vSynced; + #if !(kha_html5 || kha_debug_html5) + var hz = Display.primary != null ? Display.primary.frequency : 60; + if (hz >= 57 && hz <= 63) + hz = 60; + onedifhz = 1.0 / hz; + #end + + stopped = false; + resetTime(); + lastTime = realTime() - startTime; + for (i in 0...DIF_COUNT) + deltas[i] = 0; + + if (restartTimers) { + for (timeTask in timeTasks) { + timeTask.paused = false; + } + + for (frameTask in frameTasks) { + frameTask.paused = false; + } + } + } + + public static function stop(): Void { + stopped = true; + } + + public static function isStopped(): Bool { + return stopped; + } + + static function warpTimeTasksBack(time: Float, tasks: Array): Void { + for (timeTask in tasks) { + if (timeTask.start >= time) { + timeTask.next = timeTask.start; + } + else if (timeTask.period > 0) { + var sinceStart = time - timeTask.start; + var times = Math.ceil(sinceStart / timeTask.period); + timeTask.next = timeTask.start + times * timeTask.period; + } + } + } + + public static function warp(time: Float): Void { + if (time < lastTime) { + current = time; + lastTime = time; + lastFrameEnd = time; + + warpTimeTasksBack(time, outdatedTimeTasks); + warpTimeTasksBack(time, timeTasks); + + for (task in outdatedTimeTasks) { + if (task.next >= time) { + timeTasksScratchpad.push(task); + } + } + for (task in timeTasksScratchpad) { + outdatedTimeTasks.remove(task); + } + for (task in timeTasksScratchpad) { + insertSorted(timeTasks, task); + } + while (timeTasksScratchpad.length > 0) { + timeTasksScratchpad.remove(timeTasksScratchpad[0]); + } + } + else if (time > lastTime) { + // TODO: Changing startTime line prevents clients from falling into a + // warp-forward-then-wait-for-systemtime-to-catch-up-loop that causes + // choppy movement (e.g. every 3rd frame forward 3 times). + // But it causes backwards jumps in originally constant movements. + // And on HTML5 packets are received while no frames are executed, + // which causes the client to overtakes the server and then move + // farther away with each packet while being unable to synch back + // (backwards warping is not allowed to change startTime). + startTime -= (time - lastTime); + + current = time; + lastTime = time; + lastFrameEnd = time; + + executeTimeTasks(time); + } + } + + public static function executeFrame(): Void { + var real = realTime(); + var now: Float = real - startTime; + var delta = now - lastTime; + + var frameEnd: Float = lastFrameEnd; + + if (delta >= 0) { + if (kha.netsync.Session.the() == null) { + // tdif = 1.0 / 60.0; //force fixed frame rate + + if (delta > maxframetime) { + startTime += delta - maxframetime; + now = real - startTime; + delta = maxframetime; + frameEnd += delta; + } + else { + if (vsync) { + // var measured = delta; + // this is optimized not to run at exact speed + // but to run as fluid as possible + var frames = Math.round(delta / onedifhz); + if (frames < 1) { + return; + } + + var realdif = frames * onedifhz; + + delta = realdif; + for (i in 0...DIF_COUNT - 2) { + delta += deltas[i]; + deltas[i] = deltas[i + 1]; + } + delta += deltas[DIF_COUNT - 2]; + delta /= DIF_COUNT; + deltas[DIF_COUNT - 2] = realdif; + + frameEnd += delta; + + // trace("Measured: " + measured + " Frames: " + frames + " Delta: " + delta + " "); + } + else { + for (i in 0...DIF_COUNT - 1) { + deltas[i] = deltas[i + 1]; + } + deltas[DIF_COUNT - 1] = delta; + + var next: Float = 0; + for (i in 0...DIF_COUNT) { + next += deltas[i]; + } + next /= DIF_COUNT; + + // delta = interpolated_delta; // average the frame end estimation + + // lastTime = now; + frameEnd += next; + } + } + } + else { + frameEnd += delta; + } + + lastTime = now; + + if (!stopped) { // Stop simulation time + lastFrameEnd = frameEnd; + } + + // Extend endpoint by paused time (individually paused tasks) + for (pausedTask in pausedTimeTasks) { + pausedTask.next += delta; + } + + if (stopped) { + // Extend endpoint by paused time (running tasks) + for (timeTask in timeTasks) { + timeTask.next += delta; + } + } + + executeTimeTasks(frameEnd); + + // Maintain outdated task list + for (task in outdatedTimeTasks) { + if (task.next < frameEnd - timeWarpSaveTime) { + timeTasksScratchpad.push(task); + } + } + for (task in timeTasksScratchpad) { + outdatedTimeTasks.remove(task); + } + while (timeTasksScratchpad.length > 0) { + timeTasksScratchpad.remove(timeTasksScratchpad[0]); + } + } + + current = frameEnd; + + sortFrameTasks(); + for (frameTask in frameTasks) { + if (!stopped && !frameTask.paused && frameTask.active) { + if (!frameTask.task()) + frameTask.active = false; + } + } + + for (frameTask in frameTasks) { + if (!frameTask.active) { + toDeleteFrame.push(frameTask); + } + } + + while (toDeleteFrame.length > 0) { + frameTasks.remove(toDeleteFrame.pop()); + } + } + + static function executeTimeTasks(until: Float) { + while (timeTasks.length > 0) { + activeTimeTask = timeTasks[0]; + + if (activeTimeTask.next <= until) { + current = activeTimeTask.next; + + activeTimeTask.next += activeTimeTask.period; + timeTasks.remove(activeTimeTask); + + if (activeTimeTask.active && activeTimeTask.task()) { + if (activeTimeTask.period > 0 + && (activeTimeTask.duration == 0 || activeTimeTask.duration >= activeTimeTask.start + activeTimeTask.next)) { + insertSorted(timeTasks, activeTimeTask); + } + else { + archiveTimeTask(activeTimeTask, until); + } + } + else { + activeTimeTask.active = false; + archiveTimeTask(activeTimeTask, until); + } + } + else { + break; + } + } + activeTimeTask = null; + } + + static function archiveTimeTask(timeTask: TimeTask, frameEnd: Float) { + #if sys_server + if (timeTask.next > frameEnd - timeWarpSaveTime) { + outdatedTimeTasks.push(timeTask); + } + #end + } + + /** + * An approximation of the amount of time (in fractional seconds) that elapsed while the game was active. + * This value is optimized for achieving smooth framerates. + */ + public static function time(): Float { + return current; + } + + /** + * The amount of time (in fractional seconds) that elapsed since the game started. + */ + public static function realTime(): Float { + return System.time; + } + + public static function resetTime(): Void { + var now = System.time; + var dif = now - startTime; + startTime = now; + for (timeTask in timeTasks) { + timeTask.start -= dif; + timeTask.next -= dif; + } + for (i in 0...DIF_COUNT) + deltas[i] = 0; + current = 0; + lastTime = 0; + lastFrameEnd = 0; + } + + public static function addBreakableFrameTask(task: Void->Bool, priority: Int): Int { + frameTasks.push(new FrameTask(task, priority, ++currentFrameTaskId)); + frame_tasks_sorted = false; + return currentFrameTaskId; + } + + public static function addFrameTask(task: Void->Void, priority: Int): Int { + return addBreakableFrameTask(function() { + task(); + return true; + }, priority); + } + + public static function pauseFrameTask(id: Int, paused: Bool): Void { + for (frameTask in frameTasks) { + if (frameTask.id == id) { + frameTask.paused = paused; + break; + } + } + } + + public static function removeFrameTask(id: Int): Void { + for (frameTask in frameTasks) { + if (frameTask.id == id) { + frameTask.active = false; + break; + } + } + } + + public static function generateGroupId(): Int { + return ++currentGroupId; + } + + public static function addBreakableTimeTaskToGroup(groupId: Int, task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int { + var t = new TimeTask(); + t.active = true; + t.task = task; + t.id = ++currentTimeTaskId; + t.groupId = groupId; + + t.start = current + start; + t.period = 0; + if (period != 0) + t.period = period; + t.duration = 0; // infinite + if (duration != 0) + t.duration = t.start + duration; + + t.next = t.start; + insertSorted(timeTasks, t); + return t.id; + } + + public static function addTimeTaskToGroup(groupId: Int, task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int { + return addBreakableTimeTaskToGroup(groupId, function() { + task(); + return true; + }, start, period, duration); + } + + public static function addBreakableTimeTask(task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int { + return addBreakableTimeTaskToGroup(0, task, start, period, duration); + } + + public static function addTimeTask(task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int { + return addTimeTaskToGroup(0, task, start, period, duration); + } + + static function getTimeTask(id: Int): TimeTask { + if (activeTimeTask != null && activeTimeTask.id == id) + return activeTimeTask; + for (timeTask in timeTasks) { + if (timeTask.id == id) { + return timeTask; + } + } + for (timeTask in pausedTimeTasks) { + if (timeTask.id == id) { + return timeTask; + } + } + return null; + } + + public static function pauseTimeTask(id: Int, paused: Bool): Void { + var timeTask = getTimeTask(id); + if (timeTask != null) { + pauseRunningTimeTask(timeTask, paused); + } + if (activeTimeTask != null && activeTimeTask.id == id) { + activeTimeTask.paused = paused; + } + } + + static function pauseRunningTimeTask(timeTask: TimeTask, paused: Bool): Void { + timeTask.paused = paused; + if (paused) { + timeTasks.remove(timeTask); + pausedTimeTasks.push(timeTask); + } + else { + insertSorted(timeTasks, timeTask); + pausedTimeTasks.remove(timeTask); + } + } + + public static function pauseTimeTasks(groupId: Int, paused: Bool): Void { + if (paused) { + for (timeTask in timeTasks) { + if (timeTask.groupId == groupId) { + pauseRunningTimeTask(timeTask, paused); + } + } + } + else { + for (timeTask in pausedTimeTasks) { + if (timeTask.groupId == groupId) { + pauseRunningTimeTask(timeTask, paused); + } + } + } + if (activeTimeTask != null && activeTimeTask.groupId == groupId) { + activeTimeTask.paused = paused; + } + } + + public static function removeTimeTask(id: Int): Void { + var timeTask = getTimeTask(id); + if (timeTask != null) { + timeTask.active = false; + timeTasks.remove(timeTask); + } + } + + public static function removeTimeTasks(groupId: Int): Void { + for (timeTask in timeTasks) { + if (timeTask.groupId == groupId) { + timeTask.active = false; + timeTasksScratchpad.push(timeTask); + } + } + for (timeTask in timeTasksScratchpad) { + timeTasks.remove(timeTask); + } + while (timeTasksScratchpad.length > 0) { + timeTasksScratchpad.remove(timeTasksScratchpad[0]); + } + + if (activeTimeTask != null && activeTimeTask.groupId == groupId) { + activeTimeTask.active = false; + } + } + + public static function numTasksInSchedule(): Int { + return timeTasks.length + frameTasks.length; + } + + static function insertSorted(list: Array, task: TimeTask) { + for (i in 0...list.length) { + if (list[i].next > task.next) { + list.insert(i, task); + return; + } + } + list.push(task); + } + + static function sortFrameTasks(): Void { + if (frame_tasks_sorted) + return; + frameTasks.sort(function(a: FrameTask, b: FrameTask): Int { + return a.priority > b.priority ? 1 : ((a.priority < b.priority) ? -1 : 0); + }); + frame_tasks_sorted = true; + } +} diff --git a/Krom/Krom b/Krom/Krom old mode 100755 new mode 100644 index 9c87e68..f754db8 Binary files a/Krom/Krom and b/Krom/Krom differ diff --git a/Krom/Krom.exe b/Krom/Krom.exe index e5f40cf..13b63ed 100644 Binary files a/Krom/Krom.exe and b/Krom/Krom.exe differ diff --git a/Krom/Krom_opengl.exe b/Krom/Krom_opengl.exe index 1c367cd..f65f73c 100644 Binary files a/Krom/Krom_opengl.exe and b/Krom/Krom_opengl.exe differ diff --git a/leenkx.py b/leenkx.py index d976433..83fe3fa 100644 --- a/leenkx.py +++ b/leenkx.py @@ -310,9 +310,9 @@ class LeenkxAddonPreferences(AddonPreferences): layout.label(text="Welcome to Leenkx!") # Compare version Blender and Leenkx (major, minor) - if bpy.app.version[0] != 4 or bpy.app.version[1] != 2: + if bpy.app.version[:2] not in [(4, 4), (4, 2), (3, 6), (3, 3)]: box = layout.box().column() - box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version such as 4.2 | 3.6 | 3.3") + box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version") layout.prop(self, "sdk_path") sdk_path = get_sdk_path(context) diff --git a/leenkx/Sources/iron/object/ParticleSystem.hx b/leenkx/Sources/iron/object/ParticleSystem.hx index 245c525..6e55a36 100644 --- a/leenkx/Sources/iron/object/ParticleSystem.hx +++ b/leenkx/Sources/iron/object/ParticleSystem.hx @@ -51,6 +51,7 @@ class ParticleSystem { seed = pref.seed; particles = []; ready = false; + Data.getParticle(sceneName, pref.particle, function(b: ParticleData) { data = b; r = data.raw; @@ -70,7 +71,13 @@ class ParticleSystem { lifetime = r.lifetime / frameRate; animtime = (r.frame_end - r.frame_start) / frameRate; spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate; - for (i in 0...r.count) particles.push(new Particle(i)); + + for (i in 0...r.count) { + var particle = new Particle(i); + particle.sr = 1 - Math.random() * r.size_random; + particles.push(particle); + } + ready = true; }); } @@ -108,7 +115,7 @@ class ParticleSystem { } // Animate - time += Time.realDelta * speed; + time += Time.delta * speed; lap = Std.int(time / animtime); lapTime = time - lap * animtime; count = Std.int(lapTime / spawnRate); @@ -143,7 +150,7 @@ class ParticleSystem { } function setupGeomGpu(object: MeshObject, owner: MeshObject) { - var instancedData = new Float32Array(particles.length * 3); + var instancedData = new Float32Array(particles.length * 6); var i = 0; var normFactor = 1 / 32767; // pa.values are not normalized @@ -162,6 +169,10 @@ class ParticleSystem { instancedData.set(i, pa.values[j * pa.size ] * normFactor * scaleFactor.x); i++; instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++; instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++; + + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; } case 1: // Face @@ -185,6 +196,10 @@ class ParticleSystem { instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++; instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++; instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++; + + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; } case 2: // Volume @@ -195,9 +210,13 @@ class ParticleSystem { instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++; instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++; instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++; + + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; + instancedData.set(i, p.sr); i++; } } - object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage); + object.data.geom.setupInstanced(instancedData, 3, Usage.StaticUsage); } function fhash(n: Int): Float { @@ -236,9 +255,10 @@ class ParticleSystem { class Particle { public var i: Int; - public var x = 0.0; - public var y = 0.0; - public var z = 0.0; + public var px = 0.0; + public var py = 0.0; + public var pz = 0.0; + public var sr = 1.0; // Size random public var cameraDistance: Float; public function new(i: Int) { diff --git a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx index 734f6fb..8590b83 100644 --- a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx @@ -2,9 +2,11 @@ package leenkx.logicnode; import iron.object.Object; -#if lnx_physics +#if lnx_bullet import leenkx.trait.physics.PhysicsConstraint; import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType; +#elseif lnx_oimo +// TODO #end class AddPhysicsConstraintNode extends LogicNode { @@ -25,7 +27,7 @@ class AddPhysicsConstraintNode extends LogicNode { if (pivotObject == null || rb1 == null || rb2 == null) return; -#if lnx_physics +#if lnx_bullet var disableCollisions: Bool = inputs[4].get(); var breakable: Bool = inputs[5].get(); @@ -108,6 +110,8 @@ class AddPhysicsConstraintNode extends LogicNode { } pivotObject.addTrait(con); } +#elseif lnx_oimo +// TODO #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx index 874235b..3df20cf 100644 --- a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx @@ -4,7 +4,7 @@ import iron.object.Object; #if lnx_physics import leenkx.trait.physics.RigidBody; -import leenkx.trait.physics.bullet.RigidBody.Shape; +import leenkx.trait.physics.RigidBody.Shape; #end diff --git a/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx new file mode 100644 index 0000000..47318ed --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx @@ -0,0 +1,26 @@ +package leenkx.logicnode; + +class ArrayIndexListNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var array: Array = inputs[0].get(); + array = array.map(item -> Std.string(item)); + var value: Dynamic = inputs[1].get(); + var from: Int = 0; + + var arrayList: Array = []; + + var index: Int = array.indexOf(Std.string(value), from); + + while(index != -1){ + arrayList.push(index); + index = array.indexOf(Std.string(value), index+1); + } + + return arrayList; + } +} \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx index b553a79..9d2e05b 100644 --- a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx @@ -1,7 +1,7 @@ package leenkx.logicnode; #if lnx_physics -import leenkx.trait.physics.bullet.PhysicsWorld; +import leenkx.trait.physics.PhysicsWorld; #end import leenkx.trait.navigation.Navigation; import iron.object.Object; diff --git a/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx index 0bce755..0547aaf 100644 --- a/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx @@ -1,7 +1,7 @@ package leenkx.logicnode; #if lnx_physics -import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; +import leenkx.trait.physics.PhysicsConstraint.ConstraintAxis; #end class PhysicsConstraintNode extends LogicNode { diff --git a/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx b/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx index 71c48e5..ad6d2df 100644 --- a/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx +++ b/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx @@ -10,6 +10,10 @@ class KinematicCharacterController extends iron.Trait { public function new() { typedef KinematicCharacterController = leenkx.trait.physics.bullet.KinematicCharacterController; + #else + + typedef KinematicCharacterController = leenkx.trait.physics.oimo.KinematicCharacterController; + #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx b/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx index 5ac6d91..eeb5358 100644 --- a/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx +++ b/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx @@ -3,17 +3,16 @@ package leenkx.trait.physics; #if (!lnx_physics) class PhysicsConstraint extends iron.Trait { public function new() { super(); } } +@:enum abstract ConstraintAxis(Int) from Int to Int { } #else #if lnx_bullet - typedef PhysicsConstraint = leenkx.trait.physics.bullet.PhysicsConstraint; - + typedef ConstraintAxis = leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; #else - typedef PhysicsConstraint = leenkx.trait.physics.oimo.PhysicsConstraint; - + typedef ConstraintAxis = leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintAxis; #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx index 3e35a03..db6f28a 100644 --- a/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx @@ -2,6 +2,7 @@ package leenkx.trait.physics; #if (!lnx_physics) +class Hit { } class PhysicsWorld extends iron.Trait { public function new() { super(); } } #else @@ -9,11 +10,11 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } } #if lnx_bullet typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld; - + typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit; #else typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld; - + typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit; #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/RigidBody.hx b/leenkx/Sources/leenkx/trait/physics/RigidBody.hx index 7247531..2742418 100644 --- a/leenkx/Sources/leenkx/trait/physics/RigidBody.hx +++ b/leenkx/Sources/leenkx/trait/physics/RigidBody.hx @@ -3,17 +3,20 @@ package leenkx.trait.physics; #if (!lnx_physics) class RigidBody extends iron.Trait { public function new() { super(); } } +@:enum abstract Shape(Int) from Int to Int { } #else #if lnx_bullet typedef RigidBody = leenkx.trait.physics.bullet.RigidBody; - + typedef Shape = leenkx.trait.physics.bullet.RigidBody.Shape; + #else typedef RigidBody = leenkx.trait.physics.oimo.RigidBody; - + typedef Shape = leenkx.trait.physics.oimo.RigidBody.Shape; + #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx index 8c347a3..8b067a3 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx @@ -1,5 +1,8 @@ package leenkx.trait.physics.bullet; +#if lnx_bullet +import leenkx.trait.physics.bullet.PhysicsWorld.DebugDrawMode; + import bullet.Bt.Vector3; import kha.FastFloat; @@ -18,15 +21,21 @@ class DebugDrawHelper { static inline var contactPointNormalColor = 0xffffffff; static inline var contactPointDrawLifetime = true; + final rayCastColor: Vec4 = new Vec4(0.0, 1.0, 0.0); + final rayCastHitColor: Vec4 = new Vec4(1.0, 0.0, 0.0); + final rayCastHitPointColor: Vec4 = new Vec4(1.0, 1.0, 0.0); + final physicsWorld: PhysicsWorld; final lines: Array = []; final texts: Array = []; var font: kha.Font = null; - var debugMode: PhysicsWorld.DebugDrawMode = NoDebug; + var rayCasts:Array = []; + var debugDrawMode: DebugDrawMode = NoDebug; - public function new(physicsWorld: PhysicsWorld) { + public function new(physicsWorld: PhysicsWorld, debugDrawMode: DebugDrawMode) { this.physicsWorld = physicsWorld; + this.debugDrawMode = debugDrawMode; #if lnx_ui iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont: kha.Font) { @@ -35,6 +44,11 @@ class DebugDrawHelper { #end iron.App.notifyOnRender2D(onRender); + if (debugDrawMode & DrawRayCast != 0) { + iron.App.notifyOnUpdate(function () { + rayCasts.resize(0); + }); + } } public function drawLine(from: bullet.Bt.Vector3, to: bullet.Bt.Vector3, color: bullet.Bt.Vector3) { @@ -63,25 +77,6 @@ class DebugDrawHelper { } } - // Draws raycast in its own function because - // something is conflicting with the btVector3 and JS pointer wrapping - public function drawRayCast(fx:FastFloat, fy:FastFloat, fz:FastFloat, tx:FastFloat, ty:FastFloat, tz:FastFloat, r:FastFloat, g:FastFloat, b:FastFloat) { - final fromScreenSpace = worldToScreenFast(new Vec4(fx, fy, fz, 1.0)); - final toScreenSpace = worldToScreenFast(new Vec4(tx, ty, tz, 1.0)); - - // TO DO: May still go off screen sides. - if (fromScreenSpace.w == 1 || toScreenSpace.w == 1) { - final color = kha.Color.fromFloats(r, g, b, 1.0); - lines.push({ - fromX: fromScreenSpace.x, - fromY: fromScreenSpace.y, - toX: toScreenSpace.x, - toY: toScreenSpace.y, - color: color - }); - } - } - public function drawContactPoint(pointOnB: Vector3, normalOnB: Vector3, distance: kha.FastFloat, lifeTime: Int, color: Vector3) { #if js pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB); @@ -126,7 +121,62 @@ class DebugDrawHelper { x: contactPointScreenSpace.x, y: contactPointScreenSpace.y, color: color, - text: Std.string(lifeTime), + text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed + }); + } + } + } + + public function rayCast(rayCastData:TRayCastData) { + rayCasts.push(rayCastData); + } + + function drawRayCast(f: Vec4, t: Vec4, hit: Bool) { + final from = worldToScreenFast(f.clone()); + final to = worldToScreenFast(t.clone()); + var c: kha.Color; + + if (from.w == 1 && to.w == 1) { + if (hit) c = kha.Color.fromFloats(rayCastHitColor.x, rayCastHitColor.y, rayCastHitColor.z); + else c = kha.Color.fromFloats(rayCastColor.x, rayCastColor.y, rayCastColor.z); + + lines.push({ + fromX: from.x, + fromY: from.y, + toX: to.x, + toY: to.y, + color: c + }); + } + } + + function drawHitPoint(hp: Vec4) { + final hitPoint = worldToScreenFast(hp.clone()); + final c = kha.Color.fromFloats(rayCastHitPointColor.x, rayCastHitPointColor.y, rayCastHitPointColor.z); + + if (hitPoint.w == 1) { + lines.push({ + fromX: hitPoint.x - contactPointSizePx, + fromY: hitPoint.y - contactPointSizePx, + toX: hitPoint.x + contactPointSizePx, + toY: hitPoint.y + contactPointSizePx, + color: c + }); + + lines.push({ + fromX: hitPoint.x - contactPointSizePx, + fromY: hitPoint.y + contactPointSizePx, + toX: hitPoint.x + contactPointSizePx, + toY: hitPoint.y - contactPointSizePx, + color: c + }); + + if (font != null) { + texts.push({ + x: hitPoint.x, + y: hitPoint.y, + color: c, + text: 'RAYCAST HIT' }); } } @@ -155,13 +205,13 @@ class DebugDrawHelper { }); } - public function setDebugMode(debugMode: PhysicsWorld.DebugDrawMode) { - this.debugMode = debugMode; + public function setDebugMode(debugDrawMode: DebugDrawMode) { + this.debugDrawMode = debugDrawMode; } - public function getDebugMode(): PhysicsWorld.DebugDrawMode { + public function getDebugMode(): DebugDrawMode { #if js - return debugMode; + return debugDrawMode; #elseif hl return physicsWorld.getDebugDrawMode(); #else @@ -170,7 +220,7 @@ class DebugDrawHelper { } function onRender(g: kha.graphics2.Graphics) { - if (getDebugMode() == NoDebug && !physicsWorld.drawRaycasts) { + if (getDebugMode() == NoDebug) { return; } @@ -179,7 +229,9 @@ class DebugDrawHelper { // will cause Bullet to call the btIDebugDraw callbacks), but this way // we can ensure that--within a frame--the function will not be called // before some user-specific physics update, which would result in a - // one-frame drawing delay... + // one-frame drawing delay... Ideally we would ensure that debugDrawWorld() + // is called when all other (late) update callbacks are already executed... + physicsWorld.world.debugDrawWorld(); g.opacity = 1.0; @@ -199,6 +251,17 @@ class DebugDrawHelper { } texts.resize(0); } + + if (debugDrawMode & DrawRayCast != 0) { + for (rayCastData in rayCasts) { + if (rayCastData.hasHit) { + drawRayCast(rayCastData.from, rayCastData.hitPoint, true); + drawHitPoint(rayCastData.hitPoint); + } else { + drawRayCast(rayCastData.from, rayCastData.to, false); + } + } + } } /** @@ -241,3 +304,14 @@ class TextData { public var color: kha.Color; public var text: String; } + +@:structInit +typedef TRayCastData = { + var from: Vec4; + var to: Vec4; + var hasHit: Bool; + @:optional var hitPoint: Vec4; + @:optional var hitNormal: Vec4; +} + +#end \ No newline at end of file diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 39ad145..e2bea17 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -71,7 +71,6 @@ class PhysicsWorld extends Trait { public var convexHitPointWorld = new Vec4(); public var convexHitNormalWorld = new Vec4(); var pairCache: Bool = false; - public var drawRaycasts: Bool = false; static var nullvec = true; static var vec1: bullet.Bt.Vector3 = null; @@ -102,7 +101,7 @@ class PhysicsWorld extends Trait { - public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug, drawRaycasts: Bool = false) { + public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) { super(); if (nullvec) { @@ -121,7 +120,6 @@ class PhysicsWorld extends Trait { this.timeScale = timeScale; this.maxSteps = maxSteps; this.solverIterations = solverIterations; - this.drawRaycasts = drawRaycasts; // First scene if (active == null) { @@ -408,14 +406,6 @@ class PhysicsWorld extends Trait { var worldDyn: bullet.Bt.DynamicsWorld = world; var worldCol: bullet.Bt.CollisionWorld = worldDyn; - if (this.drawRaycasts && this.debugDrawHelper != null) { - this.debugDrawHelper.drawRayCast( - rayFrom.x(), rayFrom.y(), rayFrom.z(), - rayTo.x(), rayTo.y(), rayTo.z(), - 0.73, 0.341, 1.0 - ); - } - worldCol.rayTest(rayFrom, rayTo, rayCallback); var rb: RigidBody = null; var hitInfo: Hit = null; @@ -441,6 +431,16 @@ class PhysicsWorld extends Trait { #end } + if (getDebugDrawMode() & DrawRayCast != 0) { + debugDrawHelper.rayCast({ + from: from, + to: to, + hasHit: rc.hasHit(), + hitPoint: hitPointWorld, + hitNormal: hitNormalWorld + }); + } + #if js bullet.Bt.Ammo.destroy(rayCallback); #else @@ -519,22 +519,14 @@ class PhysicsWorld extends Trait { public function setDebugDrawMode(debugDrawMode: DebugDrawMode) { if (debugDrawHelper == null) { - // Initialize if helper is null AND (standard debug mode is requested OR our custom raycast drawing is requested) - if (debugDrawMode != NoDebug || this.drawRaycasts) { - initDebugDrawing(); - } - else { - // Helper is null and no debug drawing needed, so exit + if (debugDrawMode == NoDebug) { return; } + initDebugDrawing(debugDrawMode); } - // If we reached here, the helper is initialized (or was already) - // Now set the standard Bullet debug mode on the actual drawer #if js - // Ensure drawer exists before setting mode (might have just been initialized) - var drawer = world.getDebugDrawer(); - if (drawer != null) drawer.setDebugMode(debugDrawMode); + world.getDebugDrawer().setDebugMode(debugDrawMode); #elseif hl hlDebugDrawer_setDebugMode(debugDrawMode); #end @@ -554,8 +546,8 @@ class PhysicsWorld extends Trait { #end } - function initDebugDrawing() { - debugDrawHelper = new DebugDrawHelper(this); + function initDebugDrawing(debugDrawMode: DebugDrawMode) { + debugDrawHelper = new DebugDrawHelper(this, debugDrawMode); #if js final drawer = new bullet.Bt.DebugDrawer(); @@ -691,6 +683,8 @@ enum abstract DebugDrawMode(Int) from Int to Int { **/ var DrawFrames = 1 << 15; + var DrawRayCast = 1 << 16; + @:op(~A) public inline function bitwiseNegate(): DebugDrawMode { return ~this; } diff --git a/leenkx/blender/data/lnx_data.blend b/leenkx/blender/data/lnx_data.blend index 51ffbaa..239b718 100644 Binary files a/leenkx/blender/data/lnx_data.blend and b/leenkx/blender/data/lnx_data.blend differ diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 40b6375..ffb2434 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -324,6 +324,20 @@ class LeenkxExporter: def export_object_transform(self, bobject: bpy.types.Object, o): wrd = bpy.data.worlds['Lnx'] + # HACK: In Blender 4.2.x, each camera must be selected to ensure its matrix is correctly assigned + if bpy.app.version >= (4, 2, 0) and bobject.type == 'CAMERA' and bobject.users_scene: + current_scene = bpy.context.window.scene + + bpy.context.window.scene = bobject.users_scene[0] + bpy.context.view_layer.update() + + bobject.select_set(True) + bpy.context.view_layer.update() + bobject.select_set(False) + + bpy.context.window.scene = current_scene + bpy.context.view_layer.update() + # Static transform o['transform'] = {'values': LeenkxExporter.write_matrix(bobject.matrix_local)} @@ -1552,8 +1566,7 @@ class LeenkxExporter: log.error(e.message) else: # Assume it was caused because of encountering n-gons - log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. -Make sure the mesh only has tris/quads.""") + log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. Make sure the mesh only has tris/quads.""") tangdata = np.empty(num_verts * 3, dtype='= (4, 1, 0): if node.noise_type == "FBM": + state.curshader.add_function(c_functions.str_tex_musgrave) if out_socket == node.outputs[1]: - state.curshader.add_function(c_functions.str_tex_musgrave) - res = 'vec3(tex_musgrave_f({0} * {1}), tex_musgrave_f({0} * {1} + 120.0), tex_musgrave_f({0} * {1} + 168.0))'.format(co, scale, detail, distortion) + res = 'vec3(tex_musgrave_f({0} * {1}, {2}, {3}), tex_musgrave_f({0} * {1} + 120.0, {2}, {3}), tex_musgrave_f({0} * {1} + 168.0, {2}, {3}))'.format(co, scale, detail, distortion) else: - res = f'tex_musgrave_f({co} * {scale} * 1.0)' + res = f'tex_musgrave_f({co} * {scale} * 1.0, {detail}, {distortion})' else: if out_socket == node.outputs[1]: res = 'vec3(tex_noise({0} * {1},{2},{3}), tex_noise({0} * {1} + 120.0,{2},{3}), tex_noise({0} * {1} + 168.0,{2},{3}))'.format(co, scale, detail, distortion) diff --git a/leenkx/blender/lnx/material/make_particle.py b/leenkx/blender/lnx/material/make_particle.py index a8dd707..d22e534 100644 --- a/leenkx/blender/lnx/material/make_particle.py +++ b/leenkx/blender/lnx/material/make_particle.py @@ -86,7 +86,7 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('p_fade = sin(min((p_age / 2) * 3.141592, 3.141592));') if out_index: - vert.add_out('float p_index'); + vert.add_out('float p_index') vert.write('p_index = gl_InstanceID;') def write_tilesheet(vert): diff --git a/leenkx/blender/lnx/material/make_shader.py b/leenkx/blender/lnx/material/make_shader.py index 8fd4dda..89f4fe9 100644 --- a/leenkx/blender/lnx/material/make_shader.py +++ b/leenkx/blender/lnx/material/make_shader.py @@ -209,7 +209,8 @@ def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[O global_elems.append({'name': 'ipos', 'data': 'float3'}) if 'Rot' in inst: global_elems.append({'name': 'irot', 'data': 'float3'}) - if 'Scale' in inst: + #HACK: checking `mat.arm_particle_flag` to force appending 'iscl' to the particle's vertex shader + if 'Scale' in inst or mat.arm_particle_flag: global_elems.append({'name': 'iscl', 'data': 'float3'}) elif inst == 'Off': diff --git a/leenkx/blender/lnx/nodes_logic.py b/leenkx/blender/lnx/nodes_logic.py index dc125bd..1babb94 100644 --- a/leenkx/blender/lnx/nodes_logic.py +++ b/leenkx/blender/lnx/nodes_logic.py @@ -77,6 +77,25 @@ class LNX_MT_NodeAddOverride(bpy.types.Menu): layout.separator() layout.menu(f'LNX_MT_{INTERNAL_GROUPS_MENU_ID}_menu', text=internal_groups_menu_class.bl_label, icon='OUTLINER_OB_GROUP_INSTANCE') + elif context.space_data.tree_type == 'ShaderNodeTree': + # TO DO - Recursively gather nodes and draw them to menu + + LNX_MT_NodeAddOverride.overridden_draw(self, context) + + layout = self.layout + layout.separator() + layout.separator() + col = layout.column() + col.label(text="Custom") + + shader_data_op = col.operator("node.add_node", text="Shader Data") + shader_data_op.type = "LnxShaderDataNode" + shader_data_op.use_transform = True + + particle_op = col.operator("node.add_node", text="Custom Particle") + particle_op.type = "LnxCustomParticleNode" + particle_op.use_transform = True + else: LNX_MT_NodeAddOverride.overridden_draw(self, context) diff --git a/leenkx/blender/lnx/props.py b/leenkx/blender/lnx/props.py index b378351..3ad4c0c 100644 --- a/leenkx/blender/lnx/props.py +++ b/leenkx/blender/lnx/props.py @@ -197,38 +197,38 @@ def init_properties(): items=[('Bullet', 'Bullet', 'Bullet'), ('Oimo', 'Oimo', 'Oimo')], name="Physics Engine", default='Bullet', update=assets.invalidate_compiler_cache) - bpy.types.World.lnx_bullet_dbg_draw_wireframe = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_wireframe = BoolProperty( name="Collider Wireframes", default=False, description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations" ) - bpy.types.World.lnx_bullet_dbg_draw_raycast = BoolProperty( - name="Trace Raycast", default=False, + bpy.types.World.lnx_physics_dbg_draw_raycast = BoolProperty( + name="Raycasts", default=False, description="Draw raycasts to trace the results" ) - bpy.types.World.lnx_bullet_dbg_draw_aabb = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_aabb = BoolProperty( name="Axis-aligned Minimum Bounding Boxes", default=False, description="Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes" ) - bpy.types.World.lnx_bullet_dbg_draw_contact_points = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_contact_points = BoolProperty( name="Contact Points", default=False, description="Visualize contact points of multiple colliders" ) - bpy.types.World.lnx_bullet_dbg_draw_constraints = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_constraints = BoolProperty( name="Constraints", default=False, description="Draw axis gizmos for important constraint points" ) - bpy.types.World.lnx_bullet_dbg_draw_constraint_limits = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_constraint_limits = BoolProperty( name="Constraint Limits", default=False, description="Draw additional constraint information such as distance or angle limits" ) - bpy.types.World.lnx_bullet_dbg_draw_normals = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_normals = BoolProperty( name="Face Normals", default=False, description=( "Draw the normal vectors of the triangles of the physics collider meshes." - " This only works for mesh collision shapes" + " This only works with Bullet physics, for mesh collision shapes" ) ) - bpy.types.World.lnx_bullet_dbg_draw_axis_gizmo = BoolProperty( + bpy.types.World.lnx_physics_dbg_draw_axis_gizmo = BoolProperty( name="Axis Gizmos", default=False, description=( "Draw a small axis gizmo at the origin of the collision shape." diff --git a/leenkx/blender/lnx/props_ui.py b/leenkx/blender/lnx/props_ui.py index 3198918..c266d9c 100644 --- a/leenkx/blender/lnx/props_ui.py +++ b/leenkx/blender/lnx/props_ui.py @@ -1907,7 +1907,7 @@ class LNX_PT_RenderPathPostProcessPanel(bpy.types.Panel): col.prop(rpdat, "rp_bloom") _col = col.column() _col.enabled = rpdat.rp_bloom - if bpy.app.version <= (4, 2, 4): + if bpy.app.version < (4, 3, 0): _col.prop(rpdat, 'lnx_bloom_follow_blender') if not rpdat.lnx_bloom_follow_blender: _col.prop(rpdat, 'lnx_bloom_threshold') @@ -2749,20 +2749,20 @@ class LNX_PT_BulletDebugDrawingPanel(bpy.types.Panel): layout.use_property_decorate = False wrd = bpy.data.worlds['Lnx'] - if wrd.lnx_physics_engine != 'Bullet': + if wrd.lnx_physics_engine != 'Bullet' and wrd.lnx_physics_engine != 'Oimo': row = layout.row() row.alert = True - row.label(text="Physics debug drawing is only supported for the Bullet physics engine") + row.label(text="Physics debug drawing is only supported for the Bullet and Oimo physics engines") col = layout.column(align=False) - col.prop(wrd, "lnx_bullet_dbg_draw_wireframe") - col.prop(wrd, "lnx_bullet_dbg_draw_raycast") - col.prop(wrd, "lnx_bullet_dbg_draw_aabb") - col.prop(wrd, "lnx_bullet_dbg_draw_contact_points") - col.prop(wrd, "lnx_bullet_dbg_draw_constraints") - col.prop(wrd, "lnx_bullet_dbg_draw_constraint_limits") - col.prop(wrd, "lnx_bullet_dbg_draw_normals") - col.prop(wrd, "lnx_bullet_dbg_draw_axis_gizmo") + col.prop(wrd, "lnx_physics_dbg_draw_wireframe") + col.prop(wrd, "lnx_physics_dbg_draw_raycast") + col.prop(wrd, "lnx_physics_dbg_draw_aabb") + col.prop(wrd, "lnx_physics_dbg_draw_contact_points") + col.prop(wrd, "lnx_physics_dbg_draw_constraints") + col.prop(wrd, "lnx_physics_dbg_draw_constraint_limits") + col.prop(wrd, "lnx_physics_dbg_draw_normals") + col.prop(wrd, "lnx_physics_dbg_draw_axis_gizmo") def draw_custom_node_menu(self, context): """Extension of the node context menu. diff --git a/leenkx/blender/lnx/utils.py b/leenkx/blender/lnx/utils.py index 4730cfd..8ac35d2 100644 --- a/leenkx/blender/lnx/utils.py +++ b/leenkx/blender/lnx/utils.py @@ -828,8 +828,8 @@ def check_blender_version(op: bpy.types.Operator): """Check whether the Blender version is supported by Leenkx, if not, report in UI. """ - if bpy.app.version[0] != 4 or bpy.app.version[1] != 2: - op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version such as 4.2 | 3.6 | 3.3') + if bpy.app.version[:2] not in [(4, 4), (4, 2), (3, 6), (3, 3)]: + op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version') def check_saved(self):