diff --git a/LDYOM/Languages/English.toml b/LDYOM/Languages/English.toml index 1673300e..81471000 100644 --- a/LDYOM/Languages/English.toml +++ b/LDYOM/Languages/English.toml @@ -130,6 +130,7 @@ nothing_params = "This objective has no parameters" set_increment_global_variable = "Set/Increment Global Variable" set_characteristics_vehicle = "Set vehicle characteristics" follow_carrec_path_vehicle = "Following Carrec Path with Vehicle" +play_camera_path = "Play camera path" [objective_categories] world = "World" @@ -357,6 +358,8 @@ visual_effects = "Visual Effects" visual_effect = "Visual Effect" checkpoints = "Checkpoints" checkpoint = "Checkpoint" +camera_paths = "Cinematic Cameras" +camera_path = "Cinematic Camera" [skin_selector] title = "Skin Selector" @@ -1007,3 +1010,17 @@ play = "Play" rerec = "Rerecord" create_copy_vehicle = "Create Vehicle Copy" overlay_states = ["When ready to record, press I", "Recording started, press O to stop", "Press O again to replay, press P to save"] + +[camera_paths] +control = "Camera Control (RMB+):\n Movement: WASD or arrow keys, QE\n Movement speed: %.2f (adjust with mouse wheel)\n View: mouse movement" +degree = "Degree" +weight = "Point Weight" +control_points = "Control Points" +add_control_point = "Add Control Point" +custom_control_knots = "Editing Knots" +preview_current_point = "Preview Current Point" +update_control_point = "Update Control Point" +rotation_easing = "Rotation Easing Type" +play = "Play" +catmull_rom_rotations = "Catmull-Rom Centripetal Rotations" +invalid_curve_warning = "Invalid curve data, try reducing the degree of the curve or adding more control points" diff --git a/LDYOM/Languages/Polish.toml b/LDYOM/Languages/Polish.toml index 0e71ba68..67e20344 100644 --- a/LDYOM/Languages/Polish.toml +++ b/LDYOM/Languages/Polish.toml @@ -131,6 +131,7 @@ nothing_params = "Ten cel nie ma parametrów" set_increment_global_variable = "Ustaw/zwiększ zmienną globalną" set_characteristics_vehicle = "Ustawienia cech pojazdu" follow_carrec_path_vehicle = "Podążanie ścieżką Carrec dla pojazdu" +play_camera_path = "Odtwórz ścieżkę kamery" [objective_categories] world = "Świat" @@ -358,6 +359,8 @@ visual_effects = "Efekty wizualne" visual_effect = "Efekt wizualny" checkpoints = "Punkty kontrolne" checkpoint = "Punkt kontrolny" +camera_paths = "Ścieżki kamery" +camera_path = "Ścieżka kamery" [skin_selector] title = "Wybór skina" @@ -1008,3 +1011,17 @@ play = "Odtwórz" rerec = "Nagranie ponowne" create_copy_vehicle = "Utwórz kopię pojazdu" overlay_states = ["Gdy będziesz gotowy do nagrywania, naciśnij I", "Nagrywanie rozpoczęte, naciśnij O, aby zatrzymać", "Naciśnij ponownie O, aby ponownie odtworzyć, naciśnij P, aby zapisać"] + +[camera_paths] +control = "Sterowanie kamerą (PPM+):\n Przesuwanie: WASD lub strzałki, QE\n Prędkość przesuwania: %.2f (zmiana za pomocą kółka myszy)\n Widok: ruch myszy" +degree = "Stopień" +weight = "Waga punktu" +control_points = "Punkty kontrolne" +add_control_point = "Dodaj punkt kontrolny" +custom_control_knots = "Edycja węzłów kontrolnych" +preview_current_point = "Podgląd bieżącego punktu" +update_control_point = "Aktualizuj punkt kontrolny" +rotation_easing = "Typ łagodzenia obrotu" +play = "Odtwórz" +catmull_rom_rotations = "Obroty Catmull-Roma" +invalid_curve_warning = "Dane krzywej są niewłaściwe, spróbuj zmniejszyć stopień krzywej lub dodać więcej punktów kontrolnych" diff --git a/LDYOM/Languages/Portuguese.toml b/LDYOM/Languages/Portuguese.toml index 47b2d900..a875c0b3 100644 --- a/LDYOM/Languages/Portuguese.toml +++ b/LDYOM/Languages/Portuguese.toml @@ -131,6 +131,7 @@ nothing_params = "Sem parâmetros" set_increment_global_variable = "Definir/aumentar variável global" set_characteristics_vehicle = "Definir características do veículo" follow_carrec_path_vehicle = "Seguir caminho Carrec do veículo" +play_camera_path = "Reproduzir caminho da câmera" [objective_categories] world = "Mundo" @@ -358,6 +359,8 @@ pyrotechnics = "Pirotecnia" audio = "Áudio" visual_effect = "Efeito visual" checkpoint = "Ponto de verificação" +camera_paths = "Caminhos da câmera" +camera_path = "Caminho da câmera" [skin_selector] title = "Seleção de Skin" @@ -1008,3 +1011,17 @@ play = "Reproduzir" rerec = "Refazer" create_copy_vehicle = "Criar cópia do veículo" overlay_states = ["Quando estiver pronto para gravar, pressione I", "Gravação iniciada, pressione O para parar", "Pressione O novamente para regravar, pressione P para salvar"] + +[camera_paths] +control = "Controle da câmera (Botão direito do mouse +):\n Movimento: WASD ou setas, QE\n Velocidade de movimento: %.2f (altera com a roda do mouse)\n Vista: movimento do mouse" +degree = "Grau" +weight = "Peso do ponto" +control_points = "Pontos de controle" +add_control_point = "Adicionar ponto de controle" +custom_control_knots = "Edição de nós personalizada" +preview_current_point = "Visualizar ponto atual" +update_control_point = "Atualizar ponto de controle" +rotation_easing = "Tipo de suavização de rotação" +play = "Reproduzir" +catmull_rom_rotations = "Rotações Catmull-Rom centrípetas" +invalid_curve_warning = "A curva é inválida, tente reduzir o grau da curva ou adicionar mais pontos de controle" diff --git a/LDYOM/Languages/Russian.toml b/LDYOM/Languages/Russian.toml index de85a541..f8efef48 100644 --- a/LDYOM/Languages/Russian.toml +++ b/LDYOM/Languages/Russian.toml @@ -130,6 +130,7 @@ nothing_params = "У этой цели нет параметров" set_increment_global_variable = "Установить/увеличить гл. переменную" set_characteristics_vehicle = "Установить характеристики ТС" follow_carrec_path_vehicle = "Следование по Carrec пути" +play_camera_path = "Воспроизвести путь камеры" [objective_categories] world = "Мир" @@ -384,6 +385,7 @@ particles = "Частицы" pickups = "Пикапы" visual_effects = "Визуальные эффекты" checkpoints = "Контрольные точки" +camera_paths = "Кинокамеры" actor = "Актер" vehicle = "ТС-во" object = "Объект" @@ -394,6 +396,7 @@ pyrotechnics = "Пиротехника" audio = "Аудио" visual_effect = "Визуальный эффект" checkpoint = "Контрольная точка" +camera_path = "Кинокамера" [skin_selector] title = "Выбор скина" @@ -545,6 +548,24 @@ conditions = [ "Игрок в ТС" ] +[camera_paths] +control = """Управление камерой (ПКМ+): + Перемещение: WASD или стрелки, QE + Скорость перемещения: %.2f (изменяется колесом мыши) + Вид: движение мыши""" +degree = "Степень" +weight = "Вес точки" +control_points = "Контрольные точки" +add_control_point = "Добавить контрольную точку" +custom_control_knots = "Редактирование узлов" +preview_current_point = "Предпросмотр текущей точки" +update_control_point = "Обновить контрольную точку" +rotation_easing = "Тип сглаживания вращения" +play = "Воспроизвести" +catmull_rom_rotations = "Центростремительное вращение Кэтмулла-Рома" +invalid_curve_warning = "Данные кривой недействительны, попробуйте уменьшить степень кривой или добавить больше контрольных точек" + + [hotkey_editor] title = "Редактор горячих клавиш" clear = "Очистить" diff --git a/LDYOM/Scripts/annotations/ldyom.lua b/LDYOM/Scripts/annotations/ldyom.lua index b1ca26be..a5b1fefc 100644 --- a/LDYOM/Scripts/annotations/ldyom.lua +++ b/LDYOM/Scripts/annotations/ldyom.lua @@ -870,6 +870,20 @@ LDActor = { copy = function() end, } + +---@class LDCameraPath +---@field getName fun(self): string +---@field copy fun(self): LDCameraPath +---@field getUuid fun(self): uuid +---@field getPositionCurve fun(self): userdata +---@field isCustomControlKnots fun(self): boolean +---@field getRotationsCurve fun(self): CQuaternion[] +---@field getRotationsEasing fun(self): integer[] +---@field getTime fun(self): number +---@field isCatmullRomRotations fun(self): boolean +---@field isValid fun(self): boolean + + ---@class LDSceneSettings ---@field groupRelations integer[][] ---@field time integer[] @@ -908,6 +922,8 @@ LDScene = { getVisualEffects = function() end, ---@return LDCheckpoint[] getCheckpoints = function() end, + ---@return LDCameraPath[] + getCameraPaths = function() end, ---@return LDSceneSettings getSceneSettings = function() end, ---@return boolean @@ -1016,8 +1032,45 @@ ld.settings = { time = {} ---current time in milliseconds ----@type number -time.snTimeInMilliseconds = nil +---@return number +time.getSnTimeInMilliseconds = function () +end + + +---@class CCameraExtend +CCameraExtend = { + ---@param mode boolean + setExtendMode = function(self, mode) end, + ---@param attachEntity userdata + ---@param lookEntity userdata + ---@param rotationOffset CQuaternion + ---@param posOffset number[] + attachToEntity = function(self, attachEntity, lookEntity, rotationOffset, posOffset) end, + ---@param path LDCameraPath + ---@return boolean + playCameraPath = function(self, path) end, + stopCameraPath = function(self) end, + ---@return boolean + isPlayingPath = function(self) end, + ---@return number + getPathProgress = function(self) end, +} + + +---@class CCameraExtend +ld.TheCameraExtend = nil + + +ld.CameraMutex = { + ---@return boolean + isLocked = function() end, +} + +---@class CutsceneMutexGuard +CutsceneMutexGuard = { + ---@return CutsceneMutexGuard + new = function() end, +} glm = {} diff --git a/LDYOM/Scripts/scripts/Core/Languages/English.toml b/LDYOM/Scripts/scripts/Core/Languages/English.toml index b55efbda..ebc48bb6 100644 --- a/LDYOM/Scripts/scripts/Core/Languages/English.toml +++ b/LDYOM/Scripts/scripts/Core/Languages/English.toml @@ -44,6 +44,7 @@ variable = "Variable" global_variable = "Global variable" objective = "Objective" char = "Character" +camera_path = "Camera Path" [nodes.types.core] flow = "Flow" number = "Number" @@ -231,3 +232,10 @@ on_complete = "On Completion" [nodes.core.stop_carrec_path_vehicle] title = "Stop Carrec Path for Vehicle" + +[nodes.core.play_camera_path] +title = "Play camera path" +on_complete = "On completion" + +[nodes.core.stop_camera_path] +title = "Stop camera path" diff --git a/LDYOM/Scripts/scripts/Core/Languages/Polish.toml b/LDYOM/Scripts/scripts/Core/Languages/Polish.toml index 0494a3d7..4be08504 100644 --- a/LDYOM/Scripts/scripts/Core/Languages/Polish.toml +++ b/LDYOM/Scripts/scripts/Core/Languages/Polish.toml @@ -44,6 +44,7 @@ variable = "Zmienna" global_variable = "Zmienna globalna" objective = "Cel" char = "Postać" +camera_path = "Ścieżka kamery" [nodes.types.core] flow = "Przepływ" number = "Liczba" @@ -234,3 +235,10 @@ on_complete = "Po zakończeniu" [nodes.core.stop_carrec_path_vehicle] title = "Zatrzymaj ścieżkę Carrec dla pojazdu" + +[nodes.core.play_camera_path] +title = "Odtwórz ścieżkę kamery" +on_complete = "Po zakończeniu" + +[nodes.core.stop_camera_path] +title = "Zatrzymaj ścieżkę kamery" diff --git a/LDYOM/Scripts/scripts/Core/Languages/Portuguese.toml b/LDYOM/Scripts/scripts/Core/Languages/Portuguese.toml index 538a0ad7..37a8f612 100644 --- a/LDYOM/Scripts/scripts/Core/Languages/Portuguese.toml +++ b/LDYOM/Scripts/scripts/Core/Languages/Portuguese.toml @@ -44,6 +44,7 @@ variable = "Variável" global_variable = "Variável Global" objective = "Objetivo" char = "Personagem" +camera_path = "Caminho da câmera" [nodes.types.core] flow = "Fluxo" number = "Número" @@ -231,3 +232,10 @@ on_complete = "Ao completar" [nodes.core.stop_carrec_path_vehicle] title = "Parar caminho Carrec para veículo" + +[nodes.core.play_camera_path] +title = "Reproduzir caminho da câmera" +on_complete = "Ao concluir" + +[nodes.core.stop_camera_path] +title = "Parar caminho da câmera" diff --git a/LDYOM/Scripts/scripts/Core/Languages/Russian.toml b/LDYOM/Scripts/scripts/Core/Languages/Russian.toml index f41a3e74..7d228701 100644 --- a/LDYOM/Scripts/scripts/Core/Languages/Russian.toml +++ b/LDYOM/Scripts/scripts/Core/Languages/Russian.toml @@ -44,6 +44,7 @@ variable = "Переменная" global_variable = "Гл. переменная" objective = "Цель" char = "Персонаж" +camera_path = "Путь камеры" [nodes.types.core] flow = "Поток" @@ -243,4 +244,11 @@ title = "Запуск Carrec пути для ТС" on_complete = "При завершении" [nodes.core.stop_carrec_path_vehicle] -title = "Остановить Carrec пути для ТС" \ No newline at end of file +title = "Остановить Carrec пути для ТС" + +[nodes.core.play_camera_path] +title = "Воспроизвести путь камеры" +on_complete = "При завершении" + +[nodes.core.stop_camera_path] +title = "Остановить путь камеры" \ No newline at end of file diff --git a/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-play_camera_path.lua b/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-play_camera_path.lua new file mode 100644 index 00000000..d5f4551f --- /dev/null +++ b/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-play_camera_path.lua @@ -0,0 +1,300 @@ +local LDNodeEditorContext = require("ld.context") +local LDNodeEditor = require("ld.node_editor") +local nodesColors = require("ld.node_colors") +local nodesIcons = require("ld.node_icons") +local fa = require("libs.fa") + +---@class LDNodeEditorPlayCameraPathNode : LDNodeEditorNode +---@field sceneId integer +---@field entityUuid string +---@field startFadeOut boolean +---@field startFadeOutTime number +---@field endFadeIn boolean +---@field endFadeInTime number +---@field lockPlayerControl boolean + +local PLAY_CAMERA_PATH_NODE_TYPE = "core.play_camera_path" + +---@type LDNodeEditorNodeType +local playCameraPathNode = { + typeName = PLAY_CAMERA_PATH_NODE_TYPE, + category = "camera_path", + icon = nodesIcons["function"], + color = nodesColors["function"], + isCallable = true, + ---@param ctx LDNodeEditorContext + ---@param newNodeId integer + ---@param getPinId fun():integer + new = function(ctx, newNodeId, getPinId) + ---@type LDNodeEditorPlayCameraPathNode + local newNode = { + id = newNodeId, + nodeType = PLAY_CAMERA_PATH_NODE_TYPE, + buffer = "", + inputs = { + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.flow", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.bool", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.number", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.bool", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.number", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.bool", + } + }, + outputs = { + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Output, + type = "core.flow", + }, + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Output, + type = "core.flow", + } + }, + sceneId = 0, + entityUuid = "", + startFadeOut = false, + startFadeOutTime = 0, + endFadeIn = false, + endFadeInTime = 0, + lockPlayerControl = false + } + return newNode + end, + ---@param editor LDNodeEditor + ---@param ctx LDNodeEditorContext + ---@param node LDNodeEditorPlayCameraPathNode + ---@param builder BlueprintNodeBuilder + draw = function(editor, ctx, node, builder) + local fontScale = ImGui.GetFontSize() / 16; + + builder:Begin(NodeEditor.NodeId(node.id)); + LDNodeEditor.defaultHeader(editor, builder, node); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[1], ""); + + ImGui.BeginGroup(); + + local scene = ld.projectsService():getCurrentProject():getScenes()[node.sceneId]; + + ImGui.Text(ld.loc.get("scenes.scene")); + ImGui.SameLine(0, -1); + if ImGui.Button((scene ~= nil and scene:getName()) or "", ImVec2.new()) then + NodeEditor.Suspend(); + ImGui.OpenPopup("##sceneIdSelect", 0); + NodeEditor.Resume(); + end + + if ImGui.IsPopupOpen("##sceneIdSelect", 0) then + NodeEditor.Suspend(); + if ImGui.BeginPopup("##sceneIdSelect", 0) then + if ImGui.BeginListBox("##sceneIdSelectChild", ImVec2.new(200 * fontScale, 100 * fontScale)) then + for id, scene in pairs(ld.projectsService():getCurrentProject():getScenes()) do + if ImGui.Selectable(scene:getName(), node.id == id, 0, ImVec2.new()) then + node.sceneId = id; + ImGui.CloseCurrentPopup(); + end + end + ImGui.EndListBox(); + end + ImGui.EndPopup(); + end + NodeEditor.Resume(); + end + + local autoBindRequireFields = ld.settings.getBool("main.autoBindRequireFields"); + if autoBindRequireFields == nil then + autoBindRequireFields = true; + end + + if autoBindRequireFields then + if node.sceneId == 0 then + node.sceneId = ld.projectsService():getCurrentProject():getCurrentSceneIndex() + end + end + + if scene then + local camerPaths = scene:getCameraPaths(); + local indexCameraPath = ld.indexByUuid(camerPaths, node.entityUuid); + + ImGui.Text(ld.loc.get("entities.camera_path")); + ImGui.SameLine(0, -1); + if ImGui.Button((indexCameraPath ~= -1 and camerPaths[indexCameraPath + 1]:getName()) or "", ImVec2.new()) then + NodeEditor.Suspend(); + ImGui.OpenPopup("##entityUuidSelect", 0); + NodeEditor.Resume(); + end + + if autoBindRequireFields then + if node.entityUuid == "" and #camerPaths > 0 then + node.entityUuid = tostring(camerPaths[#camerPaths]:getUuid()); + end + end + + if ImGui.IsPopupOpen("##entityUuidSelect", 0) then + NodeEditor.Suspend(); + if ImGui.BeginPopup("##entityUuidSelect", 0) then + if ImGui.BeginListBox("##entityUuidSelectChild", ImVec2.new(200 * fontScale, 100 * fontScale)) then + for i, vehicle in ipairs(camerPaths) do + if ImGui.Selectable(vehicle:getName(), node.entityUuid == tostring(vehicle:getUuid()), 0, ImVec2.new()) then + node.entityUuid = tostring(vehicle:getUuid()); + ImGui.CloseCurrentPopup(); + end + end + ImGui.EndListBox(); + end + ImGui.EndPopup(); + end + NodeEditor.Resume(); + end + end + + ImGui.EndGroup(); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[2], ld.loc.get("cutscene_objective.start_fade_out"), function (value) + node.startFadeOut = editor.dataTypes[node.inputs[2].type].drawEditValue(node.startFadeOut, "##startFadeOutEdit", fontScale * 100) + end); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[3], ld.loc.get("cutscene_objective.fade_out_time"), function (value) + ImGui.BeginDisabled(not node.startFadeOut); + node.startFadeOutTime = editor.dataTypes[node.inputs[3].type].drawEditValue(node.startFadeOutTime, "##startFadeOutTimeEdit", fontScale * 100) + ImGui.EndDisabled(); + end); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[4], ld.loc.get("cutscene_objective.end_fade_in"), function (value) + node.endFadeIn = editor.dataTypes[node.inputs[4].type].drawEditValue(node.endFadeIn, "##endFadeInEdit", fontScale * 100) + end); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[5], ld.loc.get("cutscene_objective.fade_in_time"), function (value) + ImGui.BeginDisabled(not node.endFadeIn); + node.endFadeInTime = editor.dataTypes[node.inputs[5].type].drawEditValue(node.endFadeInTime, "##endFadeInTimeEdit", fontScale * 100) + ImGui.EndDisabled(); + end); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[6], ld.loc.get("cutscene_objective.lock_player_control"), function (value) + node.lockPlayerControl = editor.dataTypes[node.inputs[6].type].drawEditValue(node.lockPlayerControl, "##lockPlayerControlEdit", fontScale * 100) + end); + + LDNodeEditor.defaultOutput(editor, ctx, builder, node.outputs[1], ""); + LDNodeEditor.defaultOutput(editor, ctx, builder, node.outputs[2], ld.loc.get("nodes.core.play_camera_path.on_complete")); + + builder:End(); + end, + ---@param editor LDNodeEditor + ---@param context LDNodeEditorContext + ---@param node LDNodeEditorPlayCameraPathNode + ---@param inputValues any[] + run = function(editor, context, node, inputValues) + local startFadeOut = inputValues[2] or node.startFadeOut; + local startFadeOutTime = inputValues[3] or node.startFadeOutTime; + local endFadeIn = inputValues[4] or node.endFadeIn; + local endFadeInTime = inputValues[5] or node.endFadeInTime; + local lockPlayerControlVal = inputValues[6] or node.lockPlayerControl; + + if node.sceneId == 0 then + error("Scene is not set!") + end + + local scene = ld.projectsService():getCurrentProject():getScenes()[node.sceneId]; + local indexCameraPath = ld.indexByUuid(scene:getCameraPaths(), node.entityUuid); + if indexCameraPath == -1 then + error("Camera path is not set!") + end + local cameraPath = scene:getCameraPaths()[indexCameraPath + 1]; + + while ld.CameraMutex.isLocked() do + coroutine.yield(1) + end + + + local lockPlayerControl = lockPlayerControlVal and PlayerOp.isControlOn(0); + + if lockPlayerControl then + PlayerOp.setControl(0, false); + end + + HudOp.display(false); + HudOp.displayRadar(false); + + ld.projectPlayerService:getSceneTasklist():add_task(function () + (function () + local guard = ld.createCutsceneGuard(); + if startFadeOut then + CameraOp.doFade(startFadeOutTime * 1000, 1); + end + + ld.TheCameraExtend:setExtendMode(true); + + ld.TheCameraExtend:playCameraPath(cameraPath); + + local startTime = time.getSnTimeInMilliseconds(); + local fadeInDuration = endFadeInTime * 1000; + local duration = cameraPath:getTime() * 1000; + local endFadeInStarted = false; + print(startTime); + while ld.TheCameraExtend:isPlayingPath() do + if endFadeIn and not endFadeInStarted and duration - fadeInDuration <= time.getSnTimeInMilliseconds() - startTime then + CameraOp.doFade(fadeInDuration, 0); + endFadeInStarted = true; + end + coroutine.yield(1); + end + + ld.TheCameraExtend:stopCameraPath(); + ld.TheCameraExtend:setExtendMode(false); + + if lockPlayerControl then + PlayerOp.setControl(0, true); + end + + HudOp.display(true); + HudOp.displayRadar(true); + + ld.destroyCutsceneGuard(guard); + end)() + + for _, link in ipairs(context.__links) do + if link.outputId == node.outputs[2].id then + local nodeCallId = math.floor(link.inputId / 100) * 100 + LDNodeEditor.runNode(editor, context, context.nodes[nodeCallId]) + end + end + end); + + return {1, 0} + end +} + +return playCameraPathNode diff --git a/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-stop_camera_path.lua b/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-stop_camera_path.lua new file mode 100644 index 00000000..05f1af24 --- /dev/null +++ b/LDYOM/Scripts/scripts/Core/nodes/camera_path/core-stop_camera_path.lua @@ -0,0 +1,74 @@ +local LDNodeEditorContext = require("ld.context") +local LDNodeEditor = require("ld.node_editor") +local nodesColors = require("ld.node_colors") +local nodesIcons = require("ld.node_icons") +local fa = require("libs.fa") + +---@class LDNodeEditorStopCameraPathNode : LDNodeEditorNode + +local STOP_CAMERA_PATH_NODE_TYPE = "core.stop_camera_path" + +---@type LDNodeEditorNodeType +local stopCameraPathNode = { + typeName = STOP_CAMERA_PATH_NODE_TYPE, + category = "camera_path", + icon = nodesIcons["function"], + color = nodesColors["function"], + isCallable = true, + ---@param ctx LDNodeEditorContext + ---@param newNodeId integer + ---@param getPinId fun():integer + new = function(ctx, newNodeId, getPinId) + ---@type LDNodeEditorStopCameraPathNode + local newNode = { + id = newNodeId, + nodeType = STOP_CAMERA_PATH_NODE_TYPE, + buffer = "", + inputs = { + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Input, + type = "core.flow", + } + }, + outputs = { + { + id = getPinId(), + node = newNodeId, + kind = NodeEditorPinKind.Output, + type = "core.flow", + } + } + } + return newNode + end, + ---@param editor LDNodeEditor + ---@param ctx LDNodeEditorContext + ---@param node LDNodeEditorStopCameraPathNode + ---@param builder BlueprintNodeBuilder + draw = function(editor, ctx, node, builder) + local fontScale = ImGui.GetFontSize() / 16; + + builder:Begin(NodeEditor.NodeId(node.id)); + LDNodeEditor.defaultHeader(editor, builder, node); + + LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[1], ""); + + LDNodeEditor.defaultOutput(editor, ctx, builder, node.outputs[1], ""); + + builder:End(); + end, + ---@param editor LDNodeEditor + ---@param context LDNodeEditorContext + ---@param node LDNodeEditorStopCameraPathNode + ---@param inputValues any[] + run = function(editor, context, node, inputValues) + + ld.TheCameraExtend:stopCameraPath(); + + return {1} + end +} + +return stopCameraPathNode diff --git a/LDYOM/Scripts/scripts/Core/nodes/char/core-char_follow_path.lua b/LDYOM/Scripts/scripts/Core/nodes/char/core-char_follow_path.lua index 72e1dd22..8418f22c 100644 --- a/LDYOM/Scripts/scripts/Core/nodes/char/core-char_follow_path.lua +++ b/LDYOM/Scripts/scripts/Core/nodes/char/core-char_follow_path.lua @@ -241,7 +241,7 @@ local charFollowPathNode = { local execute = true local step = 1 local index = 1 - local startTime = time.snTimeInMilliseconds + local startTime = time.getSnTimeInMilliseconds() if node.startFromNearest then local x, y, z = CharOp.getCoordinates(ped) @@ -272,11 +272,11 @@ local charFollowPathNode = { TaskOp.goStraightToCoord(ped, x, y, z, moveState[moveType + 1], -1) - local timeCondition = pathType == 0 or time.snTimeInMilliseconds - startTime < executeTime * 1000 + local timeCondition = pathType == 0 or time.getSnTimeInMilliseconds() - startTime < executeTime * 1000 local reachDistance = moveType == 0 and 0.5 or 1 while CharOp.doesExist(ped) and distanceBetweenPoints(x, y, z, CharOp.getCoordinates(ped)) > reachDistance and timeCondition do - timeCondition = (pathType == 0) or (time.snTimeInMilliseconds - startTime < executeTime * 1000) + timeCondition = (pathType == 0) or (time.getSnTimeInMilliseconds() - startTime < executeTime * 1000) coroutine.yield(1) end diff --git a/LDYOM/Scripts/scripts/Core/nodes/global_variable/core-set_global_variable.lua b/LDYOM/Scripts/scripts/Core/nodes/global_variable/core-set_global_variable.lua index 851cafc9..c705e1da 100644 --- a/LDYOM/Scripts/scripts/Core/nodes/global_variable/core-set_global_variable.lua +++ b/LDYOM/Scripts/scripts/Core/nodes/global_variable/core-set_global_variable.lua @@ -148,7 +148,6 @@ local setVariableNode = { if var then LDNodeEditor.defaultInput(editor, ctx, builder, node.inputs[2], ld.loc.get("general.value"), function () local type = editor.dataTypes[node.inputs[2].type]; - print("type", node.varValue) node.varValue = type.drawEditValue(node.varValue, "##varValue", fontScale * 100); end); end diff --git a/source/LDYOM_R/Data/Actor.cpp b/source/LDYOM_R/Data/Actor.cpp index fadaab9b..9ea3d25e 100644 --- a/source/LDYOM_R/Data/Actor.cpp +++ b/source/LDYOM_R/Data/Actor.cpp @@ -39,7 +39,8 @@ void Actor::spawnProjectEntity() { Command(0, &g); Command(g, CPools::GetPedRef(this->projectPed_.value())); } - this->projectPed_.value()->m_fMaxHealth = max(this->projectPed_.value()->m_fMaxHealth, this->health); + this->projectPed_.value()->m_fMaxHealth = std::max(this->projectPed_.value()->m_fMaxHealth, + static_cast(this->health)); this->projectPed_.value()->m_fHealth = static_cast(this->health); updateLocation(); @@ -48,7 +49,7 @@ void Actor::spawnProjectEntity() { auto tasklist = ProjectPlayerService::getInstance().getSceneTasklist(); if (this->isShowHealthBarCounter()) { - const auto initialHealth = static_cast(static_cast(this->getHealth()) / static_cast(max( + const auto initialHealth = static_cast(static_cast(this->getHealth()) / static_cast(std::max( this->getHealth(), 100)) * 100.f); this->projectHealthBarCounter = CounterService::getInstance().addBarCounter( this->getHealthBarCounterText(), initialHealth); @@ -58,8 +59,8 @@ void Actor::spawnProjectEntity() { tasklist->add_task([](Actor *actor) -> ktwait { while (actor->getProjectPed().has_value()) { if (actor->getProjectHealthBarCounter().has_value()) { - const auto counterHealth = static_cast(actor->getProjectPed().value()->m_fHealth / max( - actor->getProjectPed().value()->m_fMaxHealth, 100) * 100.f); + const auto counterHealth = static_cast(actor->getProjectPed().value()->m_fHealth / std::max( + actor->getProjectPed().value()->m_fMaxHealth, 100.f) * 100.f); CounterService::getInstance().updateCounter(actor->getProjectHealthBarCounter().value(), counterHealth); } diff --git a/source/LDYOM_R/Data/CCameraExtend.cpp b/source/LDYOM_R/Data/CCameraExtend.cpp index 0b74a5f4..226e7885 100644 --- a/source/LDYOM_R/Data/CCameraExtend.cpp +++ b/source/LDYOM_R/Data/CCameraExtend.cpp @@ -3,7 +3,10 @@ #include #include +#include + #include "CMatrixUtils.h" +#include "CQuaternionUtils.h" #include "glm/gtx/quaternion.hpp" CCameraExtend TheCameraExtend = CCameraExtend( @@ -13,25 +16,88 @@ plugin::ThiscallEvent, plugin::PRIORITY_BEFORE, plugin::ArgPickN, void(CCamera *, char, char)> cameraProcessEvent; + +struct CachedQuaterninonData { + CQuaternion *from, *to; + float halftheta, sintheta_inv; +} quatData; + +auto tween = tweeny::from(0.f).to(1.f); + void CCameraExtend::Process() { + const auto deltaTime = static_cast(CTimer::m_snTimeInMilliseconds - CTimer::m_snPreviousTimeInMilliseconds) / + 1000.0f; if (extendMode) { - if (this->attachEntity != nullptr) { - const CMatrix *matrix = this->attachEntity->m_matrix; - - glm::quat q = quat_cast(CMatrixToGlmMat4(*matrix)); - auto multPos = q * glm::vec3(this->localMatrix.pos.x, this->localMatrix.pos.y, this->localMatrix.pos.z); - this->matrix.pos = matrix->pos + CVector(multPos.x, multPos.y, multPos.z); - auto rotated = quat_cast( - CMatrixToGlmMat4(*matrix) * CMatrixToGlmMat4(this->localMatrix)); - this->matrix.SetRotate({{rotated.x, rotated.y, rotated.z}, rotated.w}); - } - if (this->lookEntity != nullptr) { - CVector up = {0.0f, 0.0f, 1.0f}; - this->camera->SetCamPositionForFixedMode(&this->matrix.pos, &up); - this->camera->TakeControl(lookEntity, 15, 2, 1); - this->matrix.right = this->camera->m_mCameraMatrix.up; - this->matrix.up = this->camera->m_mCameraMatrix.right; - this->matrix.at = this->camera->m_mCameraMatrix.at; + if (this->playingPath) { + if (this->pathTimeProgress < this->playingPath->getTime()) { + const auto t = std::min(this->pathTimeProgress / this->playingPath->getTime(), 1.f); + const auto point = curvePoint(this->playingPath->getPositionCurve(), t); + this->matrix.pos = CVector(point.x, point.y, point.z); + + auto rotationsCurve = this->playingPath->getRotationsCurve(); + + CQuaternion interpolatedRot; + + if (this->playingPath->isCatmullRomRotations()) { + interpolatedRot = catmullRomSpline(rotationsCurve, t); + } else { + const float rotT = static_cast(rotationsCurve.size() - 1) * t; + auto &rotFrom = rotationsCurve.at(static_cast(rotT)); + auto &rotTo = rotationsCurve.at( + std::min(static_cast(rotT + 1), rotationsCurve.size() - 1)); + + if (quatData.from != &rotFrom || quatData.to != &rotTo) { + const float dot = glm::dot( + glm::quat(rotFrom.real, rotFrom.imag.x, rotFrom.imag.y, rotFrom.imag.z), + glm::quat(rotTo.real, rotTo.imag.x, rotTo.imag.y, rotTo.imag.z)); + if (dot > 0.99995f) { + quatData.halftheta = 0.0f; + quatData.sintheta_inv = 1.0f; + } else { + quatData.halftheta = acos(dot); + quatData.sintheta_inv = 1.0f / sin(quatData.halftheta); + } + quatData.from = &rotFrom; + quatData.to = &rotTo; + } + + tween = tween.via( + static_cast(this->playingPath->getRotationsEasing()[static_cast< + size_t>( + rotT)])); + tween = tween.during(static_cast(this->playingPath->getTime() * 1000.f)); + const float rotP = tween.seek(rotT - floor(rotT)); + + interpolatedRot.Slerp(rotFrom, rotTo, quatData.halftheta, quatData.sintheta_inv, rotP); + } + + + this->matrix.SetRotate(interpolatedRot); + + this->pathTimeProgress += deltaTime; + } else { + this->playingPath = nullptr; + this->pathTimeProgress = 0.0f; + } + } else { + if (this->attachEntity != nullptr) { + const CMatrix *matrix = this->attachEntity->m_matrix; + + glm::quat q = quat_cast(CMatrixToGlmMat4(*matrix)); + auto multPos = q * glm::vec3(this->localMatrix.pos.x, this->localMatrix.pos.y, this->localMatrix.pos.z); + this->matrix.pos = matrix->pos + CVector(multPos.x, multPos.y, multPos.z); + auto rotated = quat_cast( + CMatrixToGlmMat4(*matrix) * CMatrixToGlmMat4(this->localMatrix)); + this->matrix.SetRotate({{rotated.x, rotated.y, rotated.z}, rotated.w}); + } + if (this->lookEntity != nullptr) { + CVector up = {0.0f, 0.0f, 1.0f}; + this->camera->SetCamPositionForFixedMode(&this->matrix.pos, &up); + this->camera->TakeControl(lookEntity, 15, 2, 1); + this->matrix.right = this->camera->m_mCameraMatrix.up; + this->matrix.up = this->camera->m_mCameraMatrix.right; + this->matrix.at = this->camera->m_mCameraMatrix.at; + } } this->camera->m_mCameraMatrix.right = matrix.up; @@ -73,3 +139,31 @@ void CCameraExtend::attachToEntity(CEntity *attachEntity, CEntity *lookEntity, c this->localMatrix.SetRotate(rotationOffset); this->localMatrix.pos = posOffset; } + +bool CCameraExtend::playCameraPath(CameraPath *path) { + const bool isValid = path->isValid(); + if (isValid) { + this->playingPath = path; + this->pathTimeProgress = 0.0f; + ensureShortestPath(path->getRotationsCurve()); + } + return isValid; +} + +void CCameraExtend::stopCameraPath() { + if (this->playingPath) { + this->playingPath = nullptr; + this->pathTimeProgress = 0.0f; + } +} + +bool CCameraExtend::isPlayingPath() const { + return this->playingPath != nullptr && this->pathTimeProgress < this->playingPath->getTime(); +} + +float CCameraExtend::getPathProgress() const { + if (this->playingPath) { + return this->pathTimeProgress / this->playingPath->getTime(); + } + return 0.0f; +} diff --git a/source/LDYOM_R/Data/CCameraExtend.h b/source/LDYOM_R/Data/CCameraExtend.h index bd20287e..4aea1c77 100644 --- a/source/LDYOM_R/Data/CCameraExtend.h +++ b/source/LDYOM_R/Data/CCameraExtend.h @@ -1,6 +1,8 @@ #pragma once #include +#include "CameraPath.h" + class CCameraExtend { private: @@ -15,6 +17,8 @@ class CCameraExtend { CMatrix localMatrix; CEntity *attachEntity = nullptr; CEntity *lookEntity = nullptr; + CameraPath *playingPath = nullptr; + float pathTimeProgress = 0.0f; CCameraExtend(CCamera *camera); @@ -25,6 +29,10 @@ class CCameraExtend { void setExtendMode(bool mode); void attachToEntity(CEntity *attachEntity, CEntity *lookEntity, const CQuaternion &rotationOffset, const CVector &posOffset); + bool playCameraPath(CameraPath *path); + void stopCameraPath(); + bool isPlayingPath() const; + float getPathProgress() const; }; extern CCameraExtend TheCameraExtend; diff --git a/source/LDYOM_R/Data/CMatrixUtils.h b/source/LDYOM_R/Data/CMatrixUtils.h index e2286676..44ddb36b 100644 --- a/source/LDYOM_R/Data/CMatrixUtils.h +++ b/source/LDYOM_R/Data/CMatrixUtils.h @@ -3,6 +3,8 @@ #include #include #include +#include "CDraw.h" +#include "plugin.h" #ifndef CMatrixUtils_H #define CMatrixUtils_H @@ -59,4 +61,15 @@ inline CMatrix GlmMat4ToCMatrix(const glm::mat4 &mat) { return result; } +inline void CalcWorldCoors(RwV3d *screenCoords, RwV3d *worldCoords) { + const float invZ = 1.f / screenCoords->z; + screenCoords->x = screenCoords->x / static_cast(RsGlobal.maximumWidth) / invZ; + screenCoords->y = screenCoords->y / static_cast(RsGlobal.maximumHeight) / invZ; + + RwMatrix viewMatrixInverse; + RwMatrixInvert(&viewMatrixInverse, reinterpret_cast(&TheCamera.m_mViewMatrix)); + + *worldCoords = *RwV3dTransformPoint(worldCoords, screenCoords, &viewMatrixInverse); +} + #endif // CMatrixUtils_H diff --git a/source/LDYOM_R/Data/CQuaternionUtils.h b/source/LDYOM_R/Data/CQuaternionUtils.h new file mode 100644 index 00000000..d1a013bc --- /dev/null +++ b/source/LDYOM_R/Data/CQuaternionUtils.h @@ -0,0 +1,45 @@ +#pragma once +#include + +#include +#include +#include + +inline glm::quat CQuatToGlmQuat(const CQuaternion &quat) { + return {quat.real, quat.imag.x, quat.imag.y, quat.imag.z}; +} + +inline glm::quat catmullRom(const glm::quat &p0, const glm::quat &p1, const glm::quat &p2, const glm::quat &p3, + float t) { + const float t2 = t * t; + const float t3 = t2 * t; + + const float w0 = -0.5f * t3 + t2 - 0.5f * t; + const float w1 = 1.5f * t3 - 2.5f * t2 + 1.0f; + const float w2 = -1.5f * t3 + 2.0f * t2 + 0.5f * t; + const float w3 = 0.5f * t3 - 0.5f * t2; + + return p0 * w0 + p1 * w1 + p2 * w2 + p3 * w3; +} + +inline CQuaternion catmullRomSpline(const std::vector &q, float t) { + const int numPoints = q.size(); + const int p = static_cast(t * static_cast(numPoints - 1)); + t = t * static_cast(numPoints - 1) - static_cast(p); + + const glm::quat p0 = CQuatToGlmQuat(q[glm::clamp(p - 1, 0, numPoints - 1)]); + const glm::quat p1 = CQuatToGlmQuat(q[p]); + const glm::quat p2 = CQuatToGlmQuat(q[glm::clamp(p + 1, 0, numPoints - 1)]); + const glm::quat p3 = CQuatToGlmQuat(q[glm::clamp(p + 2, 0, numPoints - 1)]); + + auto quat = catmullRom(p0, p1, p2, p3, t); + return {{quat.x, quat.y, quat.z}, quat.w}; +} + +inline void ensureShortestPath(std::vector &quaternions) { + for (size_t i = 1; i < quaternions.size(); ++i) { + if (dot(CQuatToGlmQuat(quaternions[i - 1]), CQuatToGlmQuat(quaternions[i])) < 0) { + quaternions[i] *= -1; + } + } +} diff --git a/source/LDYOM_R/Data/CameraPath.cpp b/source/LDYOM_R/Data/CameraPath.cpp new file mode 100644 index 00000000..992b7a84 --- /dev/null +++ b/source/LDYOM_R/Data/CameraPath.cpp @@ -0,0 +1,36 @@ +#include "CameraPath.h" + +#include + +CameraPath::CameraPath(const char *name): uuid(boost::uuids::random_generator()()) { + this->name = name; + this->positionCurve.degree = 2; +} + +CameraPath CameraPath::copy() const { + CameraPath copy(*this); + copy.uuid = boost::uuids::random_generator()(); + copy.name += " (copy)"; + return copy; +} + +std::string& CameraPath::getName() { + return name; +} + +boost::uuids::uuid& CameraPath::getUuid() { + return uuid; +} + +tinynurbs::RationalCurve& CameraPath::getPositionCurve() { return positionCurve; } +bool& CameraPath::isCustomControlKnots() { return customControlKnots; } +std::vector& CameraPath::getRotationsCurve() { return rotationsCurve; } +std::vector& CameraPath::getRotationsEasing() { return rotationsEasing; } +float& CameraPath::getTime() { return time; } +bool& CameraPath::isCatmullRomRotations() { return catmullRomRotations; } + +bool CameraPath::isValid() const { + const bool isMoreTwoPoints = positionCurve.control_points.size() >= (catmullRomRotations ? 4 : 2); + const bool isValidPointsCount = positionCurve.control_points.size() >= positionCurve.degree + 1; + return curveIsValid(positionCurve) && isMoreTwoPoints && isValidPointsCount; +} diff --git a/source/LDYOM_R/Data/CameraPath.h b/source/LDYOM_R/Data/CameraPath.h new file mode 100644 index 00000000..cac5516b --- /dev/null +++ b/source/LDYOM_R/Data/CameraPath.h @@ -0,0 +1,78 @@ +#pragma once +#include +#include +#include + +#include "INameable.h" +#include "IUuidable.h" +#include "jsonUtils.h" +#include "rationalCurveSerialize.h" +#include "tinynurbs/tinynurbs.h" + +struct CameraPathPoint { + std::array pos{}; + CQuaternion rotate{}; +}; + + +class CameraPath : public INameable, public IUuidable { +private: + boost::uuids::uuid uuid; + + std::string name{}; + std::vector rotationsCurve; + std::vector rotationsEasing; + tinynurbs::RationalCurve positionCurve; + bool customControlKnots = false; + float time = 1.0f; + bool catmullRomRotations = true; + +public: + CameraPath() = default; + CameraPath(const char *name); + CameraPath(const CameraPath &other) = default; + CameraPath& operator=(const CameraPath &other) = default; + + CameraPath copy() const; + + std::string& getName() override; + boost::uuids::uuid& getUuid() override; + + tinynurbs::RationalCurve& getPositionCurve(); + bool& isCustomControlKnots(); + std::vector& getRotationsCurve(); + std::vector& getRotationsEasing(); + float& getTime(); + bool& isCatmullRomRotations(); + + bool isValid() const; +}; + +NLOHMANN_JSON_NAMESPACE_BEGIN + template <> + struct adl_serializer { + static void to_json(json &j, const CameraPath &obj) { + auto &a = const_cast(obj); + j["uuid"] = a.getUuid(); + j["name"] = a.getName(); + j["rotationsCurve"] = a.getRotationsCurve(); + j["positionCurve"] = a.getPositionCurve(); + j["customControlKnots"] = a.isCustomControlKnots(); + j["time"] = a.getTime(); + j["rotationsEasing"] = a.getRotationsEasing(); + j["catmullRomRotations"] = a.isCatmullRomRotations(); + } + + static void from_json(const json &j, CameraPath &obj) { + j.at("uuid").get_to(obj.getUuid()); + j.at("name").get_to(obj.getName()); + j.at("rotationsCurve").get_to(obj.getRotationsCurve()); + j.at("positionCurve").get_to(obj.getPositionCurve()); + j.at("customControlKnots").get_to(obj.isCustomControlKnots()); + j.at("time").get_to(obj.getTime()); + j.at("rotationsEasing").get_to(obj.getRotationsEasing()); + j.at("catmullRomRotations").get_to(obj.isCatmullRomRotations()); + } + }; + +NLOHMANN_JSON_NAMESPACE_END diff --git a/source/LDYOM_R/Data/CutsceneObjective.cpp b/source/LDYOM_R/Data/CutsceneObjective.cpp index 6b83a504..8f93bb18 100644 --- a/source/LDYOM_R/Data/CutsceneObjective.cpp +++ b/source/LDYOM_R/Data/CutsceneObjective.cpp @@ -23,7 +23,6 @@ #include "ProjectsService.h" #include "Settings.h" #include "strUtils.h" -#include "tweeny.h" #include "utils.h" #include "../Windows/utilsRender.h" diff --git a/source/LDYOM_R/Data/GoToSceneObjective.cpp b/source/LDYOM_R/Data/GoToSceneObjective.cpp index 57a1fa1d..49de9991 100644 --- a/source/LDYOM_R/Data/GoToSceneObjective.cpp +++ b/source/LDYOM_R/Data/GoToSceneObjective.cpp @@ -227,7 +227,7 @@ void GoToSceneObjective::draw(Localization &local, std::vector &lis if (scenes.contains(this->sceneId_)) { const auto &objectives = ProjectsService::getInstance().getCurrentProject().getScenes().at(this->sceneId_)-> getObjectives(); - this->startObjective_ = max(min(this->startObjective_, objectives.size() - 1), 0); + this->startObjective_ = std::max(std::min(this->startObjective_, static_cast(objectives.size() - 1)), 0); const auto previewValue = objectives.empty() ? "" : objectives.at(this->startObjective_)->getName(); if (ImGui::BeginCombo(local.get("objective.title").c_str(), previewValue.c_str())) { for (int i = 0; i < objectives.size(); ++i) { diff --git a/source/LDYOM_R/Data/PlayCameraPath.cpp b/source/LDYOM_R/Data/PlayCameraPath.cpp new file mode 100644 index 00000000..475231b2 --- /dev/null +++ b/source/LDYOM_R/Data/PlayCameraPath.cpp @@ -0,0 +1,140 @@ +#include "PlayCameraPath.h" + +#include +#include +#include +#include "imgui.h" +#include "imgui_stdlib.h" + +#include "CCameraExtend.h" +#include "CutsceneMutex.h" +#include "ProjectsService.h" +#include "Settings.h" +#include "utilsRender.h" + +PlayCameraPathObjective::PlayCameraPathObjective(void *_new) : BaseObjective(nullptr) {} + +void PlayCameraPathObjective::draw(Localization &local, std::vector &listOverlay) { + const auto &cameraPaths = ProjectsService::getInstance().getCurrentProject().getCurrentScene()->getCameraPaths(); + const int indexCameraPath = utils::indexByUuid(cameraPaths, this->cameraPathUuid_); + + IncorrectHighlight(indexCameraPath == -1, [&] { + utils::Combo(local.get("entities.camera_path").c_str(), &this->cameraPathUuid_, indexCameraPath, + cameraPaths.size(), + [&cameraPaths](const int i) { + return std::ref(cameraPaths.at(i)->getName()); + }, [&cameraPaths](const int i) { + return cameraPaths.at(i)->getUuid(); + }); + }); + + if (Settings::getInstance().get("main.autoBindRequireFields").value_or(true)) { + if (indexCameraPath == -1) { + if (!cameraPaths.empty()) { + this->cameraPathUuid_ = cameraPaths.back()->getUuid(); + } + } + } + + ImGui::Separator(); + + ImGui::InputText(local.get("general.text").c_str(), &this->text); + ImGui::DragFloat(local.get("general.time").c_str(), &this->textTime_, 0.001f); + + ImGui::Separator(); + + utils::ToggleButton(local.get("cutscene_objective.start_fade_out").c_str(), &this->startFadeOut); + ImGui::BeginDisabled(!this->startFadeOut); + ImGui::DragFloat(local.get("cutscene_objective.fade_out_time").c_str(), &this->startFadeOutTime, 0.01f); + ImGui::EndDisabled(); + + utils::ToggleButton(local.get("cutscene_objective.end_fade_in").c_str(), &this->endFadeIn); + ImGui::BeginDisabled(!this->endFadeIn); + ImGui::DragFloat(local.get("cutscene_objective.fade_in_time").c_str(), &this->endFadeInTime, 0.01f); + ImGui::EndDisabled(); + + ImGui::Separator(); + + utils::ToggleButton(local.get("cutscene_objective.lock_player_control").c_str(), &this->lockPlayerControl_); + utils::ToggleButton(local.get("cutscene_objective.asynchronous").c_str(), &this->async_); +} + +ktwait PlayCameraPathObjective::execute(Scene *scene, Result &result, ktcoro_tasklist &tasklist) { + using namespace plugin; + + const auto &cameraPaths = scene->getCameraPaths(); + const int indexCameraPath = utils::indexByUuid(cameraPaths, this->cameraPathUuid_); + + if (indexCameraPath == -1) { + setObjectiveError(result, *this, NotSelected, "The camera path for the objective is not selected."); + co_return; + } + + const auto &cameraPath = cameraPaths.at(indexCameraPath).get(); + + while (CutsceneMutex::isLocked()) { + co_await 1; + } + + + bool lockPlayerControl = this->lockPlayerControl_ && Command(0); + + if (lockPlayerControl) + Command(0, false); + + auto cp1251Text = utf8ToCp1251(this->text); + gxtEncode(cp1251Text); + this->gameText = cp1251Text; + + CMessages::AddMessage(this->gameText.data(), static_cast(this->textTime_ * 1000.0f), 0, false); + + CTheScripts::bDisplayHud = false; + CHud::bScriptDontDisplayRadar = true; + + static auto task = [](PlayCameraPathObjective *playCameraPathObjective, CameraPath *cameraPath, + bool lockPlayerControl) -> ktwait { + CutsceneMutexGuard guard; + + if (playCameraPathObjective->isStartFadeOut()) + plugin::Command( + static_cast(playCameraPathObjective->getStartFadeOutTime() * 1000.f), 1); + + TheCameraExtend.setExtendMode(true); + + TheCameraExtend.playCameraPath(cameraPath); + + const std::chrono::milliseconds + duration(static_cast(cameraPath->getTime() * 1000.f)); + if (playCameraPathObjective->isEndFadeIn()) { + const std::chrono::milliseconds fadeInDuration( + static_cast(playCameraPathObjective->getEndFadeInTime() * 1000.f)); + co_await(duration - fadeInDuration); + plugin::Command(static_cast(fadeInDuration.count()), 0); + co_await fadeInDuration; + } else { + co_await duration; + } + + TheCameraExtend.stopCameraPath(); + TheCameraExtend.setExtendMode(false); + + if (lockPlayerControl) + Command(0, true); + }; + + if (this->async_) + tasklist.add_task(task, this, cameraPath, lockPlayerControl); + else + co_await task(this, cameraPath, lockPlayerControl); +} + +boost::uuids::uuid& PlayCameraPathObjective::getCameraPathUuid() { return cameraPathUuid_; } +std::string& PlayCameraPathObjective::getText() { return text; } +float& PlayCameraPathObjective::getTextTime() { return textTime_; } +bool& PlayCameraPathObjective::isAsync() { return async_; } +bool& PlayCameraPathObjective::isStartFadeOut() { return startFadeOut; } +float& PlayCameraPathObjective::getStartFadeOutTime() { return startFadeOutTime; } +bool& PlayCameraPathObjective::isEndFadeIn() { return endFadeIn; } +float& PlayCameraPathObjective::getEndFadeInTime() { return endFadeInTime; } +bool& PlayCameraPathObjective::isLockPlayerControl() { return lockPlayerControl_; } +std::string& PlayCameraPathObjective::getGameText() { return gameText; } diff --git a/source/LDYOM_R/Data/PlayCameraPath.h b/source/LDYOM_R/Data/PlayCameraPath.h new file mode 100644 index 00000000..d96d743a --- /dev/null +++ b/source/LDYOM_R/Data/PlayCameraPath.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include + +#include "jsonUtils.h" +#include "WorldObjective.h" + + +class PlayCameraPathObjective final : virtual public WorldObjective { +private: + boost::uuids::uuid cameraPathUuid_{}; + std::string text; + float textTime_ = 1.f; + bool async_ = false; + bool startFadeOut = false; + float startFadeOutTime = 1.f; + bool endFadeIn = false; + float endFadeInTime = 1.f; + bool lockPlayerControl_ = true; + + std::string gameText; + +public: + PlayCameraPathObjective() = default; + explicit PlayCameraPathObjective(void *_new); + ~PlayCameraPathObjective() override = default; + + int getTypeCategory() override { + return 10; + } + + void draw(Localization &local, std::vector &listOverlay) override; + ktwait execute(Scene *scene, Result &result, ktcoro_tasklist &tasklist) override; + + boost::uuids::uuid& getCameraPathUuid(); + std::string& getText(); + float& getTextTime(); + bool& isAsync(); + bool& isStartFadeOut(); + float& getStartFadeOutTime(); + bool& isEndFadeIn(); + float& getEndFadeInTime(); + bool& isLockPlayerControl(); + std::string& getGameText(); +}; + +NLOHMANN_JSON_NAMESPACE_BEGIN + template <> + struct adl_serializer { + static void to_json(json &j, const PlayCameraPathObjective &obj) { + auto &worldObjective = static_cast(obj); + adl_serializer::to_json(j, worldObjective); + auto &a = const_cast(obj); + j["cameraPathUuid"] = a.getCameraPathUuid(); + j["text"] = a.getText(); + j["textTime"] = a.getTextTime(); + j["async"] = a.isAsync(); + j["startFadeOut"] = a.isStartFadeOut(); + j["startFadeOutTime"] = a.getStartFadeOutTime(); + j["endFadeIn"] = a.isEndFadeIn(); + j["endFadeInTime"] = a.getEndFadeInTime(); + j["lockPlayerControl"] = a.isLockPlayerControl(); + } + + static void from_json(const json &j, PlayCameraPathObjective &obj) { + auto &worldObjective = static_cast(obj); + j.get_to(worldObjective); + j.at("cameraPathUuid").get_to(obj.getCameraPathUuid()); + j.at("text").get_to(obj.getText()); + j.at("textTime").get_to(obj.getTextTime()); + j.at("async").get_to(obj.isAsync()); + j.at("startFadeOut").get_to(obj.isStartFadeOut()); + j.at("startFadeOutTime").get_to(obj.getStartFadeOutTime()); + j.at("endFadeIn").get_to(obj.isEndFadeIn()); + j.at("endFadeInTime").get_to(obj.getEndFadeInTime()); + j.at("lockPlayerControl").get_to(obj.isLockPlayerControl()); + } + }; + +NLOHMANN_JSON_NAMESPACE_END diff --git a/source/LDYOM_R/Data/Scene.cpp b/source/LDYOM_R/Data/Scene.cpp index bb8dda16..3192d7f1 100644 --- a/source/LDYOM_R/Data/Scene.cpp +++ b/source/LDYOM_R/Data/Scene.cpp @@ -120,6 +120,8 @@ std::vector>& Scene::getCheckpoints() { return checkpoints_; } +std::vector>& Scene::getCameraPaths() { return cameraPaths_; } + SceneSettings& Scene::getSceneSettings() { return sceneSettings_; } @@ -218,6 +220,12 @@ void Scene::createNewCheckpoint() { this->checkpoints_.back()->spawnEditorCheckpoint(); } +void Scene::createNewCameraPath() { + const auto defaultName = fmt::format("{} #{}", Localization::getInstance().get("entities.camera_path"), + this->cameraPaths_.size()); + this->cameraPaths_.emplace_back(std::make_unique(defaultName.c_str())); +} + void Scene::createNewActorFrom(Actor &actor) { this->actors_.emplace_back(std::make_unique(actor.copy())); } @@ -258,6 +266,10 @@ void Scene::createNewCheckpointFrom(Checkpoint &checkpoint) { this->checkpoints_.emplace_back(std::make_unique(checkpoint.copy())); } +void Scene::createNewCameraPathFrom(CameraPath &cameraPath) { + this->cameraPaths_.emplace_back(std::make_unique(cameraPath.copy())); +} + void Scene::updateEditorObjectsCollision() const { for (auto &object : this->objects_) { object->spawnEditorObject(); diff --git a/source/LDYOM_R/Data/Scene.h b/source/LDYOM_R/Data/Scene.h index d97e5438..8a2e5af4 100644 --- a/source/LDYOM_R/Data/Scene.h +++ b/source/LDYOM_R/Data/Scene.h @@ -6,6 +6,7 @@ #include "Audio.h" #include "BaseObjective.h" +#include "CameraPath.h" #include "Checkpoint.h" #include "CheckpointObjective.h" #include "Object.h" @@ -35,6 +36,7 @@ class Scene final : public INameable { std::vector> audio_; std::vector> visualEffects_; std::vector> checkpoints_; + std::vector> cameraPaths_; SceneSettings sceneSettings_; bool toggleSceneSettings_ = true; @@ -52,6 +54,7 @@ class Scene final : public INameable { std::vector> pyrotechnics, std::vector> audio, std::vector> visualEffects, std::vector> checkpoints, + std::vector> cameraPaths, SceneSettings sceneSettings, bool toggleSceneSettings) : name(std::move(name)), id_(id), @@ -66,6 +69,7 @@ class Scene final : public INameable { audio_(std::move(audio)), visualEffects_(std::move(visualEffects)), checkpoints_(std::move(checkpoints)), + cameraPaths_(std::move(cameraPaths)), sceneSettings_(std::move(sceneSettings)), toggleSceneSettings_(toggleSceneSettings) {} @@ -87,6 +91,7 @@ class Scene final : public INameable { std::vector>& getAudio(); std::vector>& getVisualEffects(); std::vector>& getCheckpoints(); + std::vector>& getCameraPaths(); SceneSettings& getSceneSettings(); bool& isToggleSceneSettings(); @@ -103,6 +108,7 @@ class Scene final : public INameable { void createNewAudio(); void createNewVisualEffect(); void createNewCheckpoint(); + void createNewCameraPath(); template >> @@ -117,6 +123,7 @@ class Scene final : public INameable { void createNewAudioFrom(Audio &a); void createNewVisualEffectFrom(VisualEffect &visualEffect); void createNewCheckpointFrom(Checkpoint &checkpoint); + void createNewCameraPathFrom(CameraPath &cameraPath); void updateEditorObjectsCollision() const; diff --git a/source/LDYOM_R/Data/glmSerialization.h b/source/LDYOM_R/Data/glmSerialization.h new file mode 100644 index 00000000..73a66507 --- /dev/null +++ b/source/LDYOM_R/Data/glmSerialization.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#include "glm/vec3.hpp" + +NLOHMANN_JSON_NAMESPACE_BEGIN + template <> + struct adl_serializer { + static void to_json(json &j, const glm::vec3 &obj) { + j["x"] = obj.x; + j["y"] = obj.y; + j["z"] = obj.z; + } + + static void from_json(const json &j, glm::vec3 &obj) { + j.at("x").get_to(obj.x); + j.at("y").get_to(obj.y); + j.at("z").get_to(obj.z); + } + }; + +NLOHMANN_JSON_NAMESPACE_END diff --git a/source/LDYOM_R/Data/rationalCurveSerialize.h b/source/LDYOM_R/Data/rationalCurveSerialize.h new file mode 100644 index 00000000..58d6fbed --- /dev/null +++ b/source/LDYOM_R/Data/rationalCurveSerialize.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include "glmSerialization.h" +#include "tinynurbs/core/curve.h" + +NLOHMANN_JSON_NAMESPACE_BEGIN + template + struct adl_serializer> { + static void to_json(json &j, const tinynurbs::RationalCurve &obj) { + j["degree"] = obj.degree; + j["knots"] = obj.knots; + j["control_points"] = obj.control_points; + j["weights"] = obj.weights; + } + + static void from_json(const json &j, tinynurbs::RationalCurve &obj) { + j.at("degree").get_to(obj.degree); + j.at("knots").get_to(obj.knots); + j.at("control_points").get_to(obj.control_points); + j.at("weights").get_to(obj.weights); + } + }; + +NLOHMANN_JSON_NAMESPACE_END diff --git a/source/LDYOM_R/LDYOM_R.vcxproj b/source/LDYOM_R/LDYOM_R.vcxproj index 17346794..4b404da7 100644 --- a/source/LDYOM_R/LDYOM_R.vcxproj +++ b/source/LDYOM_R/LDYOM_R.vcxproj @@ -174,6 +174,7 @@ + @@ -206,6 +207,7 @@ + @@ -289,6 +291,8 @@ + + @@ -362,6 +366,7 @@ + @@ -412,6 +417,7 @@ + @@ -419,6 +425,7 @@ + @@ -435,6 +442,7 @@ + @@ -452,8 +460,10 @@ + + @@ -504,6 +514,15 @@ + + + + + + + + + @@ -875,6 +894,7 @@ + diff --git a/source/LDYOM_R/LDYOM_R.vcxproj.filters b/source/LDYOM_R/LDYOM_R.vcxproj.filters index c231c734..ab197663 100644 --- a/source/LDYOM_R/LDYOM_R.vcxproj.filters +++ b/source/LDYOM_R/LDYOM_R.vcxproj.filters @@ -464,6 +464,18 @@ {62e63cd8-e2f5-4ef0-8458-8f93b024db89} + + {3889d55d-6c7e-4cba-825e-52907ca0d172} + + + {784d9245-4bf8-4bf3-908b-5938dd049789} + + + {4454e8a3-5127-4c70-ae73-0adc20013767} + + + {abeadb77-c4b1-462d-9ad3-67622f03a2c4} + @@ -1170,6 +1182,21 @@ Data\CCameraExtend + + Data\СameraPath + + + Windows\CameraPathsWindow + + + Data\Objectives\World\PlayCameraPath + + + Lua\Wrappers\LdyomWrappers\Data + + + Lua\Wrappers\LdyomWrappers + @@ -2709,6 +2736,51 @@ Data + + Data\СameraPath + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Utils\tinynurbs + + + Windows\CameraPathsWindow + + + Data + + + Data + + + Data + + + Data\Objectives\World\PlayCameraPath + diff --git a/source/LDYOM_R/Services/WindowsRenderService.cpp b/source/LDYOM_R/Services/WindowsRenderService.cpp index da887725..a57310c6 100644 --- a/source/LDYOM_R/Services/WindowsRenderService.cpp +++ b/source/LDYOM_R/Services/WindowsRenderService.cpp @@ -6,6 +6,7 @@ #include "AbstractWindow.h" #include "ActorsWindow.h" #include "AudioWindow.h" +#include "CameraPathsWindow.h" #include "CarrecPathsWindow.h" #include "CheckpointsWindow.h" #include "ConsoleWindow.h" @@ -107,6 +108,8 @@ void addWindows() { std::make_unique()); windows.emplace_back( std::make_unique()); + windows.emplace_back( + std::make_unique()); Windows::WindowsRenderService::getInstance().addRender("showEntitiesName", [] { if (!ProjectPlayerService::getInstance().isProjectRunning()) { if (Settings::getInstance().get("main.showEntitiesName").value_or(false)) diff --git a/source/LDYOM_R/Windows/CameraPathsWindow.cpp b/source/LDYOM_R/Windows/CameraPathsWindow.cpp new file mode 100644 index 00000000..9682fe78 --- /dev/null +++ b/source/LDYOM_R/Windows/CameraPathsWindow.cpp @@ -0,0 +1,424 @@ +#include "CameraPathsWindow.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include +#include +#include +#include +#include "EditByPlayerService.h" +#include "fa.h" +#include "imgui_internal.h" +#include "Settings.h" +#include "utilsRender.h" +#include "../Data/CCameraExtend.h" +#include "../Data/CMatrixUtils.h" +#include "fmt/core.h" +#include "Localization/Localization.h" + +using namespace plugin; + +std::string Windows::CameraPathsWindow::getNameList() { + return fmt::format("{} {}", ICON_FA_CAMERA_MOVIE, Localization::getInstance().get("entities.camera_paths")); +} + +std::string Windows::CameraPathsWindow::getNameOption() { + return fmt::format("{} {}", ICON_FA_CAMERA_MOVIE, Localization::getInstance().get("entities.camera_path")); +} + +int Windows::CameraPathsWindow::getListSize() { + return static_cast(ProjectsService::getInstance() + .getCurrentProject() + .getCurrentScene() + ->getCameraPaths() + .size()); +} + +void Windows::CameraPathsWindow::createNewElement() { + ProjectsService::getInstance().getCurrentProject().getCurrentScene()->createNewCameraPath(); +} + +void Windows::CameraPathsWindow::createNewElementFrom(int i) { + const auto currentScene = ProjectsService::getInstance().getCurrentProject().getCurrentScene(); + const auto &cameraPath = currentScene->getCameraPaths().at(i); + currentScene->createNewCameraPathFrom(*cameraPath); + currentScene->getObjects().back()->spawnEditorObject(); +} + +std::string& Windows::CameraPathsWindow::getElementName(int i) { + return ProjectsService::getInstance().getCurrentProject().getCurrentScene()->getCameraPaths().at(i)->getName(); +} + +void Windows::CameraPathsWindow::deleteElement(int i) { + const auto begin = ProjectsService::getInstance().getCurrentProject().getCurrentScene()->getCameraPaths().begin(); + ProjectsService::getInstance().getCurrentProject().getCurrentScene()->getCameraPaths().erase(begin + i); + this->currentElement--; +} + +enum EditorMouseMode { + NONE, + HOVER, + DRAG, +}; + +extern float speedCameraMultiplier; + +void Windows::CameraPathsWindow::drawOptions() { + auto &local = Localization::getInstance(); + const auto fontScale = ImGui::GetFontSize() / 16.0f; + + static auto mouseMode = NONE; + static auto currentMousePoint = -1; + static auto previewCurrentPoint = false; + + auto angles = degrees(eulerAngles(quat_cast(CMatrixToGlmMat4(TheCameraExtend.matrix)))); + + const auto &cameraPath = ProjectsService::getInstance().getCurrentProject().getCurrentScene()->getCameraPaths().at( + currentElement); + auto &positionCurve = cameraPath->getPositionCurve(); + bool recalcKnots = false; + + if (ImGui::DragFloat3(local.get("general.angle_rotation").c_str(), reinterpret_cast(&angles), 0.1, -180.f, + 180.f, "%.3f deg")) { + glm::quat quat(radians(angles)); + TheCameraExtend.matrix.SetRotate({{quat.x, quat.y, quat.z}, quat.w}); + if (previewCurrentPoint) { + cameraPath->getRotationsCurve()[currentPoint] = {{quat.x, quat.y, quat.z}, quat.w}; + } + } + + ImGui::DragFloat3(local.get("general.position").c_str(), reinterpret_cast(&TheCameraExtend.matrix.pos), + 0.1f); + + static auto minDegree = 1; + static auto maxDegree = 10; + if (ImGui::DragScalar(local.get("camera_paths.degree").c_str(), ImGuiDataType_U32, &positionCurve.degree, 0.5, + &minDegree, &maxDegree)) { + recalcKnots = true; + } + + auto editControlPointFunc = [&](int i) { + if (ImGui::MenuItem(local.get("general.delete").c_str())) { + positionCurve.control_points.erase(positionCurve.control_points.begin() + i); + positionCurve.weights.erase(positionCurve.weights.begin() + i); + cameraPath->getRotationsCurve().erase(cameraPath->getRotationsCurve().begin() + i); + cameraPath->getRotationsEasing().erase(cameraPath->getRotationsEasing().begin() + i); + recalcKnots = true; + currentPoint = -1; + } + const bool isLastPoint = i == positionCurve.control_points.size() - 1; + if (ImGui::MenuItem(local.get("general.duplicate").c_str(), nullptr, false, !isLastPoint)) { + const auto point = positionCurve.control_points[i]; + const auto nextPoint = positionCurve.control_points[i + 1]; + positionCurve.control_points.insert(positionCurve.control_points.begin() + i + 1, + (point + nextPoint) / 2.f); + positionCurve.weights.insert(positionCurve.weights.begin() + i + 1, + (positionCurve.weights[i] + positionCurve.weights[i + 1]) / 2.f); + CQuaternion newQuat; + newQuat.Slerp(cameraPath->getRotationsCurve()[i], cameraPath->getRotationsCurve()[i + 1], 0.5f); + cameraPath->getRotationsCurve().insert(cameraPath->getRotationsCurve().begin() + i + 1, + newQuat); + cameraPath->getRotationsEasing().insert(cameraPath->getRotationsEasing().begin() + i + 1, + cameraPath->getRotationsEasing()[i]); + recalcKnots = true; + } + }; + + ImGui::DragFloat(local.get("general.time").c_str(), &cameraPath->getTime(), 0.001f); + + + ImGui::Text(local.get("camera_paths.control_points").c_str()); + if (ImGui::BeginListBox("##controlPoints", ImVec2(fontScale * 300, 0))) { + for (int i = 0; i < positionCurve.control_points.size(); ++i) { + if (currentMousePoint == i) { + ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered)); + } + + auto &point = positionCurve.control_points[i]; + auto name = fmt::format("{}, {}, {}", point.x, point.y, point.z); + if (ImGui::Selectable(name.c_str(), currentPoint == i || currentMousePoint == i)) { + currentPoint = i; + } + + if (currentMousePoint == i) { + ImGui::PopStyleColor(); + } + + if (ImGui::BeginPopupContextItem(fmt::format("##controlPoint{}", i).c_str())) { + editControlPointFunc(i); + ImGui::EndPopup(); + } + } + + ImGui::EndListBox(); + } + + if (ImGui::Button(local.get("camera_paths.add_control_point").c_str())) { + positionCurve.control_points.emplace_back(reinterpret_cast(TheCameraExtend.matrix.pos)); + positionCurve.weights.emplace_back(1.f); + auto q = quat_cast(CMatrixToGlmMat4(TheCameraExtend.matrix)); + cameraPath->getRotationsCurve().push_back(CQuaternion{{q.x, q.y, q.z}, q.w}); + cameraPath->getRotationsEasing().push_back(17); + recalcKnots = true; + } + + ImGui::SameLine(); + + ImGui::BeginDisabled(currentPoint == -1); + if (ImGui::Button(local.get("camera_paths.update_control_point").c_str())) { + positionCurve.control_points[currentPoint] = reinterpret_cast(TheCameraExtend.matrix.pos); + auto q = quat_cast(CMatrixToGlmMat4(TheCameraExtend.matrix)); + cameraPath->getRotationsCurve()[currentPoint] = {{q.x, q.y, q.z}, q.w}; + recalcKnots = true; + } + ImGui::EndDisabled(); + + utils::ToggleButton(local.get("camera_paths.catmull_rom_rotations").c_str(), + &cameraPath->isCatmullRomRotations()); + + if (currentPoint != -1) { + ImGui::DragFloat(local.get("camera_paths.weight").c_str(), &positionCurve.weights[currentPoint], 0.01f, 0, 0); + ImGui::BeginDisabled(cameraPath->isCatmullRomRotations()); + EasingCombo(local.get("camera_paths.rotation_easing").c_str(), &cameraPath->getRotationsEasing()[currentPoint]); + ImGui::EndDisabled(); + } + + + ///////////////////////////////////////// + + /*std::string knotsValues; + for (auto knot : positionCurve.knots) { + knotsValues += fmt::format("{:.2f} ", knot); + } + ImGui::Text(knotsValues.c_str());*/ + + utils::ToggleButton(local.get("camera_paths.custom_control_knots").c_str(), + &cameraPath->isCustomControlKnots()); + + if (cameraPath->isCustomControlKnots()) { + ImGui::BeginChild("##knots", ImVec2(0, 100 * fontScale), ImGuiChildFlags_Border); + for (int i = 0; i < positionCurve.knots.size(); ++i) { + if (ImGui::DragFloat(fmt::format("##knots{}", i).c_str(), &positionCurve.knots[i], 0.01f, 0.f, 1.f)) { + for (int j = 0; j < i; ++j) { + if (positionCurve.knots[j] > positionCurve.knots[i]) { + positionCurve.knots[j] = positionCurve.knots[i]; + } + } + for (int j = i + 1; j < positionCurve.knots.size(); ++j) { + if (positionCurve.knots[j] < positionCurve.knots[i]) { + positionCurve.knots[j] = positionCurve.knots[i]; + } + } + } + } + ImGui::EndChild(); + } + + utils::ToggleButton(local.get("camera_paths.preview_current_point").c_str(), &previewCurrentPoint); + + if (ImGui::Button(local.get("camera_paths.play").c_str())) { + TheCameraExtend.playCameraPath(cameraPath.get()); + } + + + if (mouseMode == HOVER) { + currentMousePoint = -1; + mouseMode = NONE; + } + + // draw curve control points + for (int i = 0; i < positionCurve.control_points.size(); ++i) { + auto &point = positionCurve.control_points[i]; + ImVec2 screenPos; + if (utils::getScreenPositionFromGamePosition(CVector(point.x, point.y, point.z), screenPos)) { + if (i == currentPoint) { + ImGui::GetBackgroundDrawList()->AddCircleFilled(screenPos, 5.f, IM_COL32(255, 255, 0, 255)); + } else { + ImGui::GetBackgroundDrawList()->AddCircleFilled(screenPos, 5.f, IM_COL32(255, 255, 255, 255)); + } + //hover + if (mouseMode == NONE && + ImGui::IsMouseHoveringRect(screenPos - ImVec2(5, 5), screenPos + ImVec2(5, 5), false)) { + currentMousePoint = i; + mouseMode = HOVER; + + ImGui::BeginTooltip(); + ImGui::Text("%d", i); + ImGui::EndTooltip(); + } + + if (i > 0) { + const auto lastPoint = positionCurve.control_points[i - 1]; + ImVec2 lastScreenPos; + if (utils::getScreenPositionFromGamePosition(CVector(lastPoint.x, lastPoint.y, lastPoint.z), + lastScreenPos)) { + ImGui::GetBackgroundDrawList()-> + AddLine(screenPos, lastScreenPos, IM_COL32(255, 255, 255, 100), 1.f); + } + } + } + } + + switch (mouseMode) { + case HOVER: { + if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + currentPoint = currentMousePoint; + mouseMode = DRAG; + } else if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { + currentPoint = currentMousePoint; + ImGui::OpenPopup("##controlPointMenu"); + } + break; + } + case DRAG: { + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + mouseMode = NONE; + } else if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + auto &point = positionCurve.control_points[currentPoint]; + RwV3d outB; + float w, h; + CSprite::CalcScreenCoors({point.x, point.y, point.z}, &outB, &w, &h, true, true); + auto &mouse = ImGui::GetIO().MousePos; + RwV3d screen = {mouse.x, mouse.y, outB.z}; + RwV3d world; + CalcWorldCoors(&screen, &world); + point = glm::vec3(world.x, world.y, world.z); + } + break; + } + } + + if (ImGui::BeginPopup("##controlPointMenu")) { + editControlPointFunc(currentPoint); + ImGui::EndPopup(); + } + + unsigned allSteps = positionCurve.control_points.size() * 10; + float step = 1.f / static_cast(allSteps - 1); + //draw curve + if (cameraPath->isValid()) { + float minDistance = FLT_MAX; + float maxDistance = 0; + for (const auto &controlPoint : positionCurve.control_points) { + const auto distance = glm::distance(controlPoint, reinterpret_cast(TheCameraExtend.matrix.pos)); + minDistance = std::min(minDistance, distance); + maxDistance = std::max(maxDistance, distance); + } + for (unsigned i = 1; i < allSteps; i++) { + const auto t = static_cast(i) * step; + const auto point = curvePoint(positionCurve, t); + if (i > 0) { + const auto lastPoint = curvePoint(positionCurve, t - step); + ImVec2 p1, p2; + auto isVisible = + utils::getScreenPositionFromGamePosition(CVector(lastPoint.x, lastPoint.y, lastPoint.z), p1) && + utils::getScreenPositionFromGamePosition(CVector(point.x, point.y, point.z), p2); + if (isVisible) { + auto distance = DistanceBetweenPoints(CVector(point.x, point.y, point.z), + TheCameraExtend.matrix.pos); + auto depth = 1 - (distance - minDistance) / (maxDistance - minDistance) / 2; + ImGui::GetBackgroundDrawList()->AddLine( + p1, p2, ImColor(depth, depth, depth, 1.f), 2.f); + } + } + } + } else { + ImGui::PushTextWrapPos(fontScale * 300.f); + ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), + fmt::format("{} {}", ICON_FA_EXCLAMATION_TRIANGLE, + local.get("camera_paths.invalid_curve_warning")).c_str()); + ImGui::PopTextWrapPos(); + } + + if (const bool isWindow = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered(); + !isWindow) { + const float mouseSensitive = CCamera::m_fMouseAccelHorzntal * 1000; + if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { + const ImVec2 dt = ImGui::GetIO().MouseDelta * mouseSensitive * 0.2f; + auto eular = eulerAngles(quat_cast(CMatrixToGlmMat4(TheCameraExtend.matrix))); + eular.y += RAD(dt.y); + eular.z += RAD(-dt.x); + auto quat = glm::quat(eular); + TheCameraExtend.matrix.SetRotate({{quat.x, quat.y, quat.z}, quat.w}); + if (previewCurrentPoint) { + cameraPath->getRotationsCurve()[currentPoint] = {{quat.x, quat.y, quat.z}, quat.w}; + } + } + if (ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + if (ImGui::IsKeyDown(ImGuiKey_W)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.right * speedCameraMultiplier; + } else if (ImGui::IsKeyDown(ImGuiKey_S)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.right * speedCameraMultiplier * -1; + } + + if (ImGui::IsKeyDown(ImGuiKey_A)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.up * speedCameraMultiplier; + } else if (ImGui::IsKeyDown(ImGuiKey_D)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.up * speedCameraMultiplier * -1; + } + + if (ImGui::IsKeyDown(ImGuiKey_Q)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.at * speedCameraMultiplier; + } else if (ImGui::IsKeyDown(ImGuiKey_E)) { + TheCameraExtend.matrix.pos += TheCameraExtend.matrix.at * speedCameraMultiplier * -1; + } + + if (ImGui::GetIO().MouseWheel < 0.f) { + speedCameraMultiplier -= 0.01f; + speedCameraMultiplier = std::max(speedCameraMultiplier, 0.f); + Settings::getInstance().set("camera.editByPlayerSpeed", speedCameraMultiplier); + Settings::getInstance().Save(); + } else if (ImGui::GetIO().MouseWheel > 0.f) { + speedCameraMultiplier += 0.01f; + Settings::getInstance().set("camera.editByPlayerSpeed", speedCameraMultiplier); + Settings::getInstance().Save(); + } + } + } + + if (previewCurrentPoint && currentPoint != -1 && cameraPath->isValid() && !TheCameraExtend.isPlayingPath()) { + unsigned allSteps = positionCurve.control_points.size(); + float step = 1.f / static_cast(allSteps - 1); + auto point = curvePoint(positionCurve, step * static_cast(currentPoint)); + TheCameraExtend.matrix.pos = {point.x, point.y, point.z}; + auto q = cameraPath->getRotationsCurve()[currentPoint]; + TheCameraExtend.matrix.SetRotate(q); + } + + if (recalcKnots) { + positionCurve.knots.clear(); + const unsigned knotsSize = positionCurve.control_points.size() + positionCurve.degree + 1; + positionCurve.knots.resize(knotsSize); + for (unsigned i = 0; i < knotsSize; ++i) { + if (i <= positionCurve.degree) { + positionCurve.knots[i] = 0.f; + } else if (i >= knotsSize - positionCurve.degree - 1) { + positionCurve.knots[i] = 1.f; + } else { + const auto step = 1.f / static_cast(knotsSize - (positionCurve.degree + 1) * 2 + 1); + positionCurve.knots[i] = static_cast(i - positionCurve.degree) * step; + } + } + } + + std::array speedMove; + ImFormatString(speedMove.data(), sizeof speedMove, local.get("camera_paths.control").c_str(), + speedCameraMultiplier); + this->listOverlays.emplace_back(speedMove.data()); +} + +void Windows::CameraPathsWindow::close() { + ListWindow::close(); + TheCameraExtend.setExtendMode(false); + Command(FindPlayerPed(), false); + CWorld::Add(FindPlayerPed()); + TheCamera.Restore(); +} + +void Windows::CameraPathsWindow::open() { + ListWindow::open(); + speedCameraMultiplier = Settings::getInstance().get("camera.editByPlayerSpeed").value_or(1.f); + TheCameraExtend.setExtendMode(true); + Command(FindPlayerPed(), true); + CWorld::Remove(FindPlayerPed()); +} diff --git a/source/LDYOM_R/Windows/CameraPathsWindow.h b/source/LDYOM_R/Windows/CameraPathsWindow.h new file mode 100644 index 00000000..b4fbbb2f --- /dev/null +++ b/source/LDYOM_R/Windows/CameraPathsWindow.h @@ -0,0 +1,21 @@ +#pragma once +#include "ListWindow.h" + +namespace Windows { + class CameraPathsWindow : public ListWindow { + private: + int currentPoint = -1; + + public: + std::string getNameList() override; + std::string getNameOption() override; + int getListSize() override; + void createNewElement() override; + void createNewElementFrom(int i) override; + std::string& getElementName(int i) override; + void deleteElement(int i) override; + void drawOptions() override; + void close() override; + void open() override; + }; +} diff --git a/source/LDYOM_R/Windows/EntitiesWindow.cpp b/source/LDYOM_R/Windows/EntitiesWindow.cpp index 5094a84a..9ad8cb74 100644 --- a/source/LDYOM_R/Windows/EntitiesWindow.cpp +++ b/source/LDYOM_R/Windows/EntitiesWindow.cpp @@ -7,6 +7,7 @@ #include "ActorsWindow.h" #include "AudioWindow.h" +#include "CameraPathsWindow.h" #include "CheckpointsWindow.h" #include "ObjectsWindow.h" #include "ParticlesWindow.h" @@ -67,6 +68,12 @@ void Windows::EntitiesWindow::draw() { c_str(), ImVec2(ImGui::GetFontSize() * 12.5f, .0f))) { WindowsRenderService::getInstance().replaceWindow(); } + + if (ImGui::Button(fmt::format("{} {}", ICON_FA_CAMERA_MOVIE, + Localization::getInstance().get("entities.camera_paths")). + c_str(), ImVec2(ImGui::GetFontSize() * 12.5f, .0f))) { + WindowsRenderService::getInstance().replaceWindow(); + } } ImGui::End(); } diff --git a/source/LDYOM_R/Windows/ListWindow.cpp b/source/LDYOM_R/Windows/ListWindow.cpp index 65a1fb2f..be5004f0 100644 --- a/source/LDYOM_R/Windows/ListWindow.cpp +++ b/source/LDYOM_R/Windows/ListWindow.cpp @@ -116,7 +116,7 @@ void Windows::ListWindow::drawOverlay() const { void Windows::ListWindow::draw() { this->listOverlays.clear(); - this->currentElement = min(this->currentElement, getListSize() - 1); + this->currentElement = std::min(this->currentElement, getListSize() - 1); const ImVec2 listSize = drawList(); if (this->currentElement != -1) { const auto displaySize = ImGui::GetIO().DisplaySize; diff --git a/source/LDYOM_R/Windows/ObjectivesWindow.cpp b/source/LDYOM_R/Windows/ObjectivesWindow.cpp index 6a761d56..1bb8e2a9 100644 --- a/source/LDYOM_R/Windows/ObjectivesWindow.cpp +++ b/source/LDYOM_R/Windows/ObjectivesWindow.cpp @@ -33,6 +33,7 @@ #include "../Data/LevelWantedPlayerObjective.h" #include "../Data/PhoneCallPlayerObjective.h" #include "../Data/PhotographObjectObjective.h" +#include "../Data/PlayCameraPath.h" #include "../Data/RemoveTimerObjective.h" #include "../Data/RemoveWeaponsObjective.h" #include "../Data/SaveObjective.h" @@ -112,7 +113,10 @@ void Windows::ObjectivesWindow::createNewElementFrom(int i) { ProjectsService::getInstance().getCurrentProject().getCurrentScene()->createNewObjectiveFrom( fast_dynamic_cast(*objective)); break; - + case 10: + ProjectsService::getInstance().getCurrentProject().getCurrentScene()->createNewObjectiveFrom( + fast_dynamic_cast(*objective)); + break; default: break; } @@ -386,6 +390,12 @@ void Windows::ObjectivesWindow::drawListWindow() { this->selectElement(this->getListSize() - 1); } + if (ImGui::MenuItem(Localization::getInstance().get("objective.play_camera_path").c_str())) { + ProjectsService::getInstance().getCurrentProject().getCurrentScene()->createNewObjectives< + PlayCameraPathObjective>(nullptr); + this->selectElement(this->getListSize() - 1); + } + ImGui::EndMenu(); } diff --git a/source/LDYOM_R/Windows/ProjectsWindowPopup.cpp b/source/LDYOM_R/Windows/ProjectsWindowPopup.cpp index 614beb13..f1f8b3e7 100644 --- a/source/LDYOM_R/Windows/ProjectsWindowPopup.cpp +++ b/source/LDYOM_R/Windows/ProjectsWindowPopup.cpp @@ -48,7 +48,7 @@ void Windows::ProjectsWindowPopup::projectInfo(Localization &local, const Projec const float scaleX = BASE_WIDTH / static_cast(icon->getWidth()); const float scaleY = (BASE_WIDTH - 100.0f) / static_cast(icon->getHeight()); - const float scale = min(scaleX, scaleY); + const float scale = std::min(scaleX, scaleY); const auto size = ImVec2(static_cast(icon->getWidth()) * scale, static_cast(icon->getHeight()) * scale); diff --git a/source/LDYOM_R/include/tinynurbs/core/basis.h b/source/LDYOM_R/include/tinynurbs/core/basis.h new file mode 100644 index 00000000..a654b9fb --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/basis.h @@ -0,0 +1,271 @@ +/** + * Low-level functions for evaluating B-spline basis functions and their derivatives + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_BASIS_H +#define TINYNURBS_BASIS_H + +#include "../util/array2.h" +#include "../util/util.h" +#include + +namespace tinynurbs +{ + +/** + * Find the span of the given parameter in the knot vector. + * @param[in] degree Degree of the curve. + * @param[in] knots Knot vector of the curve. + * @param[in] u Parameter value. + * @return Span index into the knot vector such that (span - 1) < u <= span +*/ +template int findSpan(unsigned int degree, const std::vector &knots, T u) +{ + // index of last control point + int n = static_cast(knots.size()) - degree - 2; + assert(n >= 0); + + // For values of u that lies outside the domain + if (u >= knots[n + 1]) + { + return n; + } + if (u <= knots[degree]) + { + return degree; + } + + // Binary search + // TODO: Replace this with std::lower_bound + int low = degree; + int high = n + 1; + int mid = (int)std::floor((low + high) / 2.0); + while (u < knots[mid] || u >= knots[mid + 1]) + { + if (u < knots[mid]) + { + high = mid; + } + else + { + low = mid; + } + mid = (int)std::floor((low + high) / 2.0); + } + return mid; +} + +/** + * Compute a single B-spline basis function + * @param[in] i The ith basis function to compute. + * @param[in] deg Degree of the basis function. + * @param[in] knots Knot vector corresponding to the basis functions. + * @param[in] u Parameter to evaluate the basis functions at. + * @return The value of the ith basis function at u. + */ +template T bsplineOneBasis(int i, unsigned int deg, const std::vector &U, T u) +{ + int m = static_cast(U.size()) - 1; + // Special case + if ((i == 0 && close(u, U[0])) || (i == m - deg - 1 && close(u, U[m]))) + { + return 1.0; + } + // Local property ensures that basis function is zero outside span + if (u < U[i] || u >= U[i + deg + 1]) + { + return 0.0; + } + // Initialize zeroth-degree functions + std::vector N; + N.resize(deg + 1); + for (int j = 0; j <= static_cast(deg); j++) + { + N[j] = (u >= U[i + j] && u < U[i + j + 1]) ? 1.0 : 0.0; + } + // Compute triangular table + for (int k = 1; k <= static_cast(deg); k++) + { + T saved = (util::close(N[0], 0.0)) ? 0.0 : ((u - U[i]) * N[0]) / (U[i + k] - U[i]); + for (int j = 0; j < static_cast(deg) - k + 1; j++) + { + T Uleft = U[i + j + 1]; + T Uright = U[i + j + k + 1]; + if (util::close(N[j + 1], 0.0)) + { + N[j] = saved; + saved = 0.0; + } + else + { + T temp = N[j + 1] / (Uright - Uleft); + N[j] = saved + (Uright - u) * temp; + saved = (u - Uleft) * temp; + } + } + } + return N[0]; +} + +/** + * Compute all non-zero B-spline basis functions + * @param[in] deg Degree of the basis function. + * @param[in] span Index obtained from findSpan() corresponding the u and knots. + * @param[in] knots Knot vector corresponding to the basis functions. + * @param[in] u Parameter to evaluate the basis functions at. + * @return N Values of (deg+1) non-zero basis functions. + */ +template +std::vector bsplineBasis(unsigned int deg, int span, const std::vector &knots, T u) +{ + std::vector N; + N.resize(deg + 1, T(0)); + std::vector left, right; + left.resize(deg + 1, static_cast(0.0)); + right.resize(deg + 1, static_cast(0.0)); + T saved = 0.0, temp = 0.0; + + N[0] = 1.0; + + for (int j = 1; j <= static_cast(deg); j++) + { + left[j] = (u - knots[span + 1 - j]); + right[j] = knots[span + j] - u; + saved = 0.0; + for (int r = 0; r < j; r++) + { + temp = N[r] / (right[r + 1] + left[j - r]); + N[r] = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + N[j] = saved; + } + return N; +} + +/** + * Compute all non-zero derivatives of B-spline basis functions + * @param[in] deg Degree of the basis function. + * @param[in] span Index obtained from findSpan() corresponding the u and knots. + * @param[in] knots Knot vector corresponding to the basis functions. + * @param[in] u Parameter to evaluate the basis functions at. + * @param[in] num_ders Number of derivatives to compute (num_ders <= deg) + * @return ders Values of non-zero derivatives of basis functions. + */ +template +array2 bsplineDerBasis(unsigned int deg, int span, const std::vector &knots, T u, + int num_ders) +{ + std::vector left, right; + left.resize(deg + 1, 0.0); + right.resize(deg + 1, 0.0); + T saved = 0.0, temp = 0.0; + + array2 ndu(deg + 1, deg + 1); + ndu(0, 0) = 1.0; + + for (int j = 1; j <= static_cast(deg); j++) + { + left[j] = u - knots[span + 1 - j]; + right[j] = knots[span + j] - u; + saved = 0.0; + + for (int r = 0; r < j; r++) + { + // Lower triangle + ndu(j, r) = right[r + 1] + left[j - r]; + temp = ndu(r, j - 1) / ndu(j, r); + // Upper triangle + ndu(r, j) = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + + ndu(j, j) = saved; + } + + array2 ders(num_ders + 1, deg + 1, T(0)); + + for (int j = 0; j <= static_cast(deg); j++) + { + ders(0, j) = ndu(j, deg); + } + + array2 a(2, deg + 1); + + for (int r = 0; r <= static_cast(deg); r++) + { + int s1 = 0; + int s2 = 1; + a(0, 0) = 1.0; + + for (int k = 1; k <= num_ders; k++) + { + T d = 0.0; + int rk = r - k; + int pk = deg - k; + int j1 = 0; + int j2 = 0; + + if (r >= k) + { + a(s2, 0) = a(s1, 0) / ndu(pk + 1, rk); + d = a(s2, 0) * ndu(rk, pk); + } + + if (rk >= -1) + { + j1 = 1; + } + else + { + j1 = -rk; + } + + if (r - 1 <= pk) + { + j2 = k - 1; + } + else + { + j2 = deg - r; + } + + for (int j = j1; j <= j2; j++) + { + a(s2, j) = (a(s1, j) - a(s1, j - 1)) / ndu(pk + 1, rk + j); + d += a(s2, j) * ndu(rk + j, pk); + } + + if (r <= pk) + { + a(s2, k) = -a(s1, k - 1) / ndu(pk + 1, r); + d += a(s2, k) * ndu(r, pk); + } + + ders(k, r) = d; + + int temp = s1; + s1 = s2; + s2 = temp; + } + } + + T fac = static_cast(deg); + for (int k = 1; k <= num_ders; k++) + { + for (int j = 0; j <= static_cast(deg); j++) + { + ders(k, j) *= fac; + } + fac *= static_cast(deg - k); + } + + return ders; +} + +} // namespace tinynurbs + +#endif // TINYNURBS_BASIS_H diff --git a/source/LDYOM_R/include/tinynurbs/core/check.h b/source/LDYOM_R/include/tinynurbs/core/check.h new file mode 100644 index 00000000..4bc7fd52 --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/check.h @@ -0,0 +1,411 @@ +/** + * Functionality for checking validity and properties of NURBS curves and + * surfaces + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_CHECK_H +#define TINYNURBS_CHECK_H + +#include "curve.h" +#include "glm/glm.hpp" +#include "surface.h" +#include +#include +#include + +namespace tinynurbs +{ + +///////////////////////////////////////////////////////////////////// + +namespace internal +{ + +/** + * Checks if the relation between degree, number of knots, and + * number of control points is valid + * @param[in] degree Degree + * @param[in] num_knots Number of knot values + * @param[in] num_ctrl_pts Number of control points + * @return Whether the relationship is valid + */ +inline bool isValidRelation(unsigned int degree, size_t num_knots, size_t num_ctrl_pts) +{ + return (num_knots - degree - 1) == num_ctrl_pts; +} + +/** + * isKnotVectorMonotonic returns whether the knots are in ascending order + * @tparam Type of knot values + * @param[in] knots Knot vector + * @return Whether monotonic + */ +template bool isKnotVectorMonotonic(const std::vector &knots) +{ + return std::is_sorted(knots.begin(), knots.end()); +} + +/** + * Returns whether the curve is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] degree Degree of curve + * @param[in] knots Knot vector of curve + * @param[in] control_points Control points of curve + * @return Whether valid + */ +template +bool curveIsValid(unsigned int degree, const std::vector &knots, + const std::vector> &control_points) +{ + if (degree < 1 || degree > 9) + { + return false; + } + if (!isValidRelation(degree, knots.size(), control_points.size())) + { + return false; + } + if (!isKnotVectorMonotonic(knots)) + { + return false; + } + return true; +} + +/** + * Returns whether the curve is valid + * @tparam T Type of control point coordinates, knot values and weights + * @param[in] degree Degree of curve + * @param[in] knots Knot vector of curve + * @param[in] control_points Control points of curve + * @return Whether valid + */ +template +bool curveIsValid(unsigned int degree, const std::vector &knots, + const std::vector> &control_points, const std::vector &weights) +{ + if (!isValidRelation(degree, knots.size(), control_points.size())) + { + return false; + } + if (weights.size() != control_points.size()) + { + return false; + } + return true; +} + +/** + * Returns whether the surface is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] degree_u Degree of surface along u-direction + * @param[in] degree_v Degree of surface along v-direction + * @param[in] knots_u Knot vector of surface along u-direction + * @param[in] knots_v Knot vector of surface along v-direction + * @param[in] control_points Control points grid of surface + * @return Whether valid + */ +template +bool surfaceIsValid(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, + const std::vector &knots_v, const array2> &control_points) +{ + if (degree_u < 1 || degree_u > 9 || degree_v < 1 || degree_v > 9) + { + return false; + } + if (!isValidRelation(degree_u, knots_u.size(), control_points.rows()) || + !isValidRelation(degree_v, knots_v.size(), control_points.cols())) + { + return false; + } + if (!isKnotVectorMonotonic(knots_u) || !isKnotVectorMonotonic(knots_v)) + { + return false; + } + return true; +} + +/** + * Returns whether the rational surface is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] degree_u Degree of surface along u-direction + * @param[in] degree_v Degree of surface along v-direction + * @param[in] knots_u Knot vector of surface along u-direction + * @param[in] knots_v Knot vector of surface along v-direction + * @param[in] control_points Control points grid of surface + * @param[in] weights Weights corresponding to control point grid of surface + * @return Whether valid + */ +template +bool surfaceIsValid(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, + const std::vector &knots_v, const array2> &control_points, + const array2 &weights) +{ + if (!surfaceIsValid(degree_u, degree_v, knots_u, knots_v, control_points)) + { + return false; + } + if (control_points.rows() != weights.rows() || control_points.cols() != weights.cols()) + { + return false; + } + return true; +} + +/** + * Returns whether the given knot vector is closed by checking the + * periodicity of knot vectors near the start and end + * @param[in] degree Degree of curve/surface + * @param[in] knots Knot vector of curve/surface + * @return Whether knot vector is closed + */ +template bool isKnotVectorClosed(unsigned int degree, const std::vector &knots) +{ + for (int i = 0; i < degree - 1; ++i) + { + int j = knots.size() - degree + i; + if (std::abs((knots[i + 1] - knots[i]) - (knots[j + 1] - knots[j])) > + std::numeric_limits::epsilon()) + { + return false; + } + } + return true; +} + +/** + * Returns whether the given knot vector is closed by checking the + * periodicity of knot vectors near the start and end + * @param[in] degree Degree of curve/surface + * @param[in] vec Array of any control points/weights + * @return Whether knot vector is closed + */ +template bool isArray1Closed(unsigned int degree, const std::vector &vec) +{ + for (int i = 0; i < degree; ++i) + { + int j = vec.size() - degree + i; + if (glm::length(vec[i] - vec[j]) > 1e-5) + { + return false; + } + } + return true; +} + +/** + * Returns whether the 2D array is closed along the u-direction + * i.e., along rows. + * @param[in] degree_u Degree along u-direction + * @param[in] arr 2D array of control points / weights + * @return Whether closed along u-direction + */ +template bool isArray2ClosedU(unsigned int degree_u, const array2 &arr) +{ + for (int i = 0; i < degree_u; ++i) + { + for (int j = 0; j < arr.cols(); ++j) + { + int k = arr.cols() - degree_u + i; + if (glm::length(arr(i, j) - arr(k, j)) > 1e-5) + { + return false; + } + } + } + return true; +} + +/** + * Returns whether the 2D array is closed along the v-direction + * i.e., along columns. + * @param[in] degree_v Degree along v-direction + * @param[in] arr 2D array of control points / weights + * @return Whether closed along v-direction + */ +template bool isArray2ClosedV(unsigned int degree_v, const array2 &arr) +{ + for (int i = 0; i < arr.rows(); ++i) + { + for (int j = 0; j < degree_v; j++) + { + int k = arr.rows() - degree_v + i; + if (glm::length(arr(i, j) - arr(i, k)) > 1e-5) + { + return false; + } + } + } + return true; +} + +} // namespace internal + +///////////////////////////////////////////////////////////////////// + +/** + * Returns the multiplicity of the knot at index + * @tparam Type of knot values + * @param[in] knots Knot vector + * @param[in] knot_val Knot of interest + * @return Multiplicity (>= 0) + */ +template unsigned int knotMultiplicity(const std::vector &knots, T knot_val) +{ + T eps = std::numeric_limits::epsilon(); + unsigned int mult = 0; + for (const T knot : knots) + { + if (std::abs(knot_val - knot) < eps) + { + ++mult; + } + } + return mult; +} + +/** + * Returns the mulitplicity of the knot at index + * @tparam Type of knot values + * @param[in] knots Knot vector + * @param[in] index Index of knot of interest + * @return Multiplicity (>= 1) + */ +template +[[deprecated("Use knotMultiplicity(knots, param).")]] +unsigned int knotMultiplicity(const std::vector &knots, unsigned int index) +{ + T curr_knot_val = knots[index]; + T eps = std::numeric_limits::epsilon(); + unsigned int mult = 0; + for (const T knot : knots) + { + if (std::abs(curr_knot_val - knot) < eps) + { + ++mult; + } + } + return mult; +} + +/** + * Returns whether the curve is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] crv Curve object + * @return Whether valid + */ +template bool curveIsValid(const Curve &crv) +{ + return internal::curveIsValid(crv.degree, crv.knots, crv.control_points); +} + +/** + * Returns whether the curve is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] crv RationalCurve object + * @return Whether valid + */ +template bool curveIsValid(const RationalCurve &crv) +{ + return internal::curveIsValid(crv.degree, crv.knots, crv.control_points, crv.weights); +} + +/** + * Returns whether the surface is valid + * @tparam T Type of control point coordinates, knot values + * @param srf Surface object + * @return Whether valid + */ +template bool surfaceIsValid(const Surface &srf) +{ + return internal::surfaceIsValid(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, + srf.control_points); +} + +/** + * Returns whether the rational surface is valid + * @tparam T Type of control point coordinates, knot values + * @param[in] srf RationalSurface object + * @return Whether valid + */ +template bool surfaceIsValid(const RationalSurface &srf) +{ + return internal::surfaceIsValid(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, + srf.control_points, srf.weights); +} + +/** + * Checks whether the curve is closed + * @param[in] crv Curve object + * @return Whether closed + */ +template bool curveIsClosed(const Curve &crv) +{ + return internal::isArray1Closed(crv.degree, crv.control_points) && + internal::isKnotVectorClosed(crv.degree, crv.knots); +} + +/** + * Checks whether the rational curve is closed + * @param[in] crv RationalCurve object + * @return Whether closed + */ +template bool curveIsClosed(const RationalCurve &crv) +{ + return internal::isArray1Closed(crv.degree, crv.control_points) && + internal::isArray1Closed(crv.degree, crv.weights) && + internal::isKnotVectorClosed(crv.degree, crv.knots); +} + +/** + * Checks whether the surface is closed along u-direction + * @param[in] srf Surface object + * @return Whether closed along u-direction + */ +template bool surfaceIsClosedU(const Surface &srf) +{ + return internal::isArray2ClosedU(srf.degree_u, srf.control_points) && + internal::isKnotVectorClosed(srf.degree_u, srf.knots_u); +} + +/** + * Checks whether the surface is closed along v-direction + * @param[in] srf Surface object + * @return Whether closed along v-direction + */ +template bool surfaceIsClosedV(const Surface &srf) +{ + return internal::isArray2ClosedV(srf.degree_v, srf.control_points) && + internal::isKnotVectorClosed(srf.degree_v, srf.knots_v); +} + +/** + * Checks whether the rational surface is closed along u-direction + * @param[in] srf RationalSurface object + * @return Whether closed along u-direction + */ +template bool surfaceIsClosedU(const RationalSurface &srf) +{ + return internal::isArray2ClosedU(srf.degree_u, srf.control_points) && + internal::isKnotVectorClosed(srf.degree_u, srf.knots_u) && + internal::isArray2ClosedU(srf.degree_u, srf.weights); +} + +/** + * Checks whether the rational surface is closed along v-direction + * @param[in] srf RationalSurface object + * @return Whether closed along v-direction + */ +template bool surfaceIsClosedV(const RationalSurface &srf) +{ + return internal::isArray2ClosedV(srf.degree_v, srf.control_points) && + internal::isKnotVectorClosed(srf.degree_v, srf.knots_v) && + internal::isArray2ClosedV(srf.degree_v, srf.weights); +} + +} // namespace tinynurbs + +#endif // TINYNURBS_CHECK_H diff --git a/source/LDYOM_R/include/tinynurbs/core/curve.h b/source/LDYOM_R/include/tinynurbs/core/curve.h new file mode 100644 index 00000000..04eb346e --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/curve.h @@ -0,0 +1,77 @@ +/** + * The Curve class represents a non-uniform polynomial B-spline curve, while the + * RationalCurve class represents a non-uniform rational B-spline (NURBS) curve. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_CURVE_H +#define TINYNURBS_CURVE_H + +#include "glm/glm.hpp" +#include +#include +#include + +namespace tinynurbs +{ + +// Forward declaration +template struct RationalCurve; + +/** +Struct for holding a polynomial B-spline curve +@tparam T Data type of control points and knots (float or double) +*/ +template struct Curve +{ + unsigned int degree; + std::vector knots; + std::vector> control_points; + + Curve() = default; + Curve(const RationalCurve &crv) : Curve(crv.degree, crv.knots, crv.control_points) {} + Curve(unsigned int degree, const std::vector &knots, + const std::vector> &control_points) + : degree(degree), knots(knots), control_points(control_points) + { + } +}; + +/** +Struct for holding a rational B-spline curve +@tparam T Data type of control points and knots (float or double) +*/ +template struct RationalCurve +{ + unsigned int degree; + std::vector knots; + std::vector> control_points; + std::vector weights; + + RationalCurve() = default; + RationalCurve(const Curve &crv) + : RationalCurve(crv, std::vector(crv.control_points.size(), 1.0)) + { + } + RationalCurve(const Curve &crv, const std::vector &weights) + : RationalCurve(crv.degree, crv.knots, crv.control_points, weights) + { + } + RationalCurve(unsigned int degree, const std::vector &knots, + const std::vector> &control_points, const std::vector weights) + : degree(degree), knots(knots), control_points(control_points), weights(weights) + { + } +}; + +// Typedefs for ease of use +typedef Curve Curve3f; +typedef Curve Curve3d; +typedef RationalCurve RationalCurve3f; +typedef RationalCurve RationalCurve3d; + +} // namespace tinynurbs + +#endif // TINYNURBS_CURVE_H diff --git a/source/LDYOM_R/include/tinynurbs/core/evaluate.h b/source/LDYOM_R/include/tinynurbs/core/evaluate.h new file mode 100644 index 00000000..3267ae5d --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/evaluate.h @@ -0,0 +1,585 @@ +/** + * Core functionality for evaluating points, derivatives and related + * quantities on NURBS curves and surfaces. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_EVALUATE_H +#define TINYNURBS_EVALUATE_H + +#include "../util/util.h" +#include "basis.h" +#include "curve.h" +#include "glm/glm.hpp" +#include "surface.h" +#include "tinynurbs/util/array2.h" +#include +#include + +namespace tinynurbs +{ + +///////////////////////////////////////////////////////////////////// + +namespace internal +{ + +/** + * Evaluate point on a nonrational NURBS curve + * @param[in] degree Degree of the given curve. + * @param[in] knots Knot vector of the curve. + * @param[in] control_points Control points of the curve. + * @param[in] u Parameter to evaluate the curve at. + * @return point Resulting point on the curve at parameter u. + */ +template +glm::vec curvePoint(unsigned int degree, const std::vector &knots, + const std::vector> &control_points, T u) +{ + // Initialize result to 0s + glm::vec point(T(0)); + + // Find span and corresponding non-zero basis functions + int span = findSpan(degree, knots, u); + std::vector N = bsplineBasis(degree, span, knots, u); + + // Compute point + for (unsigned int j = 0; j <= degree; j++) + { + point += static_cast(N[j]) * control_points[span - degree + j]; + } + return point; +} + +/** + * Evaluate derivatives of a non-rational NURBS curve + * @param[in] degree Degree of the curve + * @param[in] knots Knot vector of the curve. + * @param[in] control_points Control points of the curve. + * @param[in] num_ders Number of times to derivate. + * @param[in] u Parameter to evaluate the derivatives at. + * @return curve_ders Derivatives of the curve at u. + * E.g. curve_ders[n] is the nth derivative at u, where 0 <= n <= num_ders. + */ +template +std::vector> curveDerivatives(unsigned int degree, const std::vector &knots, + const std::vector> &control_points, + int num_ders, T u) +{ + + typedef glm::vec tvecn; + using std::vector; + + std::vector> curve_ders; + curve_ders.resize(num_ders + 1); + + // Assign higher order derivatives to zero + for (int k = degree + 1; k <= num_ders; k++) + { + curve_ders[k] = tvecn(0.0); + } + + // Find the span and corresponding non-zero basis functions & derivatives + int span = findSpan(degree, knots, u); + array2 ders = bsplineDerBasis(degree, span, knots, u, num_ders); + + // Compute first num_ders derivatives + int du = num_ders < static_cast(degree) ? num_ders : static_cast(degree); + for (int k = 0; k <= du; k++) + { + curve_ders[k] = tvecn(0.0); + for (int j = 0; j <= static_cast(degree); j++) + { + curve_ders[k] += static_cast(ders(k, j)) * control_points[span - degree + j]; + } + } + return curve_ders; +} + +/** + * Evaluate point on a nonrational NURBS surface + * @param[in] degree_u Degree of the given surface in u-direction. + * @param[in] degree_v Degree of the given surface in v-direction. + * @param[in] knots_u Knot vector of the surface in u-direction. + * @param[in] knots_v Knot vector of the surface in v-direction. + * @param[in] control_points Control points of the surface in a 2d array. + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @return point Resulting point on the surface at (u, v). + */ +template +glm::vec surfacePoint(unsigned int degree_u, unsigned int degree_v, + const std::vector &knots_u, const std::vector &knots_v, + const array2> &control_points, T u, T v) +{ + + // Initialize result to 0s + glm::vec point(T(0.0)); + + // Find span and non-zero basis functions + int span_u = findSpan(degree_u, knots_u, u); + int span_v = findSpan(degree_v, knots_v, v); + std::vector Nu = bsplineBasis(degree_u, span_u, knots_u, u); + std::vector Nv = bsplineBasis(degree_v, span_v, knots_v, v); + + for (int l = 0; l <= degree_v; l++) + { + glm::vec temp(0.0); + for (int k = 0; k <= degree_u; k++) + { + temp += static_cast(Nu[k]) * + control_points(span_u - degree_u + k, span_v - degree_v + l); + } + + point += static_cast(Nv[l]) * temp; + } + return point; +} + +/** + * Evaluate derivatives on a non-rational NURBS surface + * @param[in] degree_u Degree of the given surface in u-direction. + * @param[in] degree_v Degree of the given surface in v-direction. + * @param[in] knots_u Knot vector of the surface in u-direction. + * @param[in] knots_v Knot vector of the surface in v-direction. + * @param[in] control_points Control points of the surface in a 2D array. + * @param[in] num_ders Number of times to differentiate + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @param[out] surf_ders Derivatives of the surface at (u, v). + */ +template +array2> +surfaceDerivatives(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, + const std::vector &knots_v, const array2> &control_points, + unsigned int num_ders, T u, T v) +{ + + array2> surf_ders(num_ders + 1, num_ders + 1, glm::vec(0.0)); + + // Set higher order derivatives to 0 + for (int k = degree_u + 1; k <= num_ders; k++) + { + for (int l = degree_v + 1; l <= num_ders; l++) + { + surf_ders(k, l) = glm::vec(0.0); + } + } + + // Find span and basis function derivatives + int span_u = findSpan(degree_u, knots_u, u); + int span_v = findSpan(degree_v, knots_v, v); + array2 ders_u = bsplineDerBasis(degree_u, span_u, knots_u, u, num_ders); + array2 ders_v = bsplineDerBasis(degree_v, span_v, knots_v, v, num_ders); + + // Number of non-zero derivatives is <= degree + unsigned int du = std::min(num_ders, degree_u); + unsigned int dv = std::min(num_ders, degree_v); + + std::vector> temp; + temp.resize(degree_v + 1); + // Compute derivatives + for (int k = 0; k <= du; k++) + { + for (int s = 0; s <= degree_v; s++) + { + temp[s] = glm::vec(0.0); + for (int r = 0; r <= degree_u; r++) + { + temp[s] += static_cast(ders_u(k, r)) * + control_points(span_u - degree_u + r, span_v - degree_v + s); + } + } + + int dd = std::min(num_ders - k, dv); + + for (int l = 0; l <= dd; l++) + { + for (int s = 0; s <= degree_v; s++) + { + surf_ders(k, l) += ders_v(l, s) * temp[s]; + } + } + } + return surf_ders; +} + +} // namespace internal + +///////////////////////////////////////////////////////////////////// + +/** +Evaluate point on a nonrational NURBS curve +@param[in] crv Curve object +@param[in] u Parameter to evaluate the curve at. +@return point Resulting point on the curve at parameter u. +*/ +template glm::vec<3, T> curvePoint(const Curve &crv, T u) +{ + return internal::curvePoint(crv.degree, crv.knots, crv.control_points, u); +} + +/** + * Evaluate point on a rational NURBS curve + * @param[in] crv RationalCurve object + * @param[in] u Parameter to evaluate the curve at. + * @return point Resulting point on the curve. + */ +template glm::vec<3, T> curvePoint(const RationalCurve &crv, T u) +{ + + typedef glm::vec<4, T> tvecnp1; + + // Compute homogenous coordinates of control points + std::vector Cw; + Cw.reserve(crv.control_points.size()); + for (size_t i = 0; i < crv.control_points.size(); i++) + { + Cw.push_back(tvecnp1(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i]))); + } + + // Compute point using homogenous coordinates + tvecnp1 pointw = internal::curvePoint(crv.degree, crv.knots, Cw, u); + + // Convert back to cartesian coordinates + return util::homogenousToCartesian(pointw); +} + +/** + * Evaluate derivatives of a non-rational NURBS curve + * @param[in] crv Curve object + * @param[in] num_ders Number of times to derivate. + * @param[in] u Parameter to evaluate the derivatives at. + * @return curve_ders Derivatives of the curve at u. + * E.g. curve_ders[n] is the nth derivative at u, where 0 <= n <= num_ders. + */ +template +std::vector> curveDerivatives(const Curve &crv, int num_ders, T u) +{ + return internal::curveDerivatives(crv.degree, crv.knots, crv.control_points, num_ders, u); +} + +/** + * Evaluate derivatives of a rational NURBS curve + * @param[in] u Parameter to evaluate the derivatives at. + * @param[in] knots Knot vector of the curve. + * @param[in] control_points Control points of the curve. + * @param[in] weights Weights corresponding to each control point. + * @param[in] num_ders Number of times to differentiate. + * @param[inout] curve_ders Derivatives of the curve at u. + * E.g. curve_ders[n] is the nth derivative at u, where n is between 0 and + * num_ders-1. + */ +template +std::vector> curveDerivatives(const RationalCurve &crv, int num_ders, T u) +{ + + typedef glm::vec<3, T> tvecn; + typedef glm::vec<4, T> tvecnp1; + + std::vector curve_ders; + curve_ders.reserve(num_ders + 1); + + // Compute homogenous coordinates of control points + std::vector Cw; + Cw.reserve(crv.control_points.size()); + for (size_t i = 0; i < crv.control_points.size(); i++) + { + Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); + } + + // Derivatives of Cw + std::vector Cwders = + internal::curveDerivatives(crv.degree, crv.knots, Cw, num_ders, u); + + // Split Cwders into coordinates and weights + std::vector Aders; + std::vector wders; + for (const auto &val : Cwders) + { + Aders.push_back(util::truncateHomogenous(val)); + wders.push_back(val.w); + } + + // Compute rational derivatives + for (int k = 0; k <= num_ders; k++) + { + tvecn v = Aders[k]; + for (int i = 1; i <= k; i++) + { + v -= static_cast(util::binomial(k, i)) * wders[i] * curve_ders[k - i]; + } + curve_ders.push_back(v / wders[0]); + } + return curve_ders; +} + +/** + * Evaluate the tangent of a B-spline curve + * @param[in] crv Curve object + * @return Unit tangent of the curve at u. + */ +template glm::vec<3, T> curveTangent(const Curve &crv, T u) +{ + std::vector> ders = curveDerivatives(crv, 1, u); + glm::vec<3, T> du = ders[1]; + T du_len = glm::length(du); + if (!util::close(du_len, T(0))) + { + du /= du_len; + } + return du; +} + +/** + * Evaluate the tangent of a rational B-spline curve + * @param[in] crv RationalCurve object + * @return Unit tangent of the curve at u. + */ +template glm::vec<3, T> curveTangent(const RationalCurve &crv, T u) +{ + std::vector> ders = curveDerivatives(crv, 1, u); + glm::vec<3, T> du = ders[1]; + T du_len = glm::length(du); + if (!util::close(du_len, T(0))) + { + du /= du_len; + } + return du; +} + +/** + * Evaluate point on a nonrational NURBS surface + * @param[in] srf Surface object + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @return Resulting point on the surface at (u, v). + */ +template glm::vec<3, T> surfacePoint(const Surface &srf, T u, T v) +{ + return internal::surfacePoint(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, + srf.control_points, u, v); +} + +/** + * Evaluate point on a non-rational NURBS surface + * @param[in] srf RationalSurface object + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @return Resulting point on the surface at (u, v). + */ +template glm::vec<3, T> surfacePoint(const RationalSurface &srf, T u, T v) +{ + + typedef glm::vec<4, T> tvecnp1; + + // Compute homogenous coordinates of control points + array2 Cw; + Cw.resize(srf.control_points.rows(), srf.control_points.cols()); + for (int i = 0; i < srf.control_points.rows(); i++) + { + for (int j = 0; j < srf.control_points.cols(); j++) + { + Cw(i, j) = + tvecnp1(util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j))); + } + } + + // Compute point using homogenous coordinates + tvecnp1 pointw = + internal::surfacePoint(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, Cw, u, v); + + // Convert back to cartesian coordinates + return util::homogenousToCartesian(pointw); +} + +/** + * Evaluate derivatives on a non-rational NURBS surface + * @param[in] degree_u Degree of the given surface in u-direction. + * @param[in] degree_v Degree of the given surface in v-direction. + * @param[in] knots_u Knot vector of the surface in u-direction. + * @param[in] knots_v Knot vector of the surface in v-direction. + * @param[in] control_points Control points of the surface in a 2D array. + * @param[in] num_ders Number of times to differentiate + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @return surf_ders Derivatives of the surface at (u, v). + */ +template +array2> surfaceDerivatives(const Surface &srf, int num_ders, T u, T v) +{ + return internal::surfaceDerivatives(srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, + srf.control_points, num_ders, u, v); +} + +/** + * Evaluate derivatives on a rational NURBS surface + * @param[in] srf RationalSurface object + * @param[in] u Parameter to evaluate the surface at. + * @param[in] v Parameter to evaluate the surface at. + * @param[in] num_ders Number of times to differentiate + * @return Derivatives on the surface at parameter (u, v). + */ +template +array2> surfaceDerivatives(const RationalSurface &srf, int num_ders, T u, T v) +{ + + using namespace std; + using namespace glm; + + typedef vec<3, T> tvecn; + typedef vec<4, T> tvecnp1; + + array2 homo_cp; + homo_cp.resize(srf.control_points.rows(), srf.control_points.cols()); + for (int i = 0; i < srf.control_points.rows(); ++i) + { + for (int j = 0; j < srf.control_points.cols(); ++j) + { + homo_cp(i, j) = + util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); + } + } + + array2 homo_ders = internal::surfaceDerivatives( + srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v, homo_cp, num_ders, u, v); + + array2 Aders; + Aders.resize(num_ders + 1, num_ders + 1); + for (int i = 0; i < homo_ders.rows(); ++i) + { + for (int j = 0; j < homo_ders.cols(); ++j) + { + Aders(i, j) = util::truncateHomogenous(homo_ders(i, j)); + } + } + + array2 surf_ders(num_ders + 1, num_ders + 1); + for (int k = 0; k < num_ders + 1; ++k) + { + for (int l = 0; l < num_ders - k + 1; ++l) + { + auto der = Aders(k, l); + + for (int j = 1; j < l + 1; ++j) + { + der -= (T)util::binomial(l, j) * homo_ders(0, j).w * surf_ders(k, l - j); + } + + for (int i = 1; i < k + 1; ++i) + { + der -= (T)util::binomial(k, i) * homo_ders(i, 0).w * surf_ders(k - i, l); + + tvecn tmp((T)0.0); + for (int j = 1; j < l + 1; ++j) + { + tmp -= (T)util::binomial(l, j) * homo_ders(i, j).w * surf_ders(k - 1, l - j); + } + + der -= (T)util::binomial(k, i) * tmp; + } + + der *= 1 / homo_ders(0, 0).w; + surf_ders(k, l) = der; + } + } + return surf_ders; +} + +/** + * Evaluate the two orthogonal tangents of a non-rational surface at the given + * parameters + * @param[in] srf Surface object + * @param u Parameter in the u-direction + * @param v Parameter in the v-direction + * @return Tuple with unit tangents along u- and v-directions + */ +template +std::tuple, glm::vec<3, T>> surfaceTangent(const Surface &srf, T u, T v) +{ + array2> ptder = surfaceDerivatives(srf, 1, u, v); + glm::vec<3, T> du = ptder(1, 0); + glm::vec<3, T> dv = ptder(0, 1); + T du_len = glm::length(ptder(1, 0)); + T dv_len = glm::length(ptder(0, 1)); + if (!util::close(du_len, T(0))) + { + du /= du_len; + } + if (!util::close(dv_len, T(0))) + { + dv /= dv_len; + } + return std::make_tuple(std::move(du), std::move(dv)); +} + +/** + * Evaluate the two orthogonal tangents of a rational surface at the given + * parameters + * @param[in] srf Rational Surface object + * @param u Parameter in the u-direction + * @param v Parameter in the v-direction + * @return Tuple with unit tangents along u- and v-directions + */ +template +std::tuple, glm::vec<3, T>> surfaceTangent(const RationalSurface &srf, T u, T v) +{ + array2> ptder = surfaceDerivatives(srf, 1, u, v); + glm::vec<3, T> du = ptder(1, 0); + glm::vec<3, T> dv = ptder(0, 1); + T du_len = glm::length(ptder(1, 0)); + T dv_len = glm::length(ptder(0, 1)); + if (!util::close(du_len, T(0))) + { + du /= du_len; + } + if (!util::close(dv_len, T(0))) + { + dv /= dv_len; + } + return std::make_tuple(std::move(du), std::move(dv)); +} + +/** + * Evaluate the normal a non-rational surface at the given parameters + * @param[in] srf Surface object + * @param u Parameter in the u-direction + * @param v Parameter in the v-direction + * @param[inout] normal Unit normal at of the surface at (u, v) + */ +template glm::vec<3, T> surfaceNormal(const Surface &srf, T u, T v) +{ + array2> ptder = surfaceDerivatives(srf, 1, u, v); + glm::vec<3, T> n = glm::cross(ptder(0, 1), ptder(1, 0)); + T n_len = glm::length(n); + if (!util::close(n_len, T(0))) + { + n /= n_len; + } + return n; +} + +/** + * Evaluate the normal of a rational surface at the given parameters + * @param[in] srf Rational Surface object + * @param u Parameter in the u-direction + * @param v Parameter in the v-direction + * @return Unit normal at of the surface at (u, v) + */ +template glm::vec<3, T> surfaceNormal(const RationalSurface &srf, T u, T v) +{ + array2> ptder = surfaceDerivatives(srf, 1, u, v); + glm::vec<3, T> n = glm::cross(ptder(0, 1), ptder(1, 0)); + T n_len = glm::length(n); + if (!util::close(n_len, T(0))) + { + n /= n_len; + } + return n; +} + +} // namespace tinynurbs + +#endif // TINYNURBS_EVALUATE_H diff --git a/source/LDYOM_R/include/tinynurbs/core/modify.h b/source/LDYOM_R/include/tinynurbs/core/modify.h new file mode 100644 index 00000000..71098256 --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/modify.h @@ -0,0 +1,743 @@ +/** + * Functions for modifying NURBS curves and surfaces. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_MODIFY_H +#define TINYNURBS_MODIFY_H + +#include +#include +#include +#include +#include +#include +#include + +namespace tinynurbs +{ + +///////////////////////////////////////////////////////////////////// + +namespace internal +{ + +/** + * Insert knots in the curve + * @param[in] deg Degree of the curve + * @param[in] knots Knot vector of the curve + * @param[in] cp Control points of the curve + * @param[in] u Parameter to insert knot(s) at + * @param[in] r Number of times to insert knot + * @param[out] new_knots Updated knot vector + * @param[out] new_cp Updated control points + */ +template +void curveKnotInsert(unsigned int deg, const std::vector &knots, + const std::vector> &cp, T u, unsigned int r, + std::vector &new_knots, std::vector> &new_cp) +{ + int k = findSpan(deg, knots, u); + unsigned int s = knotMultiplicity(knots, u); + assert(s <= deg); // Multiplicity cannot be greater than degree + if (s == deg) + { + new_knots = knots; + new_cp = cp; + return; + } + if ((r + s) > deg) + { + r = deg - s; + } + + // Insert new knots between k and (k + 1) + new_knots.resize(knots.size() + r); + for (int i = 0; i < k + 1; ++i) + { + new_knots[i] = knots[i]; + } + for (unsigned int i = 1; i < r + 1; ++i) + { + new_knots[k + i] = u; + } + for (int i = k + 1; i < knots.size(); ++i) + { + new_knots[i + r] = knots[i]; + } + // Copy unaffected control points + new_cp.resize(cp.size() + r); + for (int i = 0; i < k - deg + 1; ++i) + { + new_cp[i] = cp[i]; + } + for (int i = k - s; i < cp.size(); ++i) + { + new_cp[i + r] = cp[i]; + } + // Copy affected control points + std::vector> tmp; + tmp.resize(deg - s + 1); + for (int i = 0; i < deg - s + 1; ++i) + { + tmp[i] = cp[k - deg + i]; + } + // Modify affected control points + for (int j = 1; j <= r; ++j) + { + int L = k - deg + j; + for (int i = 0; i < deg - j - s + 1; ++i) + { + T a = (u - knots[L + i]) / (knots[i + k + 1] - knots[L + i]); + tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; + } + new_cp[L] = tmp[0]; + new_cp[k + r - j - s] = tmp[deg - j - s]; + } + int L = k - deg + r; + for (int i = L + 1; i < k - s; ++i) + { + new_cp[i] = tmp[i - L]; + } +} + +/** + * Insert knots in the surface along one direction + * @param[in] degree Degree of the surface along which to insert knot + * @param[in] knots Knot vector + * @param[in] cp 2D array of control points + * @param[in] knot Knot value to insert + * @param[in] r Number of times to insert + * @param[in] along_u Whether inserting along u-direction + * @param[out] new_knots Updated knot vector + * @param[out] new_cp Updated control points + */ +template +void surfaceKnotInsert(unsigned int degree, const std::vector &knots, + const array2> &cp, T knot, unsigned int r, bool along_u, + std::vector &new_knots, array2> &new_cp) +{ + int span = findSpan(degree, knots, knot); + unsigned int s = knotMultiplicity(knots, knot); + assert(s <= degree); // Knot multiplicity cannot be greater than degree + if (s == degree) + { + new_cp = cp; + new_knots = knots; + return; + } + if ((r + s) > degree) + { + r = degree - s; + } + + // Create a new knot vector + new_knots.resize(knots.size() + r); + for (int i = 0; i <= span; ++i) + { + new_knots[i] = knots[i]; + } + for (int i = 1; i <= r; ++i) + { + new_knots[span + i] = knot; + } + for (int i = span + 1; i < knots.size(); ++i) + { + new_knots[i + r] = knots[i]; + } + // Compute alpha + array2 alpha(degree - s, r + 1, T(0)); + for (int j = 1; j <= r; ++j) + { + int L = span - degree + j; + for (int i = 0; i <= degree - j - s; ++i) + { + alpha(i, j) = (knot - knots[L + i]) / (knots[i + span + 1] - knots[L + i]); + } + } + + // Create a temporary container for affected control points per row/column + std::vector> tmp(degree + 1); + + if (along_u) + { + // Create new control points with additional rows + new_cp.resize(cp.rows() + r, cp.cols()); + + // Update control points + // Each row is a u-isocurve, each col is a v-isocurve + for (int col = 0; col < cp.cols(); ++col) + { + // Copy unaffected control points + for (int i = 0; i <= span - degree; ++i) + { + new_cp(i, col) = cp(i, col); + } + for (int i = span - s; i < cp.rows(); ++i) + { + new_cp(i + r, col) = cp(i, col); + } + // Copy affected control points to temp array + for (int i = 0; i < degree - s + 1; ++i) + { + tmp[i] = cp(span - degree + i, col); + } + // Insert knot + for (int j = 1; j <= r; ++j) + { + int L = span - degree + j; + for (int i = 0; i <= degree - j - s; ++i) + { + T a = alpha(i, j); + tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; + } + new_cp(L, col) = tmp[0]; + new_cp(span + r - j - s, col) = tmp[degree - j - s]; + } + int L = span - degree + r; + for (int i = L + 1; i < span - s; ++i) + { + new_cp(i, col) = tmp[i - L]; + } + } + } + else + { + // Create new control points with additional columns + new_cp.resize(cp.rows(), cp.cols() + r); + + // Update control points + // Each row is a u-isocurve, each col is a v-isocurve + for (int row = 0; row < cp.rows(); ++row) + { + // Copy unaffected control points + for (int i = 0; i <= span - degree; ++i) + { + new_cp(row, i) = cp(row, i); + } + for (int i = span - s; i < cp.cols(); ++i) + { + new_cp(row, i + r) = cp(row, i); + } + // Copy affected control points to temp array + for (int i = 0; i < degree - s + 1; ++i) + { + tmp[i] = cp(row, span - degree + i); + } + // Insert knot + for (int j = 1; j <= r; ++j) + { + int L = span - degree + j; + for (int i = 0; i <= degree - j - s; ++i) + { + T a = alpha(i, j); + tmp[i] = (1 - a) * tmp[i] + a * tmp[i + 1]; + } + new_cp(row, L) = tmp[0]; + new_cp(row, span + r - j - s) = tmp[degree - j - s]; + } + int L = span - degree + r; + for (int i = L + 1; i < span - s; ++i) + { + new_cp(row, i) = tmp[i - L]; + } + } + } +} + +/** + * Split the curve into two + * @param[in] degree Degree of curve + * @param[in] knots Knot vector + * @param[in] control_points Array of control points + * @param[in] u Parameter to split curve + * @param[out] left_knots Knots of the left part of the curve + * @param[out] left_control_points Control points of the left part of the curve + * @param[out] right_knots Knots of the right part of the curve + * @param[out] right_control_points Control points of the right part of the curve + */ +template +void curveSplit(unsigned int degree, const std::vector &knots, + const std::vector> &control_points, T u, + std::vector &left_knots, std::vector> &left_control_points, + std::vector &right_knots, std::vector> &right_control_points) +{ + std::vector tmp_knots; + std::vector> tmp_cp; + + int span = findSpan(degree, knots, u); + int r = degree - knotMultiplicity(knots, u); + + internal::curveKnotInsert(degree, knots, control_points, u, r, tmp_knots, tmp_cp); + + left_knots.clear(); + right_knots.clear(); + left_control_points.clear(); + right_control_points.clear(); + + int span_l = findSpan(degree, tmp_knots, u) + 1; + for (int i = 0; i < span_l; ++i) + { + left_knots.push_back(tmp_knots[i]); + } + left_knots.push_back(u); + + for (int i = 0; i < degree + 1; ++i) + { + right_knots.push_back(u); + } + for (int i = span_l; i < tmp_knots.size(); ++i) + { + right_knots.push_back(tmp_knots[i]); + } + + int ks = span - degree + 1; + for (int i = 0; i < ks + r; ++i) + { + left_control_points.push_back(tmp_cp[i]); + } + for (int i = ks + r - 1; i < tmp_cp.size(); ++i) + { + right_control_points.push_back(tmp_cp[i]); + } +} + +/** + * Split the surface into two along given parameter direction + * @param[in] degree Degree of surface along given direction + * @param[in] knots Knot vector of surface along given direction + * @param[in] control_points Array of control points + * @param[in] param Parameter to split curve + * @param[in] along_u Whether the direction to split along is the u-direction + * @param[out] left_knots Knots of the left part of the curve + * @param[out] left_control_points Control points of the left part of the curve + * @param[out] right_knots Knots of the right part of the curve + * @param[out] right_control_points Control points of the right part of the curve + */ +template +void surfaceSplit(unsigned int degree, const std::vector &knots, + const array2> &control_points, T param, bool along_u, + std::vector &left_knots, array2> &left_control_points, + std::vector &right_knots, array2> &right_control_points) +{ + std::vector tmp_knots; + array2> tmp_cp; + + int span = findSpan(degree, knots, param); + unsigned int r = degree - knotMultiplicity(knots, param); + internal::surfaceKnotInsert(degree, knots, control_points, param, r, along_u, tmp_knots, + tmp_cp); + + left_knots.clear(); + right_knots.clear(); + + int span_l = findSpan(degree, tmp_knots, param) + 1; + for (int i = 0; i < span_l; ++i) + { + left_knots.push_back(tmp_knots[i]); + } + left_knots.push_back(param); + + for (int i = 0; i < degree + 1; ++i) + { + right_knots.push_back(param); + } + for (int i = span_l; i < tmp_knots.size(); ++i) + { + right_knots.push_back(tmp_knots[i]); + } + + int ks = span - degree + 1; + if (along_u) + { + size_t ii = 0; + left_control_points.resize(ks + r, tmp_cp.cols()); + for (int i = 0; i < ks + r; ++i) + { + for (int j = 0; j < tmp_cp.cols(); ++j) + { + left_control_points[ii++] = tmp_cp(i, j); + } + } + ii = 0; + right_control_points.resize(tmp_cp.rows() - ks - r + 1, tmp_cp.cols()); + for (int i = ks + r - 1; i < tmp_cp.rows(); ++i) + { + for (int j = 0; j < tmp_cp.cols(); ++j) + { + right_control_points[ii++] = tmp_cp(i, j); + } + } + } + else + { + size_t ii = 0; + left_control_points.resize(tmp_cp.rows(), ks + r); + for (int i = 0; i < tmp_cp.rows(); ++i) + { + for (int j = 0; j < ks + r; ++j) + { + left_control_points[ii++] = tmp_cp(i, j); + } + } + ii = 0; + right_control_points.resize(tmp_cp.rows(), tmp_cp.cols() - ks - r + 1); + for (int i = 0; i < tmp_cp.rows(); ++i) + { + for (int j = ks + r - 1; j < tmp_cp.cols(); ++j) + { + right_control_points[ii++] = tmp_cp(i, j); + } + } + } +} + +} // namespace internal + +///////////////////////////////////////////////////////////////////// + +/** + * Insert knots in the curve + * @param[in] crv Curve object + * @param[in] u Parameter to insert knot at + * @param[in] repeat Number of times to insert + * @return New curve with #repeat knots inserted at u + */ +template Curve curveKnotInsert(const Curve &crv, T u, unsigned int repeat = 1) +{ + Curve new_crv; + new_crv.degree = crv.degree; + internal::curveKnotInsert(crv.degree, crv.knots, crv.control_points, u, repeat, new_crv.knots, + new_crv.control_points); + return new_crv; +} + +/** + * Insert knots in the rational curve + * @param[in] crv RationalCurve object + * @param[in] u Parameter to insert knot at + * @param[in] repeat Number of times to insert + * @return New RationalCurve object with #repeat knots inserted at u + */ +template +RationalCurve curveKnotInsert(const RationalCurve &crv, T u, unsigned int repeat = 1) +{ + RationalCurve new_crv; + new_crv.degree = crv.degree; + + // Convert to homogenous coordinates + std::vector> Cw; + Cw.reserve(crv.control_points.size()); + for (int i = 0; i < crv.control_points.size(); ++i) + { + Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); + } + + // Perform knot insertion and get new knots and control points + std::vector> new_Cw; + std::vector new_knots; + internal::curveKnotInsert(crv.degree, crv.knots, Cw, u, repeat, new_crv.knots, new_Cw); + + // Convert back to cartesian coordinates + new_crv.control_points.reserve(new_Cw.size()); + new_crv.weights.reserve(new_Cw.size()); + for (int i = 0; i < new_Cw.size(); ++i) + { + new_crv.control_points.push_back(util::homogenousToCartesian(new_Cw[i])); + new_crv.weights.push_back(new_Cw[i].w); + } + return new_crv; +} + +/** + * Insert knots in the surface along u-direction + * @param[in] srf Surface object + * @param[in] u Knot value to insert + * @param[in] repeat Number of times to insert + * @return New Surface object after knot insertion + */ +template +Surface surfaceKnotInsertU(const Surface &srf, T u, unsigned int repeat = 1) +{ + Surface new_srf; + new_srf.degree_u = srf.degree_u; + new_srf.degree_v = srf.degree_v; + new_srf.knots_v = srf.knots_v; + internal::surfaceKnotInsert(new_srf.degree_u, srf.knots_u, srf.control_points, u, repeat, true, + new_srf.knots_u, new_srf.control_points); + return new_srf; +} + +/** + * Insert knots in the rational surface along u-direction + * @param[in] srf RationalSurface object + * @param[in] u Knot value to insert + * @param[in] repeat Number of times to insert + * @return New RationalSurface object after knot insertion + */ +template +RationalSurface surfaceKnotInsertU(const RationalSurface &srf, T u, unsigned int repeat = 1) +{ + RationalSurface new_srf; + new_srf.degree_u = srf.degree_u; + new_srf.degree_v = srf.degree_v; + new_srf.knots_v = srf.knots_v; + + // Original control points in homogenous coordinates + array2> Cw(srf.control_points.rows(), srf.control_points.cols()); + for (int i = 0; i < srf.control_points.rows(); ++i) + { + for (int j = 0; j < srf.control_points.cols(); ++j) + { + Cw(i, j) = util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); + } + } + + // New knots and new homogenous control points after knot insertion + std::vector new_knots_u; + array2> new_Cw; + internal::surfaceKnotInsert(srf.degree_u, srf.knots_u, Cw, u, repeat, true, new_srf.knots_u, + new_Cw); + + // Convert back to cartesian coordinates + new_srf.control_points.resize(new_Cw.rows(), new_Cw.cols()); + new_srf.weights.resize(new_Cw.rows(), new_Cw.cols()); + for (int i = 0; i < new_Cw.rows(); ++i) + { + for (int j = 0; j < new_Cw.cols(); ++j) + { + new_srf.control_points(i, j) = util::homogenousToCartesian(new_Cw(i, j)); + new_srf.weights(i, j) = new_Cw(i, j).w; + } + } + return new_srf; +} + +/** + * Insert knots in the surface along v-direction + * @param[in] srf Surface object + * @param[in] v Knot value to insert + * @param[in] repeat Number of times to insert + * @return New Surface object after knot insertion + */ +template +Surface surfaceKnotInsertV(const Surface &srf, T v, unsigned int repeat = 1) +{ + Surface new_srf; + new_srf.degree_u = srf.degree_u; + new_srf.degree_v = srf.degree_v; + new_srf.knots_u = srf.knots_u; + // New knots and new control points after knot insertion + internal::surfaceKnotInsert(srf.degree_v, srf.knots_v, srf.control_points, v, repeat, false, + new_srf.knots_v, new_srf.control_points); + return new_srf; +} + +/** + * Insert knots in the rational surface along v-direction + * @param[in] srf RationalSurface object + * @param[in] v Knot value to insert + * @param[in] repeat Number of times to insert + * @return New RationalSurface object after knot insertion + */ +template +RationalSurface surfaceKnotInsertV(const RationalSurface &srf, T v, unsigned int repeat = 1) +{ + RationalSurface new_srf; + new_srf.degree_u = srf.degree_u; + new_srf.degree_v = srf.degree_v; + new_srf.knots_u = srf.knots_u; + // Original control points in homogenous coordinates + array2> Cw(srf.control_points.rows(), srf.control_points.cols()); + for (int i = 0; i < srf.control_points.rows(); ++i) + { + for (int j = 0; j < srf.control_points.cols(); ++j) + { + Cw(i, j) = util::cartesianToHomogenous(srf.control_points(i, j), srf.weights(i, j)); + } + } + + // New knots and new homogenous control points after knot insertion + std::vector new_knots_v; + array2> new_Cw; + internal::surfaceKnotInsert(srf.degree_v, srf.knots_v, Cw, v, repeat, false, new_srf.knots_v, + new_Cw); + + // Convert back to cartesian coordinates + new_srf.control_points.resize(new_Cw.rows(), new_Cw.cols()); + new_srf.weights.resize(new_Cw.rows(), new_Cw.cols()); + for (int i = 0; i < new_Cw.rows(); ++i) + { + for (int j = 0; j < new_Cw.cols(); ++j) + { + new_srf.control_points(i, j) = util::homogenousToCartesian(new_Cw(i, j)); + new_srf.weights(i, j) = new_Cw(i, j).w; + } + } + return new_srf; +} + +/** + * Split a curve into two + * @param[in] crv Curve object + * @param[in] u Parameter to split at + * @return Tuple with first half and second half of the curve + */ +template std::tuple, Curve> curveSplit(const Curve &crv, T u) +{ + Curve left, right; + left.degree = crv.degree; + right.degree = crv.degree; + internal::curveSplit(crv.degree, crv.knots, crv.control_points, u, left.knots, + left.control_points, right.knots, right.control_points); + return std::make_tuple(std::move(left), std::move(right)); +} + +/** + * Split a rational curve into two + * @param[in] crv RationalCurve object + * @param[in] u Parameter to split at + * @return Tuple with first half and second half of the curve + */ +template +std::tuple, RationalCurve> curveSplit(const RationalCurve &crv, T u) +{ + RationalCurve left, right; + left.degree = crv.degree; + right.degree = crv.degree; + + std::vector> Cw, left_Cw, right_Cw; + Cw.reserve(crv.control_points.size()); + for (int i = 0; i < crv.control_points.size(); ++i) + { + Cw.push_back(util::cartesianToHomogenous(crv.control_points[i], crv.weights[i])); + } + + internal::curveSplit(crv.degree, crv.knots, Cw, u, left.knots, left_Cw, right.knots, right_Cw); + + left.control_points.reserve(left_Cw.size()); + left.weights.reserve(left_Cw.size()); + right.control_points.reserve(right_Cw.size()); + right.weights.reserve(right_Cw.size()); + for (int i = 0; i < left_Cw.size(); ++i) + { + left.control_points.push_back(util::homogenousToCartesian(left_Cw[i])); + left.weights.push_back(left_Cw[i].w); + } + for (int i = 0; i < right_Cw.size(); ++i) + { + right.control_points.push_back(util::homogenousToCartesian(right_Cw[i])); + right.weights.push_back(right_Cw[i].w); + } + return std::make_tuple(std::move(left), std::move(right)); +} + +/** + * Split a surface into two along u-direction + * @param[in] srf Surface object + * @param[in] u Parameter along u-direction to split the surface + * @return Tuple with first and second half of the surfaces + */ +template std::tuple, Surface> surfaceSplitU(const Surface &srf, T u) +{ + Surface left, right; + left.degree_u = srf.degree_u; + left.degree_v = srf.degree_v; + left.knots_v = srf.knots_v; + right.degree_u = srf.degree_u; + right.degree_v = srf.degree_v; + right.knots_v = srf.knots_v; + internal::surfaceSplit(srf.degree_u, srf.knots_u, srf.control_points, u, true, left.knots_u, + left.control_points, right.knots_u, right.control_points); + return std::make_tuple(std::move(left), std::move(right)); +} + +/** + * Split a rational surface into two along u-direction + * @param[in] srf RationalSurface object + * @param[in] u Parameter along u-direction to split the surface + * @return Tuple with first and second half of the surfaces + */ +template +std::tuple, RationalSurface> surfaceSplitU(const RationalSurface &srf, T u) +{ + RationalSurface left, right; + left.degree_u = srf.degree_u; + left.degree_v = srf.degree_v; + left.knots_v = srf.knots_v; + right.degree_u = srf.degree_u; + right.degree_v = srf.degree_v; + right.knots_v = srf.knots_v; + + // Compute homogenous coordinates of control points and weights + array2> Cw = util::cartesianToHomogenous(srf.control_points, srf.weights); + + // Split surface with homogenous coordinates + array2> left_Cw, right_Cw; + internal::surfaceSplit(srf.degree_u, srf.knots_u, Cw, u, true, left.knots_u, left_Cw, + right.knots_u, right_Cw); + + // Convert back to cartesian coordinates + util::homogenousToCartesian(left_Cw, left.control_points, left.weights); + util::homogenousToCartesian(right_Cw, right.control_points, right.weights); + + return std::make_tuple(std::move(left), std::move(right)); +} + +/** + * Split a surface into two along v-direction + * @param[in] srf Surface object + * @param[in] v Parameter along v-direction to split the surface + * @return Tuple with first and second half of the surfaces + */ +template std::tuple, Surface> surfaceSplitV(const Surface &srf, T v) +{ + Surface left, right; + left.degree_u = srf.degree_u; + left.degree_v = srf.degree_v; + left.knots_u = srf.knots_u; + right.degree_u = srf.degree_u; + right.degree_v = srf.degree_v; + right.knots_u = srf.knots_u; + internal::surfaceSplit(srf.degree_v, srf.knots_v, srf.control_points, v, false, left.knots_v, + left.control_points, right.knots_v, right.control_points); + return std::make_tuple(std::move(left), std::move(right)); +} + +/** + * Split a rational surface into two along v-direction + * @param[in] srf RationalSurface object + * @param[in] v Parameter along v-direction to split the surface + * @return Tuple with first and second half of the surfaces + */ +template +std::tuple, RationalSurface> surfaceSplitV(const RationalSurface &srf, T v) +{ + RationalSurface left, right; + left.degree_u = srf.degree_u; + left.degree_v = srf.degree_v; + left.knots_u = srf.knots_u; + right.degree_u = srf.degree_u; + right.degree_v = srf.degree_v; + right.knots_u = srf.knots_u; + + // Compute homogenous coordinates of control points and weights + array2> Cw = util::cartesianToHomogenous(srf.control_points, srf.weights); + + // Split surface with homogenous coordinates + array2> left_Cw, right_Cw; + internal::surfaceSplit(srf.degree_v, srf.knots_v, Cw, v, false, left.knots_v, left_Cw, + right.knots_v, right_Cw); + + // Convert back to cartesian coordinates + util::homogenousToCartesian(left_Cw, left.control_points, left.weights); + util::homogenousToCartesian(right_Cw, right.control_points, right.weights); + + return std::make_tuple(std::move(left), std::move(right)); +} + +} // namespace tinynurbs + +#endif // TINYNURBS_MODIFY_H diff --git a/source/LDYOM_R/include/tinynurbs/core/surface.h b/source/LDYOM_R/include/tinynurbs/core/surface.h new file mode 100644 index 00000000..ce42b00a --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/core/surface.h @@ -0,0 +1,85 @@ +/** + * The Surface and RationalSurface classes represent non-rational and rational + * NURBS surfaces, respectively. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_SURFACE_H +#define TINYNURBS_SURFACE_H + +#include "../util/array2.h" +#include "glm/glm.hpp" +#include +#include + +namespace tinynurbs +{ + +// Forward declaration +template struct RationalSurface; + +/** +Struct for representing a non-rational NURBS surface +\tparam T Data type of control points and weights (float or double) +*/ +template struct Surface +{ + unsigned int degree_u, degree_v; + std::vector knots_u, knots_v; + array2> control_points; + + Surface() = default; + Surface(const RationalSurface &srf) + : degree_u(srf.degree_u), degree_v(srf.degree_v), knots_u(srf.knots_u), + knots_v(srf.knots_v), control_points(srf.control_points) + { + } + Surface(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, + const std::vector &knots_v, array2> control_points) + : degree_u(degree_u), degree_v(degree_v), knots_u(knots_u), knots_v(knots_v), + control_points(control_points) + { + } +}; + +/** +Struct for representing a non-rational NURBS surface +\tparam T Data type of control points and weights (float or double) +*/ +template struct RationalSurface +{ + unsigned int degree_u, degree_v; + std::vector knots_u, knots_v; + array2> control_points; + array2 weights; + + RationalSurface() = default; + RationalSurface(const Surface &srf, const array2 &weights) + : degree_u(srf.degree_u), degree_v(srf.degree_v), knots_u(srf.knots_u), + knots_v(srf.knots_v), control_points(srf.control_points), weights(weights) + { + } + RationalSurface(const Surface &srf) + : RationalSurface(srf, array2(srf.control_points.rows(), srf.control_points.cols(), 1.0)) + { + } + RationalSurface(unsigned int degree_u, unsigned int degree_v, const std::vector &knots_u, + const std::vector &knots_v, const array2> &control_points, + const array2 &weights) + : degree_u(degree_u), degree_v(degree_v), knots_u(knots_u), knots_v(knots_v), + control_points(control_points), weights(weights) + { + } +}; + +// Typedefs for ease of use +typedef Surface Surface3f; +typedef Surface Surface3d; +typedef RationalSurface RationalSurface3f; +typedef RationalSurface RationalSurface3d; + +} // namespace tinynurbs + +#endif // TINYNURBS_SURFACE_H diff --git a/source/LDYOM_R/include/tinynurbs/tinynurbs.h b/source/LDYOM_R/include/tinynurbs/tinynurbs.h new file mode 100644 index 00000000..2091eb49 --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/tinynurbs.h @@ -0,0 +1,17 @@ +/** + * Import the entire library into the tinynurbs namespace. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ +#ifndef TINYNURBS_H +#define TINYNURBS_H + +#include "core/basis.h" +#include "core/check.h" +#include "core/curve.h" +#include "core/evaluate.h" +#include "core/modify.h" +#include "core/surface.h" + +#endif // TINYNURBS_H diff --git a/source/LDYOM_R/include/tinynurbs/util/array2.h b/source/LDYOM_R/include/tinynurbs/util/array2.h new file mode 100644 index 00000000..af36defe --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/util/array2.h @@ -0,0 +1,81 @@ +/** + * A simple class for 2D runtime arrays. Mainly used for control points and + * weights of surfaces. + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. + */ + +#ifndef TINYNURBS_ARRAY2_H +#define TINYNURBS_ARRAY2_H + +#include +#include +#include + +namespace tinynurbs +{ + +/** + * A simple class for representing 2D runtime arrays. + */ +template class array2 +{ + public: + array2() = default; + array2(const array2 &arr) = default; + array2 &operator=(const array2 &arr) = default; + array2(array2 &&arr) = default; + array2 &operator=(array2 &&arr) = default; + array2(size_t rows, size_t cols, T default_value = T()) { resize(rows, cols, default_value); } + array2(size_t rows, size_t cols, const std::vector &arr) + : rows_(rows), cols_(cols), data_(arr) + { + if (arr.size() != rows * cols) + { + throw std::runtime_error("Dimensions do not match with size of vector"); + } + } + void resize(size_t rows, size_t cols, T val = T()) + { + data_.resize(rows * cols, val); + rows_ = rows; + cols_ = cols; + } + void clear() + { + rows_ = cols_ = 0; + data_.clear(); + } + T operator()(size_t row, size_t col) const + { + assert(row < rows_ && col < cols_); + return data_[row * cols_ + col]; + } + T &operator()(size_t row, size_t col) + { + assert(row < rows_ && col < cols_); + return data_[row * cols_ + col]; + } + T operator[](size_t idx) const + { + assert(idx < data_.size()); + return data_[idx]; + } + T &operator[](size_t idx) + { + assert(idx < data_.size()); + return data_[idx]; + } + size_t rows() const { return rows_; } + size_t cols() const { return cols_; } + size_t size() const { return data_.size(); } + + private: + size_t rows_, cols_; + std::vector data_; +}; + +} // namespace tinynurbs + +#endif // TINYNURBS_ARRAY2_H diff --git a/source/LDYOM_R/include/tinynurbs/util/util.h b/source/LDYOM_R/include/tinynurbs/util/util.h new file mode 100644 index 00000000..02551824 --- /dev/null +++ b/source/LDYOM_R/include/tinynurbs/util/util.h @@ -0,0 +1,196 @@ +/** + * Helper functions + * + * Use of this source code is governed by a BSD-style license that can be found in + * the LICENSE file. +*/ + +#ifndef TINYNURBS_UTIL +#define TINYNURBS_UTIL + +#include "array2.h" +#include +#include + +namespace tinynurbs +{ +namespace util +{ + +/** + * Convert an nd point in homogenous coordinates to an (n-1)d point in cartesian + * coordinates by perspective division + * @param[in] pt Point in homogenous coordinates + * @return Point in cartesian coordinates + */ +template +inline glm::vec homogenousToCartesian(const glm::vec &pt) +{ + return glm::vec(pt / pt[pt.length() - 1]); +} + +/** + * Convert a list of nd points in homogenous coordinates to a list of (n-1)d points in cartesian + * coordinates by perspective division + * @param[in] ptsws Points in homogenous coordinates + * @param[out] pts Points in cartesian coordinates + * @param[out] ws Homogenous weights + */ +template +inline void homogenousToCartesian(const std::vector> &ptsws, + std::vector> &pts, std::vector &ws) +{ + pts.clear(); + ws.clear(); + pts.reserve(ptsws.size()); + ws.reserve(ptsws.size()); + for (int i = 0; i < ptsws.size(); ++i) + { + const glm::vec &ptw_i = ptsws[i]; + pts.push_back(glm::vec(ptw_i / ptw_i[ptw_i.length() - 1])); + ws.push_back(ptw_i[ptw_i.length() - 1]); + } +} + +/** + * Convert a 2D list of nd points in homogenous coordinates to cartesian + * coordinates by perspective division + * @param[in] ptsws Points in homogenous coordinates + * @param[out] pts Points in cartesian coordinates + * @param[out] ws Homogenous weights + */ +template +inline void homogenousToCartesian(const array2> &ptsws, + array2> &pts, array2 &ws) +{ + pts.resize(ptsws.rows(), ptsws.cols()); + ws.resize(ptsws.rows(), ptsws.cols()); + for (int i = 0; i < ptsws.rows(); ++i) + { + for (int j = 0; j < ptsws.cols(); ++j) + { + const glm::vec &ptw_ij = ptsws(i, j); + T w_ij = ptw_ij[nd - 1]; + pts(i, j) = glm::vec(ptw_ij / w_ij); + ws(i, j) = w_ij; + } + } +} + +/** + * Convert an nd point in cartesian coordinates to an (n+1)d point in homogenous + * coordinates + * @param[in] pt Point in cartesian coordinates + * @param[in] w Weight + * @return Input point in homogenous coordinates + */ +template +inline glm::vec cartesianToHomogenous(const glm::vec &pt, T w) +{ + return glm::vec(pt * w, w); +} + +/** + * Convert list of points in cartesian coordinates to homogenous coordinates + * @param[in] pts Points in cartesian coordinates + * @param[in] ws Weights + * @return Points in homogenous coordinates + */ +template +inline std::vector> +cartesianToHomogenous(const std::vector> &pts, const std::vector &ws) +{ + std::vector> Cw; + Cw.reserve(pts.size()); + for (int i = 0; i < pts.size(); ++i) + { + Cw.push_back(cartesianToHomogenous(pts[i], ws[i])); + } + return Cw; +} + +/** + * Convert 2D list of points in cartesian coordinates to homogenous coordinates + * @param[in] pts Points in cartesian coordinates + * @param[in] ws Weights + * @return Points in homogenous coordinates + */ +template +inline array2> cartesianToHomogenous(const array2> &pts, + const array2 &ws) +{ + array2> Cw(pts.rows(), pts.cols()); + for (int i = 0; i < pts.rows(); ++i) + { + for (int j = 0; j < pts.cols(); ++j) + { + Cw(i, j) = util::cartesianToHomogenous(pts(i, j), ws(i, j)); + } + } + return Cw; +} + +/** + * Convert an (n+1)d point to an nd point without perspective division + * by truncating the last dimension + * @param[in] pt Point in homogenous coordinates + * @return Input point in cartesian coordinates + */ +template +inline glm::vec truncateHomogenous(const glm::vec &pt) +{ + return glm::vec(pt); +} + +/** + * Compute the binomial coefficient (nCk) using the formula + * \product_{i=0}^k (n + 1 - i) / i + */ +inline unsigned int binomial(unsigned int n, unsigned int k) +{ + unsigned int result = 1; + if (k > n) + { + return 0; + } + for (unsigned int i = 1; i <= k; ++i) + { + result *= (n + 1 - i); + result /= i; + } + return result; +} + +/** + * Check if two numbers are close enough within eps + * @param[in] a First number + * @param[in] b Second number + * @param[in] eps Tolerance for checking closeness + * @return Whether the numbers are close w.r.t. the tolerance + */ +template inline bool close(T a, T b, double eps = std::numeric_limits::epsilon()) +{ + return (std::abs(a - b) < eps) ? true : false; +} + +/** + * Map numbers from one interval to another + * @param[in] val Number to map to another range + * @param[in] old_min Minimum value of original range + * @param[in] old_max Maximum value of original range + * @param[in] new_min Minimum value of new range + * @param[in] new_max Maximum value of new range + * @return Number mapped to new range + */ +template inline T mapToRange(T val, T old_min, T old_max, T new_min, T new_max) +{ + T old_range = old_max - old_min; + T new_range = new_max - new_min; + return (((val - old_min) * new_range) / old_range) + new_min; +} + +} // namespace util + +} // namespace tinynurbs + +#endif // TINYNURBS_UTIL diff --git a/source/LDYOM_R/utils/CameraExtendWrapper.cpp b/source/LDYOM_R/utils/CameraExtendWrapper.cpp new file mode 100644 index 00000000..52c4eee5 --- /dev/null +++ b/source/LDYOM_R/utils/CameraExtendWrapper.cpp @@ -0,0 +1,44 @@ +#include + +#include "../Data/CCameraExtend.h" +#include "../Data/CutsceneMutex.h" + +class CutsceneMutexGuardLuaWrap { +private: + CutsceneMutexGuard *mutex; + +public: + CutsceneMutexGuardLuaWrap() { + mutex = new CutsceneMutexGuard(); + } + + ~CutsceneMutexGuardLuaWrap() { + delete mutex; + } + + void unlock() { + delete mutex; + mutex = nullptr; + } +}; + +void cameraExtendWrapper(sol::state &state) { + auto ldTable = state["ld"].get(); + ldTable["TheCameraExtend"] = &TheCameraExtend; + state.new_usertype("CCameraExtend", + "setExtendMode", &CCameraExtend::setExtendMode, + "attachToEntity", &CCameraExtend::attachToEntity, + "playCameraPath", &CCameraExtend::playCameraPath, + "stopCameraPath", &CCameraExtend::stopCameraPath, + "isPlayingPath", &CCameraExtend::isPlayingPath, + "getPathProgress", &CCameraExtend::getPathProgress + ); + auto cameraMutex = ldTable.create("CameraMutex"); + cameraMutex["isLocked"] = CutsceneMutex::isLocked; + ldTable["createCutsceneGuard"] = []() { + return std::make_shared(); + }; + ldTable["destroyCutsceneGuard"] = [](const std::shared_ptr &guard) { + guard->unlock(); + }; +} diff --git a/source/LDYOM_R/utils/CameraPathWrapper.cpp b/source/LDYOM_R/utils/CameraPathWrapper.cpp new file mode 100644 index 00000000..853e0b0b --- /dev/null +++ b/source/LDYOM_R/utils/CameraPathWrapper.cpp @@ -0,0 +1,20 @@ +#include + +#include "../Data/CameraPath.h" + +void cameraPathWrapper(sol::state &state) { + state.new_usertype("LDCameraPath", + sol::no_constructor, + "getName", &CameraPath::getName, + "copy", &CameraPath::copy, + "getName", &CameraPath::getName, + "getUuid", &CameraPath::getUuid, + "getPositionCurve", &CameraPath::getPositionCurve, + "isCustomControlKnots", &CameraPath::isCustomControlKnots, + "getRotationsCurve", &CameraPath::getRotationsCurve, + "getRotationsEasing", &CameraPath::getRotationsEasing, + "getTime", &CameraPath::getTime, + "isCatmullRomRotations", &CameraPath::isCatmullRomRotations, + "isValid", &CameraPath::isValid + ); +} diff --git a/source/LDYOM_R/utils/KTCoroWrapper.cpp b/source/LDYOM_R/utils/KTCoroWrapper.cpp index e479fe3e..31b254cf 100644 --- a/source/LDYOM_R/utils/KTCoroWrapper.cpp +++ b/source/LDYOM_R/utils/KTCoroWrapper.cpp @@ -1,10 +1,8 @@ #include #include -#include "imgui_notify.h" #include "ktcoro_wait.hpp" #include "LuaEngine.h" -#include "easylogging/easylogging++.h" void ktcoroWrapper(sol::state &state) { state.new_usertype("ktcoro_tasklist", sol::no_constructor, diff --git a/source/LDYOM_R/utils/LDyomWrappers.cpp b/source/LDYOM_R/utils/LDyomWrappers.cpp index 45d34f26..29f95c01 100644 --- a/source/LDYOM_R/utils/LDyomWrappers.cpp +++ b/source/LDYOM_R/utils/LDyomWrappers.cpp @@ -17,6 +17,7 @@ extern void visualEffectWrapper(sol::state &state); extern void checkpointWrapper(sol::state &state); extern void sceneSettingsWrapper(sol::state &state); extern void weaponWrapper(sol::state &state); +extern void cameraPathWrapper(sol::state &state); extern void eventsWrapper(sol::state &state); extern void settingsWrapper(sol::state &state); @@ -24,6 +25,8 @@ extern void taskerWrapper(sol::state &state); extern void hotKeyServiceWrapper(sol::state &state); extern void modelsServiceWrapper(sol::state &state); extern void carrecPathServiceWrapper(sol::state &state); +extern void cameraExtendWrapper(sol::state &state); + void ldyomWrapper(sol::state &state) { projectsServiceWrapper(state); @@ -43,10 +46,12 @@ void ldyomWrapper(sol::state &state) { checkpointWrapper(state); sceneSettingsWrapper(state); weaponWrapper(state); + cameraPathWrapper(state); eventsWrapper(state); settingsWrapper(state); taskerWrapper(state); hotKeyServiceWrapper(state); modelsServiceWrapper(state); carrecPathServiceWrapper(state); + cameraExtendWrapper(state); } diff --git a/source/LDYOM_R/utils/ProjectsService.cpp b/source/LDYOM_R/utils/ProjectsService.cpp index a7bae683..5cf9dfca 100644 --- a/source/LDYOM_R/utils/ProjectsService.cpp +++ b/source/LDYOM_R/utils/ProjectsService.cpp @@ -56,6 +56,7 @@ #include "zip.h" #include "../Data/FollowCarrecPathVehicleObjective.h" +#include "../Data/PlayCameraPath.h" #include "boost/archive/xml_oarchive.hpp" #include "boost/serialization/utility.hpp" #include "easylogging/easylogging++.h" @@ -82,6 +83,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN {"audio", c.getAudio()}, {"visualEffects", c.getVisualEffects()}, {"checkpoints", c.getCheckpoints()}, + {"cameraPaths", c.getCameraPaths()}, {"sceneSettings", c.getSceneSettings()}, {"toggleSceneSettings", c.isToggleSceneSettings()}, }; @@ -121,7 +123,9 @@ NLOHMANN_JSON_NAMESPACE_BEGIN case 9: jsonObjectives.push_back(fast_dynamic_cast(*objective)); break; - + case 10: + jsonObjectives.push_back(fast_dynamic_cast(*objective)); + break; default: break; } @@ -326,6 +330,11 @@ NLOHMANN_JSON_NAMESPACE_BEGIN std::make_unique( jsonObjective.get())); break; + case 10: + objectives.emplace_back( + std::make_unique( + jsonObjective.get())); + break; default: break; } @@ -539,6 +548,9 @@ NLOHMANN_JSON_NAMESPACE_BEGIN j.at("audio").get>>(), j.at("visualEffects").get>>(), j.at("checkpoints").get>>(), + j.contains("cameraPaths") + ? j.at("cameraPaths").get>>() + : std::vector>(), j.at("sceneSettings").get(), j.at("toggleSceneSettings").get() }; @@ -816,6 +828,7 @@ void ProjectsService::loadProjectData(const std::filesystem::path &projectDirect scene->getAudio().swap(s.getAudio()); scene->getVisualEffects().swap(s.getVisualEffects()); scene->getCheckpoints().swap(s.getCheckpoints()); + scene->getCameraPaths().swap(s.getCameraPaths()); scene->getSceneSettings() = s.getSceneSettings(); scene->isToggleSceneSettings() = s.isToggleSceneSettings(); scene->getObjectives().swap(s.getObjectives()); diff --git a/source/LDYOM_R/utils/SceneWrapper.cpp b/source/LDYOM_R/utils/SceneWrapper.cpp index c5ac74b9..881e2891 100644 --- a/source/LDYOM_R/utils/SceneWrapper.cpp +++ b/source/LDYOM_R/utils/SceneWrapper.cpp @@ -17,6 +17,7 @@ void sceneWrapper(sol::state &state) { "getAudio", &Scene::getAudio, "getVisualEffects", &Scene::getVisualEffects, "getCheckpoints", &Scene::getCheckpoints, + "getCameraPaths", &Scene::getCameraPaths, "getSceneSettings", &Scene::getSceneSettings, "isToggleSceneSettings", &Scene::isToggleSceneSettings, "createNewActor", &Scene::createNewActor, @@ -29,6 +30,7 @@ void sceneWrapper(sol::state &state) { "createNewAudio", &Scene::createNewAudio, "createNewVisualEffect", &Scene::createNewVisualEffect, "createNewCheckpoint", &Scene::createNewCheckpoint, + "createNewCameraPath", &Scene::createNewCameraPath, "updateEditorObjectsCollision", &Scene::updateEditorObjectsCollision, "unloadEditorScene", &Scene::unloadEditorScene, "unloadProjectScene", &Scene::unloadProjectScene, diff --git a/source/LDYOM_R/utils/indexUuidWrapper.cpp b/source/LDYOM_R/utils/indexUuidWrapper.cpp index c5ff44c7..c6cc226d 100644 --- a/source/LDYOM_R/utils/indexUuidWrapper.cpp +++ b/source/LDYOM_R/utils/indexUuidWrapper.cpp @@ -3,6 +3,7 @@ #include "../Data/Actor.h" #include "../Data/Audio.h" #include "../Data/BaseObjective.h" +#include "../Data/CameraPath.h" #include "../Data/Checkpoint.h" #include "../Data/Object.h" #include "../Data/Particle.h" @@ -57,5 +58,8 @@ void indexUuidWrapper(sol::state &state) { }, [&indexByUuuidGeneric](const std::vector> &vector, const std::string &uuid) { return indexByUuuidGeneric(vector, uuid); + }, [&indexByUuuidGeneric](const std::vector> &vector, + const std::string &uuid) { + return indexByUuuidGeneric(vector, uuid); })); } diff --git a/source/LDYOM_R/utils/pluginSdkWrapper.cpp b/source/LDYOM_R/utils/pluginSdkWrapper.cpp index 4f465733..2e9fcf44 100644 --- a/source/LDYOM_R/utils/pluginSdkWrapper.cpp +++ b/source/LDYOM_R/utils/pluginSdkWrapper.cpp @@ -7,7 +7,9 @@ #include void pluginSdkWrapper(sol::state &state) { - state["time"].get_or_create().set("snTimeInMilliseconds", &CTimer::m_snTimeInMilliseconds); + state["time"].get_or_create().set("getSnTimeInMilliseconds", [] { + return CTimer::m_snTimeInMilliseconds; + }); state.set("distanceBetweenPoints", [](const float x1, const float y1, const float z1, const float x2, const float y2, const float z2) { return DistanceBetweenPoints(CVector(x1, y1, z1), CVector(x2, y2, z2)); diff --git a/source/LDYOM_R/utils/utils.cpp b/source/LDYOM_R/utils/utils.cpp index f6a2d8e9..043cb358 100644 --- a/source/LDYOM_R/utils/utils.cpp +++ b/source/LDYOM_R/utils/utils.cpp @@ -1,5 +1,6 @@ #include "utils.h" +#include #include #include #include @@ -15,6 +16,7 @@ #include "memsafe.h" #include "plugin.h" #include "strUtils.h" +#include "../Data/CCameraExtend.h" bool utils::Combo(const char *label, int *currentItem, const std::vector &items, int count) { const int size = count == 0 ? static_cast(items.size()) : count;